diff --git a/controllers/playlists.js b/controllers/playlists.js index 568b56c9..ea746d0b 100644 --- a/controllers/playlists.js +++ b/controllers/playlists.js @@ -196,95 +196,145 @@ module.exports = { } }, - syncSetup: function(req, res, next) { + import: function() { - if (!req.user) return res.redirect('/login'); - if (!req.user.profiles || !req.user.profiles.spotify) return res.redirect('/auth/spotify'); - if (!req.user.profiles.spotify.token) return res.redirect('/auth/spotify'); - //if (req.user.profiles.spotify.expires < Date.now()) return res.redirect('/auth/spotify'); - - // stub for spotify API auth - var spotify = { - get: function( path ) { - return rest.get('https://api.spotify.com/v1/' + path , { - headers: { - 'Authorization': 'Bearer ' + req.user.profiles.spotify.token - } - }); - } - } - - var playlist = req.param('playlist'); + }, + syncAndImport: function(req, res, next) { + if (!req.user.profiles) req.user.profiles = {}; + var querySources = ['youtube', 'spotify']; + if (req.param('sourceName')) querySources = [ req.param('sourceName') ]; + + if (~querySources.indexOf('youtube') && (!req.user.profiles.google || !req.user.profiles.google.token)) return done('no creds'); + if (~querySources.indexOf('spotify') && (!req.user.profiles.spotify || !req.user.profiles.spotify.token)) return done('no creds'); + + var playlist = req.param('playlist'); if (playlist) { try { playlist = JSON.parse( playlist ); } catch (e) { return res.render('500'); } - - var url = 'users/' + playlist.user + '/playlists/' + playlist.id + '?limit=250'; - spotify.get( url ).on('complete', function(spotifyPlaylist , response ) { - if (!spotifyPlaylist || response.statusCode !== 200) { - req.flash('error', 'Could not retrieve list from Spotify. ' + response.statusCode ); + + switch (playlist.source) { + default: + req.flash('error', 'Unknown playlist source "'+playlist.source+'"'); return res.redirect('back'); - } - - console.log('spotifyPlaylist', spotifyPlaylist); - console.log('will be public: ', spotifyPlaylist.public); - - var tracks = spotifyPlaylist.tracks.items.map(function(x) { - return { - title: x.track.name, - artist: x.track.artists[0].name, - credits: x.track.artists.map(function(y) { - return y.name - }), - duration: x.track.duration_ms / 1000 - } - }); + break; + case 'youtube': + req.youtube.get('playlistItems?playlistId='+playlist.id+'&part=contentDetails&maxResults=50').on('complete', function(data) { + + var pullers = data.items.map(function( track ) { + return function( trackComplete ) { + req.soundtrack.trackFromSource('youtube', track.contentDetails.videoId , trackComplete ); + } + }); - var pushers = []; - tracks.forEach(function(track) { - pushers.push(function(done) { - req.soundtrack.trackFromSource('object', track , done ); - }); - }); - - async.series( pushers , function(err, tracks) { - - tracks.forEach(function(track) { - /* req.app.agency.publish('track:crawl', { - id: track._id - }, function(err) { - console.log('track crawled, doing stuff in initiator'); - }); */ + async.series( pullers , function(err, results) { + var createdPlaylist = new Playlist({ + name: playlist.name, + public: true, + _creator: req.user._id, + _owner: req.user._id, + _tracks: results.map(function(x) { return x._id; }) + }); + createdPlaylist.save(function(err) { + if (err) console.log(err); + return res.redirect('/' + req.user.slug + '/' + createdPlaylist.slug ); + }); + }); }); - - var playlist = new Playlist({ - name: spotifyPlaylist.name, - description: spotifyPlaylist.description, - public: spotifyPlaylist.public, - _creator: req.user._id, - _owner: req.user._id, - _tracks: tracks.map(function(x) { return x._id }), - remotes: { - spotify: { - id: spotifyPlaylist.id - } + break; + case 'spotify': + var url = 'users/' + playlist.user + '/playlists/' + playlist.id + '?limit=250'; + req.spotify.get( url ).on('complete', function(spotifyPlaylist , response ) { + if (!spotifyPlaylist || response.statusCode !== 200) { + req.flash('error', 'Could not retrieve list from Spotify. ' + response.statusCode ); + return res.redirect('back'); } + + var tracks = spotifyPlaylist.tracks.items.map(function(x) { + return { + title: x.track.name, + artist: x.track.artists[0].name, + credits: x.track.artists.map(function(y) { + return y.name + }), + duration: x.track.duration_ms / 1000 + } + }); + + var pushers = []; + tracks.forEach(function(track) { + pushers.push(function(done) { + req.soundtrack.trackFromSource('object', track , done ); + }); + }); + + async.series( pushers , function(err, tracks) { + + tracks.forEach(function(track) { + /* req.app.agency.publish('track:crawl', { + id: track._id + }, function(err) { + console.log('track crawled, doing stuff in initiator'); + }); */ + }); + + var playlist = new Playlist({ + name: spotifyPlaylist.name, + description: spotifyPlaylist.description, + public: spotifyPlaylist.public, + _creator: req.user._id, + _owner: req.user._id, + _tracks: tracks.map(function(x) { return x._id }), + remotes: { + spotify: { + id: spotifyPlaylist.id + } + } + }); + playlist.save(function(err) { + res.redirect('/' + req.user.slug + '/' + playlist.slug ); + }); + }); }); - playlist.save(function(err) { - res.redirect('/' + req.user.slug + '/' + playlist.slug ); - }); - }); + break; + } + return; + } + + var stack = {}; + + async.parallel({ + youtube: syncYoutube, + spotify: syncSpotify + }, function(err, results) { + if (~querySources.indexOf('youtube') && !results.youtube) return res.redirect('/auth/google?next=/sets/import'); + if (~querySources.indexOf('spotify') && !results.spotify) return res.redirect('/auth/spotify?next=/sets/import'); + + res.render('sets-import', { + youtube: results.youtube || [], + spotify: results.spotify || [], }); - } else { - spotify.get('users/' + req.user.profiles.spotify.id + '/playlists').on('complete', function(results, response) { - if (response.statusCode == 401) return res.redirect('/auth/spotify'); - res.render('sets-import', { - playlists: results.items + }); + + function syncYoutube( done ) { + req.youtube.get('playlists?part=snippet&mine=true&maxResults=50').on('complete', function(data) { + req.user.profiles.google.playlists = data.items; + req.user.save(function(err) { + done( err , data.items ); + }); + }); + } + + function syncSpotify( done ) { + req.spotify.get('users/' + req.user.profiles.spotify.id + '/playlists').on('complete', function(results, response) { + if (!results || response.statusCode == 401) return done('expired'); + req.user.profiles.spotify.playlists = results.items; + req.user.save(function(err) { + done( err , results.items ); }); }); } diff --git a/models/Person.js b/models/Person.js index 08a072d2..9b59edcb 100644 --- a/models/Person.js +++ b/models/Person.js @@ -26,7 +26,15 @@ var PersonSchema = new Schema({ username: String, token: String, updated: Date, - expires: Number + expires: Number, + playlists: [] + }, + google: { + id: String, + username: String, + token: String, + updated: Date, + playlists: [] } } , preferences: { diff --git a/package.json b/package.json index 66177569..243335f8 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "mongoose-agency": "0.0.0", "mongoose-slug": "~1.3.0", "passport": "~0.1.17", + "passport-google-oauth": "^0.2.0", "passport-local": "~0.1.6", "passport-local-mongoose": "~0.2.4", "passport-spotify": "^0.1.0", diff --git a/soundtrack.js b/soundtrack.js index a925a909..6596e958 100644 --- a/soundtrack.js +++ b/soundtrack.js @@ -426,6 +426,92 @@ var soundtracker = function(req, res, next) { next(); }; +var externalizer = function(req, res, next) { + if (!req.user) return res.redirect('/login'); + + req.youtube = { + get: function( path ) { + return rest.get('https://www.googleapis.com/youtube/v3/' + path + '&access_token=' + req.user.profiles.google.token , { + 'Authorization': 'Bearer ' + req.user.profiles.google.token + }); + } + } + + // stub for spotify API auth + req.spotify = { + get: function( path ) { + return rest.get('https://api.spotify.com/v1/' + path , { + headers: { + 'Authorization': 'Bearer ' + req.user.profiles.spotify.token + } + }); + } + } + + return next(); +} + +var redirectSetup = function(req, res, next) { + if (req.param('next')) { + req.session.next = req.param('next'); + req.session.save( next ); + } else { + return next(); + } +} +var redirectNext = function(req, res, next) { + if (req.session.next) { + var path = req.session.next; + delete req.session.next; + req.session.save(function() { + res.redirect( path ); + }); + } else { + res.redirect('/'); + } +} + +if (config.google && config.google.id && config.google.secret) { + var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; + passport.use(new GoogleStrategy({ + clientID: config.google.id, + clientSecret: config.google.secret, + //callbackURL: ((config.app.safe) ? 'https://' : 'http://') + config.app.host + '/auth/google/callback', + callbackURL: 'https://soundtrack.io/auth/google/callback', + scope: 'profile email https://www.googleapis.com/auth/youtube', + passReqToCallback: true + }, function(req, accessToken, refreshToken, profile, done) { + + Person.findOne({ $or: [ + { _id: (req.user) ? req.user._id : undefined } + , { 'profiles.google.id': profile.id } + ]}).exec(function(err, person) { + console.log('search result: ', err , person ); + + if (!person) var person = new Person({ username: profile.username }); + + person.profiles.google = { + id: profile.id, + token: accessToken, + updated: new Date(), + expires: null + } + + person.save(function(err) { + if (err) console.log('serious error', err ); + done(err, person); + }); + + }); + + })); + + app.get('/auth/google', redirectSetup , passport.authenticate('google') ); + app.get('/auth/google/callback', passport.authenticate('google') , redirectNext ); + + app.get('/sets/import', soundtracker , externalizer , playlists.syncAndImport ); + +} if (config.spotify && config.spotify.id && config.spotify.secret) { passport.use(new SpotifyStrategy({ @@ -456,12 +542,10 @@ if (config.spotify && config.spotify.id && config.spotify.secret) { }); })); - app.get('/auth/spotify', passport.authenticate('spotify') ); - app.get('/auth/spotify/callback', passport.authenticate('spotify') , function(req, res) { - res.redirect('/'); - }); + app.get('/auth/spotify', redirectSetup , passport.authenticate('spotify') ); + app.get('/auth/spotify/callback', passport.authenticate('spotify') , redirectNext ); - app.get('/sets/sync/spotify', soundtracker , playlists.syncSetup ); + app.get('/sets/sync/spotify', soundtracker , externalizer , playlists.syncAndImport ); } @@ -868,8 +952,9 @@ Room.find().exec(function(err, rooms) { return done(); } - app.rooms[ room.slug ].startMusic( errorHandler ); - + //app.rooms[ room.slug ].startMusic( errorHandler ); + app.rooms[ room.slug ].startMusic( done ); + }); }; }); diff --git a/views/sets-import.jade b/views/sets-import.jade index 09cbca15..d6fdb9dc 100644 --- a/views/sets-import.jade +++ b/views/sets-import.jade @@ -4,13 +4,27 @@ block content .well.content-well h1 Import Set - if (playlists.length) + + h2 From Spotify... + if (spotify.length) table.table.tablesorter - for playlist in playlists + for playlist in spotify tr td #{playlist.name} td #{playlist.tracks.total} tracks td - a.btn(href="/sets/sync/spotify?playlist=#{JSON.stringify({ source: 'spotify', id: playlist.id, user: playlist.owner.id })}") import » + a.btn(href="/sets/import?playlist=#{JSON.stringify({ source: 'spotify', id: playlist.id, user: playlist.owner.id })}") import » else p You don't have any playlists on Spotify. :( + + h2 From YouTube... + if (youtube.length) + table.table.tablesorter + for playlist in youtube + tr + td #{playlist.snippet.title} + //-td #{playlist.tracks.total} tracks + td + a.btn(href="/sets/import?playlist=#{JSON.stringify({ source: 'youtube', id: playlist.id, name: playlist.snippet.title })}") import » + else + p You don't have any playlists on YouTube. :( diff --git a/views/sets.jade b/views/sets.jade index 500297a3..5d7bff71 100644 --- a/views/sets.jade +++ b/views/sets.jade @@ -6,8 +6,16 @@ block content h1 .btn-group.pull-right if (user) - a.btn(href="/sets/sync/spotify") Import from Spotify » - a.btn(href="/#{user.slug}/playlists/new") Create a Set » + a.btn.btn-large(href="/#{user.slug}/playlists/new") Create New » + a.btn.btn-large(href="/sets/import") Import + a.btn.btn-large.dropdown-toggle(data-toggle="dropdown", href="#") + span.caret + ul.dropdown-menu + li + a(href="/sets/import?sourceName=youtube") From YouTube » + li + a(href="/sets/import?sourceName=spotify") From Spotify » + | Public Sets table.table.tablesorter