Jelajahi Sumber

Added playlist importing for private playlists.

KrisVos130 8 tahun lalu
induk
melakukan
c199ac58fb

+ 14 - 5
backend/logic/actions/playlists.js

@@ -111,6 +111,7 @@ let lib = {
 	}),
 
 	addSongToPlaylist: hooks.loginRequired((session, songId, playlistId, cb, userId) => {
+		console.log(songId);
 		async.waterfall([
 			(next) => {
 				playlists.getPlaylist(playlistId, (err, playlist) => {
@@ -163,7 +164,7 @@ let lib = {
 		});
 	}),
 	
-	/*addSetToPlaylist: hooks.loginRequired((session, url, playlistId, cb, userId) => {
+	addSetToPlaylist: hooks.loginRequired((session, url, playlistId, cb, userId) => {
 		async.waterfall([
 			(next) => {
 				utils.getPlaylistFromYouTube(url, songs => {
@@ -171,10 +172,18 @@ let lib = {
 				});
 			},
 			(songs, next) => {
+				let processed = 0;
+				function checkDone() {
+					if (processed === songs.length) {
+						next();
+					}
+				}
 				for (let s = 0; s < songs.length; s++) {
-					lib.addSongToPlaylist(session, songs[s].contentDetails.videoId, playlistId, (res) => {})();
+					lib.addSongToPlaylist(session, songs[s].contentDetails.videoId, playlistId, () => {
+						processed++;
+						checkDone();
+					});
 				}
-				next(null);
 			},
 			(next) => {
 				playlists.getPlaylist(playlistId, (err, playlist) => {
@@ -186,9 +195,9 @@ let lib = {
 		],
 		(err, playlist) => {
 			if (err) return cb({ status: 'failure', message: err });
-			else if (playlist.songs) return cb({ status: 'success', message: 'Playlist has been successfully added', data: playlist.songs });
+			else if (playlist.songs) return cb({ status: 'success', message: 'Playlist has been successfully imported.', data: playlist.songs });
 		});
-	}),*/
+	}),
 
 
 	removeSongFromPlaylist: hooks.loginRequired((session, songId, playlistId, cb, userId) => {

+ 80 - 50
backend/logic/utils.js

@@ -93,6 +93,10 @@ function convertTime (duration) {
 	return (hours < 10 ? ("0" + hours + ":") : (hours + ":")) + (minutes < 10 ? ("0" + minutes + ":") : (minutes + ":")) + (seconds < 10 ? ("0" + seconds) : seconds);
 }
 
+let youtubeRequestCallbacks = [];
+let youtubeRequestsPending = 0;
+let youtubeRequestsActive = false;
+
 module.exports = {
 	htmlEntities: str => String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'),
 	generateRandomString: function(len) {
@@ -233,48 +237,63 @@ module.exports = {
 		return roomSockets;
 	},
 	getSongFromYouTube: (songId, cb) => {
-		const youtubeParams = [
-			'part=snippet,contentDetails,statistics,status',
-			`id=${encodeURIComponent(songId)}`,
-			`key=${config.get('apis.youtube.key')}`
-		].join('&');
 
-		request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
+		youtubeRequestCallbacks.push({cb: (test) => {
+			youtubeRequestsActive = true;
+			const youtubeParams = [
+				'part=snippet,contentDetails,statistics,status',
+				`id=${encodeURIComponent(songId)}`,
+				`key=${config.get('apis.youtube.key')}`
+			].join('&');
 
-			if (err) {
-				console.error(err);
-				return next('Failed to find song from YouTube');
-			}
+			request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
 
-			body = JSON.parse(body);
+				youtubeRequestCallbacks.splice(0, 1);
+				if (youtubeRequestCallbacks.length > 0) {
+					youtubeRequestCallbacks[0].cb(youtubeRequestCallbacks[0].songId);
+				} else {
+					youtubeRequestsActive = false;
+				}
 
-			//TODO Clean up duration converter
-			let dur = body.items[0].contentDetails.duration;
-			dur = dur.replace('PT', '');
-			let duration = 0;
-			dur = dur.replace(/([\d]*)H/, (v, v2) => {
-				v2 = Number(v2);
-				duration = (v2 * 60 * 60);
-				return '';
-			});
-			dur = dur.replace(/([\d]*)M/, (v, v2) => {
-				v2 = Number(v2);
-				duration += (v2 * 60);
-				return '';
-			});
-			dur = dur.replace(/([\d]*)S/, (v, v2) => {
-				v2 = Number(v2);
-				duration += v2;
-				return '';
+				if (err) {
+					console.error(err);
+					return null;
+				}
+
+				body = JSON.parse(body);
+
+				//TODO Clean up duration converter
+				let dur = body.items[0].contentDetails.duration;
+				dur = dur.replace('PT', '');
+				let duration = 0;
+				dur = dur.replace(/([\d]*)H/, (v, v2) => {
+					v2 = Number(v2);
+					duration = (v2 * 60 * 60);
+					return '';
+				});
+				dur = dur.replace(/([\d]*)M/, (v, v2) => {
+					v2 = Number(v2);
+					duration += (v2 * 60);
+					return '';
+				});
+				dur = dur.replace(/([\d]*)S/, (v, v2) => {
+					v2 = Number(v2);
+					duration += v2;
+					return '';
+				});
+
+				let song = {
+					_id: body.items[0].id,
+					title: body.items[0].snippet.title,
+					duration
+				};
+				cb(song);
 			});
+		}, songId});
 
-			let song = {
-				_id: body.items[0].id,
-				title: body.items[0].snippet.title,
-				duration
-			};
-			cb(song);
-		});
+		if (!youtubeRequestsActive) {
+			youtubeRequestCallbacks[0].cb(youtubeRequestCallbacks[0].songId);
+		}
 	},
 	getPlaylistFromYouTube: (url, cb) => {
 		
@@ -282,22 +301,33 @@ module.exports = {
 		var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
 		let playlistId = regex.exec(url)[1];
 
-		const youtubeParams = [
-			'part=contentDetails',
-			`playlistId=${encodeURIComponent(playlistId)}`,
-			`maxResults=50`,
-			`key=${config.get('apis.youtube.key')}`
-		].join('&');
+		function getPage(pageToken, songs) {
+			let nextPageToken = (pageToken) ? `pageToken=${pageToken}` : '';
+			const youtubeParams = [
+				'part=contentDetails',
+				`playlistId=${encodeURIComponent(playlistId)}`,
+				`maxResults=5`,
+				`key=${config.get('apis.youtube.key')}`,
+				nextPageToken
+			].join('&');
 
-		request(`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`, (err, res, body) => {
-			if (err) {
-				console.error(err);
-				return next('Failed to find playlist from YouTube');
-			}
+			request(`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`, (err, res, body) => {
+				if (err) {
+					console.error(err);
+					return next('Failed to find playlist from YouTube');
+				}
+				body = JSON.parse(body);
 
-			body = JSON.parse(body);
-			cb(body.items);
-		});
+				songs = songs.concat(body.items);
+				if (body.nextPageToken) {
+					getPage(body.nextPageToken, songs);
+				} else {
+					console.log(songs);
+					cb(songs);
+				}
+			});
+		}
+		getPage(null, []);
 	},
 	getSongFromSpotify: (song, cb) => {
 		const spotifyParams = [

+ 18 - 17
frontend/components/Modals/Playlists/Edit.vue

@@ -49,14 +49,14 @@
 						</tr>
 					</tbody>
 				</table>
-				<!--div class='control is-grouped'>
+				<div class='control is-grouped'>
 					<p class='control is-expanded'>
 						<input class='input' type='text' placeholder='YouTube Playlist URL' v-model='importQuery'>
 					</p>
 					<p class='control'>
 						<a class='button is-info' @click='importPlaylist()'>Import</a>
 					</p>
-				</div-->
+				</div>
 				<h5>Edit playlist details:</h5>
 				<div class='control is-grouped'>
 					<p class='control is-expanded'>
@@ -82,7 +82,9 @@
 		data() {
 			return {
 				playlist: {},
-				songQueryResults: []
+				songQueryResults: [],
+				songQuery: '',
+				importQuery: ''
 			}
 		},
 		methods: {
@@ -99,40 +101,39 @@
 								thumbnail: res.data.items[i].snippet.thumbnails.default.url
 							});
 						}
-					} else if (res.status == 'error') Toast.methods.addToast(res.message, 3000);
+						Toast.methods.addToast(res.message, 3000);
+					} else if (res.status === 'error') Toast.methods.addToast(res.message, 3000);
 				});
 			},
 			addSongToPlaylist: function (id) {
 				let _this = this;
 				_this.socket.emit('playlists.addSongToPlaylist', id, _this.playlist._id, res => {
-					if (res.status == 'success') {
-						Toast.methods.addToast(res.message, 3000);
-					}
+					Toast.methods.addToast(res.message, 4000);
 				});
 			},
