From f20fecf0d048c199ec38a23905685c3279e2f1d0 Mon Sep 17 00:00:00 2001
From: Jonathan Cremin <jonathan@crem.in>
Date: Fri, 5 Dec 2014 16:26:01 +0000
Subject: [PATCH] Add youtube to results, persist to mongo

---
 README.md                    |  1 -
 app.js                       | 14 +++++++++
 lib/services/youtube.js      | 50 ++++++++++++++++++++++++++++++
 package.json                 |  1 +
 public/stylesheets/style.css | 21 +++++++++++++
 routes/share.js              | 60 +++++++++++++++++++-----------------
 views/album.ejs              |  9 ++++--
 7 files changed, 125 insertions(+), 31 deletions(-)
 create mode 100644 lib/services/youtube.js

diff --git a/README.md b/README.md
index be8af1c..17684ba 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,5 @@ This is in super early development and is incapable of handling getting dugg, ne
 On the immediate todo list:
 
 * Use album release year for additional sanity check on matches
-* Maybe drop everything from the first left-hand bracket in album names to improve matches **after** failing to get a good match
 * Handle expected and unexpected errors better than the current crash-fest
 * Add some kind of persistence or caching so it could take a pummeling and not get me banned from the various services
diff --git a/app.js b/app.js
index b56d2c7..135d947 100644
--- a/app.js
+++ b/app.js
@@ -8,6 +8,7 @@ var session = require('express-session');
 var cookieParser = require('cookie-parser');
 var flash = require('connect-flash');
 var bodyParser = require('body-parser');
+var pmongo = require('promised-mongo');
 
 var search = require('./routes/search');
 var share = require('./routes/share');
