From f8d3ea92b13ce39979e535dfb6c12642637630d9 Mon Sep 17 00:00:00 2001 From: Jonathan Cremin Date: Sun, 30 Nov 2014 12:21:03 +0000 Subject: [PATCH] Initial Commit --- .gitignore | 1 + Procfile | 1 + app.js | 66 ++++++++++++++++++++++++++++++++++++ bin/www | 9 +++++ lib/googleplaymusic.js | 59 ++++++++++++++++++++++++++++++++ lib/spotify.js | 49 ++++++++++++++++++++++++++ package.json | 24 +++++++++++++ public/stylesheets/style.css | 8 +++++ routes/index.js | 64 ++++++++++++++++++++++++++++++++++ views/album.ejs | 27 +++++++++++++++ views/error.ejs | 3 ++ views/index.ejs | 39 +++++++++++++++++++++ 12 files changed, 350 insertions(+) create mode 100644 .gitignore create mode 100644 Procfile create mode 100644 app.js create mode 100755 bin/www create mode 100644 lib/googleplaymusic.js create mode 100644 lib/spotify.js create mode 100644 package.json create mode 100644 public/stylesheets/style.css create mode 100644 routes/index.js create mode 100644 views/album.ejs create mode 100644 views/error.ejs create mode 100644 views/index.ejs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..a553d73 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: node ./bin/www diff --git a/app.js b/app.js new file mode 100644 index 0000000..29a849e --- /dev/null +++ b/app.js @@ -0,0 +1,66 @@ +var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var session = require('express-session'); +var cookieParser = require('cookie-parser'); +var flash = require('connect-flash'); +var bodyParser = require('body-parser'); + +var routes = require('./routes/index'); + +var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'ejs'); + +// uncomment after placing your favicon in /public +//app.use(favicon(__dirname + '/public/favicon.ico')); +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(session({ + secret: 'keyboard catz', + resave: false, + saveUninitialized: true +})); +app.use(flash()); +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', routes); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handlers + +// development error handler +// will print stacktrace +if (app.get('env') === 'development') { + app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: err + }); + }); +} + +// production error handler +// no stacktraces leaked to user +app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: {} + }); +}); + + +module.exports = app; diff --git a/bin/www b/bin/www new file mode 100755 index 0000000..84dbb73 --- /dev/null +++ b/bin/www @@ -0,0 +1,9 @@ +#!/usr/bin/env node +var debug = require('debug')('unify.audio'); +var app = require('../app'); + +app.set('port', process.env.PORT || 3000); + +var server = app.listen(app.get('port'), function() { + debug('Express server listening on port ' + server.address().port); +}); diff --git a/lib/googleplaymusic.js b/lib/googleplaymusic.js new file mode 100644 index 0000000..f4ba52c --- /dev/null +++ b/lib/googleplaymusic.js @@ -0,0 +1,59 @@ +var parse = require("url").parse; +var PlayMusic = require('playmusic'); +var pm = new PlayMusic(); + +if (!process.env.GOOGLE_EMAIL || !process.env.GOOGLE_PASSWORD) { + throw new Error("You need to set GOOGLE_EMAIL and GOOGLE_PASSWORD environment variables"); +} + +// It's probably ok to not wait for this to finish +pm.init({email: process.env.GOOGLE_EMAIL, password: process.env.GOOGLE_PASSWORD}, function() {}); + +module.exports.lookupId = function(id, type, next) { + pm.getAlbum(id, true, function(album) { + next({ + id: album.albumId, + name: album.name, + url: "https://play.google.com/music/listen#/album/" + album.albumId, + artwork: album.albumArtRef.replace("http:", ""), + artist: { + name: album.artist + }, + type: "album" + }); + }); +} + +module.exports.search = function(query, type, next) { + pm.search(query, 5, function(data) { // max 5 results + var result = data.entries.filter(function(result) { + return result.album; + }).sort(function(a, b) { // sort by match score + return a.score < b.score; + }).shift(); + + module.exports.lookupId(result.album.albumId, "album", next); + }); +} + +module.exports.parseUrl = function(url, next) { + // https://play.google.com/music/listen#/album/B3lxthejqxjxja2bhzchcw5qaci + // https://play.google.com/music/listen#/album//Underworld/Everything%2C+Everything+(Live) + var parsed = parse(url.replace(/\+/g, "%20")); + var hash = parsed.hash; + + var matches = hash.match(/\/album[\/]+([^\/]+)\/([^\/]+)/); + + if (matches && matches[2]) { + var artist = decodeURIComponent(matches[1]); + var album = decodeURIComponent(matches[2]); + module.exports.search(artist + " " + album, "album", function(googleAlbum) { + next(googleAlbum); + }) + } else { + var matches = hash.match(/\/album[\/]+([\w]+)/); + if (matches && matches[1]) { + return next({id:matches[1], type: "album"}) + } + } +} diff --git a/lib/spotify.js b/lib/spotify.js new file mode 100644 index 0000000..31dd387 --- /dev/null +++ b/lib/spotify.js @@ -0,0 +1,49 @@ +var parse = require('url').parse; +var spotify = require('spotify'); + +module.exports.lookupId = function(id, type, next) { + spotify.lookup({id: id, type: type}, function(err, data) { + if ( err ) { + console.log('Error occurred: ' + err); + return; + } + + var artist = data.artists[0]; + + next({ + id: data.id, + name: data.name, + url: "https://play.spotify.com/album/" + data.id, + artwork: data.images[0].url.replace("http:", ""), + artist: { + name: artist.name + } + }) + }); +} + +module.exports.search = function(query, type, next) { + spotify.search({query: query, type: type}, function(err, data) { + if ( err ) { + console.log('Error occurred: ' + err); + return; + } + + album = data.albums.items[0]; + + module.exports.lookupId(album.id, "album", next); + }); +} + +module.exports.parseUrl = function(url, next) { + // https://play.spotify.com/album/3W3ENDBQMJ9bD2qmxWI2f0 + // https://play.spotify.com/track/3W3ENDBQMJ9bD2qmxWI2f0 + // https://open.spotify.com/album/3W3ENDBQMJ9bD2qmxWI2f0 + // https://open.spotify.com/track/3W3ENDBQMJ9bD2qmxWI2f0 + + var matches = parse(url).path.match(/\/album[\/]+([^\/]+)/); + + if (matches && matches[1]) { + return next({id:matches[1], type: "album"}) + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fcbd6b1 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "unify.audio", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "engines": { + "node": "0.10.x" + }, + "dependencies": { + "body-parser": "~1.8.1", + "connect-flash": "^0.1.1", + "cookie-parser": "~1.3.3", + "debug": "~2.0.0", + "ejs": "~0.8.5", + "express": "~4.9.0", + "express-session": "^1.9.2", + "morgan": "~1.3.0", + "playmusic": "^1.1.0", + "serve-favicon": "~2.1.3", + "spotify": "^0.3.0" + } +} diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100644 index 0000000..30e047d --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,8 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} \ No newline at end of file diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..b4294c0 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,64 @@ +var express = require('express'); +var router = express.Router(); + +var googleplaymusic = require('../lib/googleplaymusic'); +var spotify = require('../lib/spotify'); + +//https://play.spotify.com/album/3W3ENDBQMJ9bD2qmxWI2f0 +//https://play.google.com/music/listen#/album/B3lxthejqxjxja2bhzchcw5qaci + +router.get('/:service/:type/:id', function(req, res) { + var service = req.params.service; + var type = req.params.type; + var id = req.params.id; + + switch(service) { + case "spotify": + spotify.lookupId(id, type, function(spotifyAlbum) { + googleplaymusic.search(spotifyAlbum.artist.name + " " + spotifyAlbum.name, "album", function(googleAlbum) { + res.render('album', {googleAlbum: googleAlbum, spotifyAlbum: spotifyAlbum}); + }); + }); + break; + case "google": + googleplaymusic.lookupId(id, type, function(googleAlbum) { + spotify.search(googleAlbum.artist.name + " " + googleAlbum.name, "album", function(spotifyAlbum) { + res.render('album', {googleAlbum: googleAlbum, spotifyAlbum: spotifyAlbum}); + }); + }); + break; + } +}); + +router.post('/search', function(req, res) { + // determine spotify or google music + var url = req.body.url; + + if (url.match(/spotify\.com/)) { + spotify.parseUrl(url, function(result) { + if (!result.id) { + req.flash('search-error', 'No match found for this link'); + res.redirect('/'); + } + res.redirect("/spotify/" + result.type + "/" + result.id); + }); + } else if (url.match(/play\.google\.com/)) { + googleplaymusic.parseUrl(url, function(result) { + if (!result.id) { + req.flash('search-error', 'No match found for this link'); + res.redirect('/'); + } + res.redirect("/google/" + result.type + "/" + result.id); + }); + } else { + req.flash('search-error', 'No match found for this link'); + res.redirect('/'); + } +}); + +/* GET home page. */ +router.get('/', function(req, res) { + res.render('index', { error: req.flash('search-error') }); +}); + +module.exports = router; diff --git a/views/album.ejs b/views/album.ejs new file mode 100644 index 0000000..99793ef --- /dev/null +++ b/views/album.ejs @@ -0,0 +1,27 @@ + + + + + + Unify Audio + + + + + + + +
+
+ <%= spotifyAlbum.id %> Play on Spotify +
+
+ <%= googleAlbum.id %> Play on Google Music +
+
+ + diff --git a/views/error.ejs b/views/error.ejs new file mode 100644 index 0000000..7cf94ed --- /dev/null +++ b/views/error.ejs @@ -0,0 +1,3 @@ +

<%= message %>

+

<%= error.status %>

+
<%= error.stack %>
diff --git a/views/index.ejs b/views/index.ejs new file mode 100644 index 0000000..217d9f9 --- /dev/null +++ b/views/index.ejs @@ -0,0 +1,39 @@ + + + + + + Unify Audio + + + + + + + +
+ +
+
+

Make sharing music from subscription services better. Give us one link and we'll match it with other services and give you back a link with all of them. Here's an example.

+
+
+
+ +