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>