@@ -34,6 +35,19 @@ app.use(session({
 app.use(flash());
 app.use(express.static(path.join(__dirname, 'public')));
 
+var db;
+if (process.env.MONGOHQ_URL) {
+  console.log("Connecting to MongoHQ")
+  db = pmongo(process.env.MONGOHQ_URL, ['matches']);
+} else {
+  db = pmongo('match-audio', ['matches']);
+}
+
+app.use(function(req, res, next) {
+  req.db = res.db = db;
+  next();
+})
+
 // force SSL
 app.get('*', function(req,res,next) {
   if (req.headers['cf-visitor'] && req.headers['cf-visitor'] != '{"scheme":"https"}') {
diff --git a/lib/services/youtube.js b/lib/services/youtube.js
new file mode 100644
index 0000000..a73a614
--- /dev/null
+++ b/lib/services/youtube.js
@@ -0,0 +1,50 @@
+"use strict";
+var parse = require('url').parse;
+var request = require('superagent');
+var Q = require('q');
+
+module.exports.id = "youtube";
+
+if (!process.env.YOUTUBE_KEY) {
+  console.warn("YOUTUBE_KEY environment variable not found, deactivating Youtube.");
+  return;
+}
+
+var credentials = {
+  key: process.env.YOUTUBE_KEY,
+};
+
+var apiRoot = "https://www.googleapis.com/youtube/v3";
+
+module.exports.match = function(url, type) {
+  return false;
+};
+
+module.exports.search = function(data) {
+  var deferred = Q.defer();
+  var query, album;
+  var type = data.type;
+
+  if (type == "album") {
+    query = data.artist.name + " " + data.name;
+    album = data.name;
+  } else if (type == "track") {
+    query = data.artist.name + " " + data.album.name + " " + data.name;
+    album = data.album.name
+  }
+
+  var path = "/search?part=snippet&q=" + encodeURIComponent(query) + "&type=video&videoCaption=any&key=" + credentials.key;
+  request.get(apiRoot + path, function(res) {
+    var result = res.body.items[0];
+    deferred.resolve({
+      service: "youtube",
+      type: "video",
+      id: result.id.videoId,
+      name: result.snippet.title,
+      streamUrl: "https://www.youtube.com/watch?v=" + result.id.videoId,
+      purchaseUrl: null,
+      artwork: result.snippet.thumbnails.medium.url,
+    });
+  });
+  return deferred.promise;
+};
diff --git a/package.json b/package.json
index cedcbe1..d2afced 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
     "helmet": "^0.5.2",
     "morgan": "~1.3.0",
     "playmusic": "^1.1.0",
+    "promised-mongo": "^0.11.1",
     "q": "^1.1.2",
     "rdio": "^1.5.2",
     "serve-favicon": "~2.1.3",
diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css
index dda730f..30ff136 100644
--- a/public/stylesheets/style.css
+++ b/public/stylesheets/style.css
@@ -107,3 +107,24 @@ h3 {
 .service-link img {
   margin-bottom: 7px;
 }
+
+.js-video {
+  height: 0;
+  padding-top: 25px;
+  padding-bottom: 67.5%;
+  margin-bottom: 10px;
+  position: relative;
+  overflow: hidden;
+}
+
+.js-video.widescreen {
+  padding-bottom: 57.25%;
+}
+
+.js-video embed, .js-video iframe, .js-video object, .js-video video {
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  position: absolute;
+}
diff --git a/routes/share.js b/routes/share.js
index 3b84542..38de097 100644
--- a/routes/share.js
+++ b/routes/share.js
@@ -21,37 +21,41 @@ module.exports = function(req, res) {
   var itemId = req.params.id;
   var promises = [];
 
-  if (cache[serviceId][type + "-" + itemId]) {
-    res.render(type, {page: type, items: cache[serviceId][type + "-" + itemId]});
-    return;
-  }
+  req.db.matches.findOne({item_id:serviceId + itemId}).then(function(doc) {
+    if (doc) {
+      res.render(type, {page: type, items: doc.items});
+    } else {
+      services[serviceId].lookupId(itemId, type).then(function(item) {
 
-  services[serviceId].lookupId(itemId, type).then(function(item) {
-
-    for (var id in services) {
-      if (id != serviceId) {
-        promises.push(Q.timeout(services[id].search(item), 5000));
-      }
-    }
-
-    Q.allSettled(promises).then(function(results) {
-      var items = results.map(function(result) {
-        if (result.state == "fulfilled") {
-          return result.value;
+        for (var id in services) {
+          if (id != serviceId) {
+            promises.push(Q.timeout(services[id].search(item), 5000));
+          }
         }
-      }).filter(function(result) {
-        return result || false;
-      });
 
-      items.sort(function(a, b) {
-        return !a.id || !b.id;
-      }).sort(function(a, b) {
-        return !a.streamUrl || b.streamUrl;
-      });
+        Q.allSettled(promises).then(function(results) {
+          var items = results.map(function(result) {
+            if (result.state == "fulfilled") {
+              return result.value;
+            }
+          }).filter(function(result) {
+            return result || false;
+          });
 
-      items.unshift(item);
-      cache[serviceId][type + "-" + itemId] = items;
-      res.render(type, {page: type, items: items});
-    });
+          items.sort(function(a, b) {
+            return !a.id || !b.id;
+          }).sort(function(a, b) {
+            return !a.streamUrl || b.streamUrl;
+          }).sort(function(a, b) {
+            return a.type == "video" && b.type != "video";
+          });
+
+          items.unshift(item);
+          req.db.matches.save({item_id:serviceId + itemId, items:items});
+          cache[serviceId][type + "-" + itemId] = items;
+          res.render(type, {page: type, items: items});
+        });
+      });
+    }
   });
 };
diff --git a/views/album.ejs b/views/album.ejs
index 9d276a0..cf92f06 100644
--- a/views/album.ejs
+++ b/views/album.ejs
@@ -17,10 +17,15 @@
     </div>
     <div class="row">
     <% for (var i=0;i < items.length;i++) { var album = items[i]; %>
-      <div class="col-md-3 col-xs-6">
+      <% if (album.type != "video") { %><div class="col-md-3 col-xs-6"><% } else { %><div class="col-md-6 col-xs-12"><% } %>
         <div class="service <%= i==0 ? "source-service" : "" %>">
           <div class="matching-from"><%= i==0 ? "Found matches using this link" : "" %></div>
-          <% if (album.streamUrl) { %>
+          <% if (album.type == "video") { %>
+          <div class="js-video widescreen">
+            <iframe width="100%" src="//www.youtube.com/embed/<%= album.id %>" frameborder="0" allowfullscreen></iframe>
+          </div>
+          <a href="https://www.youtube.com/results?search_query=<%= items[0].name %> <%= items[0].artist.name %>">More Youtube matches</a>
+          <% } else if (album.streamUrl) { %>
           <a href="<%= album.streamUrl %>"><img src="<%= album.artwork %>" class="img-rounded album-artwork" width="100%"></a>
           <div class="service-link">
             <a href="<%= album.streamUrl %>"><img src="/images/<%= album.service %>.png" class="img-rounded"></a>