Initial Commit
This commit is contained in:
commit
f8d3ea92b1
12 changed files with 350 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
1
Procfile
Normal file
1
Procfile
Normal file
|
@ -0,0 +1 @@
|
||||||
|
web: node ./bin/www
|
66
app.js
Normal file
66
app.js
Normal file
|
@ -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;
|
9
bin/www
Executable file
9
bin/www
Executable file
|
@ -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);
|
||||||
|
});
|
59
lib/googleplaymusic.js
Normal file
59
lib/googleplaymusic.js
Normal file
|
@ -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"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
lib/spotify.js
Normal file
49
lib/spotify.js
Normal file
|
@ -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"})
|
||||||
|
}
|
||||||
|
}
|
24
package.json
Normal file
24
package.json
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
8
public/stylesheets/style.css
Normal file
8
public/stylesheets/style.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
body {
|
||||||
|
padding: 50px;
|
||||||
|
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #00B7FF;
|
||||||
|
}
|
64
routes/index.js
Normal file
64
routes/index.js
Normal file
|
@ -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;
|
27
views/album.ejs
Normal file
27
views/album.ejs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<title>Unify Audio</title>
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
padding-top: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<img src="<%= spotifyAlbum.artwork %>" height=200 class="img-rounded" /> <%= spotifyAlbum.id %> <a href="<%= spotifyAlbum.url %>">Play on Spotify</a>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<img src="<%= googleAlbum.artwork %>" height=200 class="img-rounded" /> <%= googleAlbum.id %> <a href="<%= googleAlbum.url %>">Play on Google Music</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
views/error.ejs
Normal file
3
views/error.ejs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<h1><%= message %></h1>
|
||||||
|
<h2><%= error.status %></h2>
|
||||||
|
<pre><%= error.stack %></pre>
|
39
views/index.ejs
Normal file
39
views/index.ejs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<title>Unify Audio</title>
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
padding-top: 100px;
|
||||||
|
}
|
||||||
|
.url-search {
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row url-search">
|
||||||
|
<%= error %>
|
||||||
|
<form role="form" method="post" action="/search" class="form-inline">
|
||||||
|
<div class="form-group form-group-lg">
|
||||||
|
<input type="text" name="url" placeholder="Paste link here" class="form-control">
|
||||||
|
<input type="submit" class="btn btn-default btn-lg" value="Find Matches">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 col-sm-offset-3">
|
||||||
|
<p>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. <a href="/google/album/B3qrtsvk5s3piwyla76sk6qyxny">Here's an example</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue