Browse Source

Moved utils YouTUbe functions to its own YouTube module

Kristian Vos 4 years ago
parent
commit
0c4577d7b0

+ 23 - 15
backend/index.js

@@ -381,20 +381,21 @@ class ModuleManager {
 
 const moduleManager = new ModuleManager();
 
-moduleManager.addModule("cache");
-moduleManager.addModule("db");
-moduleManager.addModule("mail");
-moduleManager.addModule("activities");
-moduleManager.addModule("api");
-moduleManager.addModule("app");
-moduleManager.addModule("io");
-moduleManager.addModule("notifications");
-moduleManager.addModule("playlists");
-moduleManager.addModule("punishments");
-moduleManager.addModule("songs");
-moduleManager.addModule("stations");
-moduleManager.addModule("tasks");
-moduleManager.addModule("utils");
+// moduleManager.addModule("cache");
+// moduleManager.addModule("db");
+// moduleManager.addModule("mail");
+// moduleManager.addModule("activities");
+// moduleManager.addModule("api");
+// moduleManager.addModule("app");
+// moduleManager.addModule("io");
+// moduleManager.addModule("notifications");
+// moduleManager.addModule("playlists");
+// moduleManager.addModule("punishments");
+// moduleManager.addModule("songs");
+// moduleManager.addModule("stations");
+// moduleManager.addModule("tasks");
+// moduleManager.addModule("utils");
+moduleManager.addModule("youtube");
 
 moduleManager.initialize();
 
@@ -441,7 +442,14 @@ process.stdin.on("data", data => {
 		console.log(moduleManager.modules[parts[1]].jobStatistics);
 	}
 	if (command.startsWith("debug")) {
-		moduleManager.modules.utils.runJob("DEBUG");
+		moduleManager.modules.youtube
+			.runJob("GET_PLAYLIST", { url: "https://www.youtube.com/playlist?list=PLN-cFDG8y28Pz4dkAFwDNH0as0-prFfvR" })
+			.then(response => {
+				console.log(1111, response);
+			})
+			.catch(err => {
+				console.log(1112, err);
+			});
 	}
 
 	if (command.startsWith("eval")) {

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

@@ -11,6 +11,7 @@ import cache from "../cache";
 import moduleManager from "../../index";
 
 import playlists from "../playlists";
+import YouTubeModule from "../youtube";
 import activities from "../activities";
 
 cache.runJob("SUB", {
@@ -517,8 +518,7 @@ const lib = {
 							});
 						})
 						.catch(() => {
-							utils
-								.runJob("GET_SONG_FROM_YOUTUBE", { songId })
+							YouTubeModule.runJob("GET_SONG", { songId })
 								.then(response => next(null, response.song))
 								.catch(next);
 						});
@@ -597,20 +597,18 @@ const lib = {
 		async.waterfall(
 			[
 				next => {
-					utils
-						.runJob("GET_PLAYLIST_FROM_YOUTUBE", {
-							url,
-							musicOnly
-						})
-						.then(response => {
-							if (response.filteredSongs) {
-								videosInPlaylistTotal = response.songs.length;
-								songsInPlaylistTotal = response.filteredSongs.length;
-							} else {
-								songsInPlaylistTotal = videosInPlaylistTotal = response.songs.length;
-							}
-							next(null, response.songs);
-						});
+					YouTubeModule.runJob("GET_PLAYLIST", {
+						url,
+						musicOnly
+					}).then(response => {
+						if (response.filteredSongs) {
+							videosInPlaylistTotal = response.songs.length;
+							songsInPlaylistTotal = response.filteredSongs.length;
+						} else {
+							songsInPlaylistTotal = videosInPlaylistTotal = response.songs.length;
+						}
+						next(null, response.songs);
+					});
 				},
 				(songIds, next) => {
 					let processed = 0;

+ 6 - 7
backend/logic/actions/queueSongs.js

@@ -7,6 +7,7 @@ import { isAdminRequired, isLoginRequired } from "./hooks";
 import db from "../db";
 
 import utils from "../utils";
+import YouTubeModule from "../youtube";
 
 import cache from "../cache";
 // const logger = moduleManager.modules["logger"];
@@ -245,8 +246,7 @@ const lib = {
 				(song, next) => {
 					if (song) return next("This song has already been added.");
 					// TODO Add err object as first param of callback
-					return utils
-						.runJob("GET_SONG_FROM_YOUTUBE", { songId })
+					return YouTubeModule.runJob("GET_SONG", { songId })
 						.then(response => {
 							const { song } = response;
 							song.duration = -1;
@@ -320,11 +320,10 @@ const lib = {
 		async.waterfall(
 			[
 				next => {
-					utils
-						.runJob("GET_PLAYLIST_FROM_YOUTUBE", {
-							url,
-							musicOnly
-						})
+					YouTubeModule.runJob("GET_PLAYLIST", {
+						url,
+						musicOnly
+					})
 						.then(res => {
 							next(null, res.songs);
 						})

+ 2 - 2
backend/logic/actions/stations.js

@@ -9,6 +9,7 @@ import cache from "../cache";
 import notifications from "../notifications";
 import stations from "../stations";
 import activities from "../activities";
+import YouTubeModule from "../youtube";
 
 // const logger = moduleManager.modules["logger"];
 
@@ -1714,8 +1715,7 @@ export default {
 						.then(res => {
 							if (res.song) return next(null, res.song, station);
 
-							return utils
-								.runJob("GET_SONG_FROM_YOUTUBE", { songId })
+							return YouTubeModule.runJob("GET_SONG", { songId })
 								.then(response => {
 									const { song } = response;
 									song.artists = [];

+ 0 - 229
backend/logic/utils.js

@@ -1,8 +1,5 @@
-import config from "config";
-
 import async from "async";
 import crypto from "crypto";
-import request from "request";
 import CoreClass from "../core";
 
 let UtilsModule;
@@ -591,232 +588,6 @@ class _UtilsModule extends CoreClass {
 		});
 	}
 
-	/**
-	 * Gets the details of a song using the YouTube API
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {string} payload.songId - the YouTube API id of the song
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	GET_SONG_FROM_YOUTUBE(payload) {
-		// songId, cb
-		return new Promise((resolve, reject) => {
-			UtilsModule.youtubeRequestCallbacks.push({
-				cb: () => {
-					UtilsModule.youtubeRequestsActive = true;
-					const youtubeParams = [
-						"part=snippet,contentDetails,statistics,status",
-						`id=${encodeURIComponent(payload.songId)}`,
-						`key=${config.get("apis.youtube.key")}`
-					].join("&");
-
-					request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
-						UtilsModule.youtubeRequestCallbacks.splice(0, 1);
-						if (UtilsModule.youtubeRequestCallbacks.length > 0) {
-							UtilsModule.youtubeRequestCallbacks[0].cb(UtilsModule.youtubeRequestCallbacks[0].songId);
-						} else UtilsModule.youtubeRequestsActive = false;
-
-						if (err) {
-							console.error(err);
-							return null;
-						}
-
-						body = JSON.parse(body);
-
-						if (body.error) {
-							console.log("ERROR", "GET_SONG_FROM_YOUTUBE", `${body.error.message}`);
-							return reject(new Error("An error has occured. Please try again later."));
-						}
-
-						if (body.items[0] === undefined)
-							return reject(
-								new Error("The specified video does not exist or cannot be publicly accessed.")
-							);
-
-						// 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 "";
-						});
-
-						// eslint-disable-next-line no-unused-vars
-						dur = dur.replace(/([\d]*)S/, (v, v2) => {
-							v2 = Number(v2);
-							duration += v2;
-							return "";
-						});
-
-						const song = {
-							songId: body.items[0].id,
-							title: body.items[0].snippet.title,
-							duration
-						};
-
-						return resolve({ song });
-					});
-				},
-				songId: payload.songId
-			});
-
-			if (!UtilsModule.youtubeRequestsActive) {
-				UtilsModule.youtubeRequestCallbacks[0].cb(UtilsModule.youtubeRequestCallbacks[0].songId);
-			}
-		});
-	}
-
-	/**
-	 * Filters a list of YouTube videos so that they only contains videos with music
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {Array} payload.videoIds - an array of YouTube videoIds to filter through
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	FILTER_MUSIC_VIDEOS_YOUTUBE(payload) {
-		// videoIds, cb
-		return new Promise((resolve, reject) => {
-			/**
-			 * @param {Function} cb2 - callback
-			 */
-			function getNextPage(cb2) {
-				const localVideoIds = payload.videoIds.splice(0, 50);
-
-				const youtubeParams = [
-					"part=topicDetails",
-					`id=${encodeURIComponent(localVideoIds.join(","))}`,
-					`maxResults=50`,
-					`key=${config.get("apis.youtube.key")}`
-				].join("&");
-
-				request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
-					if (err) {
-						console.error(err);
-						return reject(new Error("Failed to find playlist from YouTube"));
-					}
-
-					body = JSON.parse(body);
-
-					if (body.error) {
-						console.log("ERROR", "FILTER_MUSIC_VIDEOS_YOUTUBE", `${body.error.message}`);
-						return reject(new Error("An error has occured. Please try again later."));
-					}
-
-					const songIds = [];
-					body.items.forEach(item => {
-						const songId = item.id;
-						if (!item.topicDetails) return;
-						if (item.topicDetails.relevantTopicIds.indexOf("/m/04rlf") !== -1) {
-							songIds.push(songId);
-						}
-					});
-
-					if (payload.videoIds.length > 0) {
-						return getNextPage(newSongIds => {
-							cb2(songIds.concat(newSongIds));
-						});
-					}
-
-					return cb2(songIds);
-				});
-			}
-
-			if (payload.videoIds.length === 0) resolve({ songIds: [] });
-			else getNextPage(songIds => resolve({ songIds }));
-		});
-	}
-
-	/**
-	 * Returns an array of songs taken from a YouTube playlist
-	 *
-	 * @param {object} payload - object that contains the payload
-	 * @param {boolean} payload.musicOnly - whether to return music videos or all videos in the playlist
-	 * @param {string} payload.url - the url of the YouTube playlist
-	 * @returns {Promise} - returns promise (reject, resolve)
-	 */
-	GET_PLAYLIST_FROM_YOUTUBE(payload) {
-		// payload includes: url, musicOnly
-		return new Promise((resolve, reject) => {
-			const local = this;
-
-			const name = "list".replace(/[\\[]/, "\\[").replace(/[\]]/, "\\]");
-
-			const regex = new RegExp(`[\\?&]${name}=([^&#]*)`);
-			const splitQuery = regex.exec(payload.url);
-
-			if (!splitQuery) {
-				console.log("ERROR", "GET_PLAYLIST_FROM_YOUTUBE", "Invalid YouTube playlist URL query.");
-				return reject(new Error("An error has occured. Please try again later."));
-			}
-
-			const playlistId = splitQuery[1];
-
-			/**
-			 * @param {string} pageToken - page token for YouTube API
-			 * @param {Array} songs - array of songs
-			 */
-			function getPage(pageToken, songs) {
-				const nextPageToken = pageToken ? `pageToken=${pageToken}` : "";
-				const youtubeParams = [
-					"part=contentDetails",
-					`playlistId=${encodeURIComponent(playlistId)}`,
-					`maxResults=50`,
-					`key=${config.get("apis.youtube.key")}`,
-					nextPageToken
-				].join("&");
-
-				request(
-					`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`,
-					async (err, res, body) => {
-						if (err) {
-							console.error(err);
-							return reject(new Error("Failed to find playlist from YouTube"));
-						}
-
-						body = JSON.parse(body);
-
-						if (body.error) {
-							console.log("ERROR", "GET_PLAYLIST_FROM_YOUTUBE", `${body.error.message}`);
-							return reject(new Error("An error has occured. Please try again later."));
-						}
-
-						songs = songs.concat(body.items);
-
-						if (body.nextPageToken) return getPage(body.nextPageToken, songs);
-
-						songs = songs.map(song => song.contentDetails.videoId);
-
-						if (!payload.musicOnly) return resolve({ songs });
-						return local
-							.runJob(
-								"FILTER_MUSIC_VIDEOS_YOUTUBE",
-								{
-									videoIds: songs.slice()
-								},
-								this
-							)
-							.then(filteredSongs => {
-								resolve({ filteredSongs, songs });
-							});
-					}
-				);
-			}
-
-			return getPage(null, []);
-		});
-	}
-
 	/**
 	 * Shuffles an array of songs
 	 *

+ 285 - 0
backend/logic/youtube.js

@@ -0,0 +1,285 @@
+import async from "async";
+import config from "config";
+
+import request from "request";
+
+import CoreClass from "../core";
+
+let YouTubeModule;
+
+class _YouTubeModule extends CoreClass {
+	// eslint-disable-next-line require-jsdoc
+	constructor() {
+		super("youtube");
+
+		YouTubeModule = this;
+	}
+
+	/**
+	 * Initialises the activities module
+	 *
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	initialize() {
+		return new Promise(resolve => {
+			resolve();
+		});
+	}
+
+	/**
+	 * Gets the details of a song using the YouTube API
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {string} payload.songId - the YouTube API id of the song
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	GET_SONG(payload) {
+		// songId, cb
+		return new Promise((resolve, reject) => {
+			const youtubeParams = [
+				"part=snippet,contentDetails,statistics,status",
+				`id=${encodeURIComponent(payload.songId)}`,
+				`key=${config.get("apis.youtube.key")}`
+			].join("&");
+
+			request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
+				if (err) {
+					YouTubeModule.log("ERROR", "GET_SONG", `${err.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				body = JSON.parse(body);
+
+				if (body.error) {
+					YouTubeModule.log("ERROR", "GET_SONG", `${body.error.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				if (body.items[0] === undefined)
+					return reject(new Error("The specified video does not exist or cannot be publicly accessed."));
+
+				// 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 "";
+				});
+
+				// eslint-disable-next-line no-unused-vars
+				dur = dur.replace(/([\d]*)S/, (v, v2) => {
+					v2 = Number(v2);
+					duration += v2;
+					return "";
+				});
+
+				const song = {
+					songId: body.items[0].id,
+					title: body.items[0].snippet.title,
+					duration
+				};
+
+				return resolve({ song });
+			});
+			// songId: payload.songId
+		});
+	}
+
+	/**
+	 * Returns an array of songs taken from a YouTube playlist
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {boolean} payload.musicOnly - whether to return music videos or all videos in the playlist
+	 * @param {string} payload.url - the url of the YouTube playlist
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	GET_PLAYLIST(payload) {
+		// payload includes: url, musicOnly
+		return new Promise((resolve, reject) => {
+			const name = "list".replace(/[\\[]/, "\\[").replace(/[\]]/, "\\]");
+
+			const regex = new RegExp(`[\\?&]${name}=([^&#]*)`);
+			const splitQuery = regex.exec(payload.url);
+
+			if (!splitQuery) {
+				YouTubeModule.log("ERROR", "GET_PLAYLIST", "Invalid YouTube playlist URL query.");
+				return reject(new Error("Invalid playlist URL."));
+			}
+			const playlistId = splitQuery[1];
+
+			return async.waterfall(
+				[
+					next => {
+						let songs = [];
+						let nextPageToken = "";
+
+						async.whilst(
+							next => {
+								YouTubeModule.log(
+									"INFO",
+									`Getting playlist progress for job (${this.toString()}): ${
+										songs.length
+									} songs gotten so far. Is there a next page: ${nextPageToken !== undefined}.`
+								);
+								next(null, nextPageToken !== undefined);
+							},
+							next => {
+								YouTubeModule.runJob("GET_PLAYLIST_PAGE", { playlistId, nextPageToken }, this)
+									// eslint-disable-next-line no-loop-func
+									.then(response => {
+										songs = songs.concat(response.songs);
+										nextPageToken = response.nextPageToken;
+										next();
+									})
+									// eslint-disable-next-line no-loop-func
+									.catch(err => {
+										next(err);
+									});
+							},
+							err => {
+								next(err, songs);
+							}
+						);
+					},
+
+					(songs, next) => {
+						if (!payload.musicOnly) return next(true, { songs });
+						return YouTubeModule.runJob(
+							"FILTER_MUSIC_VIDEOS_YOUTUBE",
+							{
+								videoIds: songs.slice()
+							},
+							this
+						)
+							.then(filteredSongs => {
+								next(null, { filteredSongs, songs });
+							})
+							.catch(next);
+					}
+				],
+				(err, response) => {
+					if (err && err !== true) {
+						YouTubeModule.log("ERROR", "GET_PLAYLIST", "Some error has occurred.", err.message);
+						reject(new Error("Some error has occurred."));
+					} else {
+						resolve(response);
+					}
+				}
+			);
+		});
+	}
+
+	/**
+	 * Returns a a page from a YouTube playlist. Is used internally by GET_PLAYLIST.
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {boolean} payload.playlistId - the playlist id to get videos from
+	 * @param {boolean} payload.nextPageToken - the nextPageToken to use
+	 * @param {string} payload.url - the url of the YouTube playlist
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	GET_PLAYLIST_PAGE(payload) {
+		// payload includes: playlistId, nextPageToken
+		return new Promise((resolve, reject) => {
+			const nextPageToken = payload.nextPageToken ? `pageToken=${payload.nextPageToken}` : "";
+			const videosPerPage = 50;
+			const youtubeParams = [
+				"part=contentDetails",
+				`playlistId=${encodeURIComponent(payload.playlistId)}`,
+				`maxResults=${videosPerPage}`,
+				`key=${config.get("apis.youtube.key")}`,
+				nextPageToken
+			].join("&");
+
+			request(`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`, async (err, res, body) => {
+				if (err) {
+					YouTubeModule.log("ERROR", "GET_PLAYLIST_PAGE", `${err.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				body = JSON.parse(body);
+
+				if (body.error) {
+					YouTubeModule.log("ERROR", "GET_PLAYLIST_PAGE", `${body.error.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				const songs = body.items;
+
+				if (body.nextPageToken) return resolve({ nextPageToken: body.nextPageToken, songs });
+				return resolve({ songs });
+			});
+		});
+	}
+
+	/**
+	 * Filters a list of YouTube videos so that they only contains videos with music. Is used internally by GET_PLAYLIST
+	 *
+	 * @param {object} payload - object that contains the payload
+	 * @param {Array} payload.videoIds - an array of YouTube videoIds to filter through
+	 * @param {Array} payload.page - the current page/set of video's to get, starting at 0. If left null, 0 is assumed. Will recurse.
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	FILTER_MUSIC_VIDEOS(payload) {
+		return new Promise((resolve, reject) => {
+			const page = payload.page ? payload.page : 0;
+			const videosPerPage = 50; // 50 is the max I believe
+			const localVideoIds = payload.videoIds.splice(page * 50, videosPerPage);
+
+			if (localVideoIds.length === 0) {
+				return resolve({ videoIds: [] });
+			}
+
+			const youtubeParams = [
+				"part=topicDetails",
+				`id=${encodeURIComponent(localVideoIds.join(","))}`,
+				`maxResults=${videosPerPage}`,
+				`key=${config.get("apis.youtube.key")}`
+			].join("&");
+
+			return request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
+				if (err) {
+					YouTubeModule.log("ERROR", "FILTER_MUSIC_VIDEOS", `${err.message}`);
+					return reject(new Error("Failed to find playlist from YouTube"));
+				}
+
+				body = JSON.parse(body);
+
+				if (body.error) {
+					YouTubeModule.log("ERROR", "FILTER_MUSIC_VIDEOS", `${body.error.message}`);
+					return reject(new Error("An error has occured. Please try again later."));
+				}
+
+				const songIds = [];
+				body.items.forEach(item => {
+					const songId = item.id;
+					if (!item.topicDetails) return;
+					if (item.topicDetails.relevantTopicIds.indexOf("/m/04rlf") !== -1) {
+						songIds.push(songId);
+					}
+				});
+
+				return YouTubeModule.runJob("FILTER_MUSIC_VIDEOS", { videoIds: payload.videoIds, page: page + 1 })
+					.then(result => {
+						resolve({ songIds: songIds.concat(result.songIds) });
+					})
+					.catch(err => {
+						reject(err);
+					});
+			});
+		});
+	}
+}
+
+export default new _YouTubeModule();