-			/*importPlaylist: function () {
+			importPlaylist: function () {
 				let _this = this;
+				Toast.methods.addToast('Starting to import your playlist. This can take some time to do.', 4000);
 				this.socket.emit('playlists.addSetToPlaylist', _this.importQuery, _this.playlist._id, res => {
-					if (res.status == 'success') _this.playlist.songs = res.data;
+					if (res.status === 'success') _this.playlist.songs = res.data;
+					Toast.methods.addToast(res.message, 4000);
 				});
-			},*/
+			},
 			removeSongFromPlaylist: function (id) {
 				let _this = this;
 				this.socket.emit('playlists.removeSongFromPlaylist', id, _this.playlist._id, res => {
-					if (res.status == 'success') {
-						Toast.methods.addToast(res.message, 3000);
-					}
+					Toast.methods.addToast(res.message, 4000);
 				});
 			},
 			renamePlaylist: function () {
 				this.socket.emit('playlists.updateDisplayName', this.playlist._id, this.playlist.displayName, res => {
-					if (res.status == 'success') Toast.methods.addToast(res.message, 3000);
+					Toast.methods.addToast(res.message, 4000);
 				});
 			},
 			removePlaylist: function () {
 				let _this = this;
 				_this.socket.emit('playlists.remove', _this.playlist._id, res => {
-					if (res.status == 'success') {
+					if (res.status === 'success') {
 						Toast.methods.addToast(res.message, 3000);
 						_this.$parent.toggleModal('editPlaylist');
 					}
@@ -141,13 +142,13 @@
 			promoteSong: function (fromIndex) {
 				let _this = this;
 				_this.socket.emit('playlists.promoteSong', _this.playlist._id, fromIndex, res => {
-					if (res.status == 'success') _this.$set('playlist.songs', res.data); // bug: v-for is not updating
+					if (res.status === 'success') _this.$set('playlist.songs', res.data); // bug: v-for is not updating
 				});
 			},
 			demoteSong: function (fromIndex) {
 				let _this = this;
 				_this.socket.emit('playlists.demoteSong', _this.playlist._id, fromIndex, res => {
-					if (res.status == 'success') _this.$set('playlist.songs', res.data); // bug: v-for is not updating
+					if (res.status === 'success') _this.$set('playlist.songs', res.data); // bug: v-for is not updating
 				});
 			}*/
 		},