youtube.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. import mongoose from "mongoose";
  2. import async from "async";
  3. import { isAdminRequired, isLoginRequired } from "./hooks";
  4. // eslint-disable-next-line
  5. import moduleManager from "../../index";
  6. const DBModule = moduleManager.modules.db;
  7. const CacheModule = moduleManager.modules.cache;
  8. const UtilsModule = moduleManager.modules.utils;
  9. const YouTubeModule = moduleManager.modules.youtube;
  10. const MediaModule = moduleManager.modules.media;
  11. export default {
  12. /**
  13. * Returns details about the YouTube quota usage
  14. *
  15. * @returns {{status: string, data: object}}
  16. */
  17. getQuotaStatus: isAdminRequired(function getQuotaStatus(session, fromDate, cb) {
  18. YouTubeModule.runJob("GET_QUOTA_STATUS", { fromDate }, this)
  19. .then(response => {
  20. this.log("SUCCESS", "YOUTUBE_GET_QUOTA_STATUS", `Getting quota status was successful.`);
  21. return cb({ status: "success", data: { status: response.status } });
  22. })
  23. .catch(async err => {
  24. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  25. this.log("ERROR", "YOUTUBE_GET_QUOTA_STATUS", `Getting quota status failed. "${err}"`);
  26. return cb({ status: "error", message: err });
  27. });
  28. }),
  29. /**
  30. * Returns YouTube quota chart data
  31. *
  32. * @param {object} session - the session object automatically added by the websocket
  33. * @param timePeriod - either hours or days
  34. * @param startDate - beginning date
  35. * @param endDate - end date
  36. * @param dataType - either usage or count
  37. * @returns {{status: string, data: object}}
  38. */
  39. getQuotaChartData: isAdminRequired(function getQuotaChartData(
  40. session,
  41. timePeriod,
  42. startDate,
  43. endDate,
  44. dataType,
  45. cb
  46. ) {
  47. YouTubeModule.runJob(
  48. "GET_QUOTA_CHART_DATA",
  49. { timePeriod, startDate: new Date(startDate), endDate: new Date(endDate), dataType },
  50. this
  51. )
  52. .then(data => {
  53. this.log("SUCCESS", "YOUTUBE_GET_QUOTA_CHART_DATA", `Getting quota chart data was successful.`);
  54. return cb({ status: "success", data });
  55. })
  56. .catch(async err => {
  57. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  58. this.log("ERROR", "YOUTUBE_GET_QUOTA_CHART_DATA", `Getting quota chart data failed. "${err}"`);
  59. return cb({ status: "error", message: err });
  60. });
  61. }),
  62. /**
  63. * Gets api requests, used in the admin youtube page by the AdvancedTable component
  64. *
  65. * @param {object} session - the session object automatically added by the websocket
  66. * @param page - the page
  67. * @param pageSize - the size per page
  68. * @param properties - the properties to return for each news item
  69. * @param sort - the sort object
  70. * @param queries - the queries array
  71. * @param operator - the operator for queries
  72. * @param cb
  73. */
  74. getApiRequests: isAdminRequired(async function getApiRequests(
  75. session,
  76. page,
  77. pageSize,
  78. properties,
  79. sort,
  80. queries,
  81. operator,
  82. cb
  83. ) {
  84. async.waterfall(
  85. [
  86. next => {
  87. DBModule.runJob(
  88. "GET_DATA",
  89. {
  90. page,
  91. pageSize,
  92. properties,
  93. sort,
  94. queries,
  95. operator,
  96. modelName: "youtubeApiRequest",
  97. blacklistedProperties: [],
  98. specialProperties: {},
  99. specialQueries: {}
  100. },
  101. this
  102. )
  103. .then(response => {
  104. next(null, response);
  105. })
  106. .catch(err => {
  107. next(err);
  108. });
  109. }
  110. ],
  111. async (err, response) => {
  112. if (err && err !== true) {
  113. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  114. this.log("ERROR", "YOUTUBE_GET_API_REQUESTS", `Failed to get YouTube api requests. "${err}"`);
  115. return cb({ status: "error", message: err });
  116. }
  117. this.log("SUCCESS", "YOUTUBE_GET_API_REQUESTS", `Fetched YouTube api requests successfully.`);
  118. return cb({
  119. status: "success",
  120. message: "Successfully fetched YouTube api requests.",
  121. data: response
  122. });
  123. }
  124. );
  125. }),
  126. /**
  127. * Returns a specific api request
  128. *
  129. * @returns {{status: string, data: object}}
  130. */
  131. getApiRequest: isAdminRequired(function getApiRequest(session, apiRequestId, cb) {
  132. if (!mongoose.Types.ObjectId.isValid(apiRequestId))
  133. return cb({ status: "error", message: "Api request id is not a valid ObjectId." });
  134. return YouTubeModule.runJob("GET_API_REQUEST", { apiRequestId }, this)
  135. .then(response => {
  136. this.log(
  137. "SUCCESS",
  138. "YOUTUBE_GET_API_REQUEST",
  139. `Getting api request with id ${apiRequestId} was successful.`
  140. );
  141. return cb({ status: "success", data: { apiRequest: response.apiRequest } });
  142. })
  143. .catch(async err => {
  144. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  145. this.log(
  146. "ERROR",
  147. "YOUTUBE_GET_API_REQUEST",
  148. `Getting api request with id ${apiRequestId} failed. "${err}"`
  149. );
  150. return cb({ status: "error", message: err });
  151. });
  152. }),
  153. /**
  154. * Reset stored API requests
  155. *
  156. * @returns {{status: string, data: object}}
  157. */
  158. resetStoredApiRequests: isAdminRequired(async function resetStoredApiRequests(session, cb) {
  159. YouTubeModule.runJob("RESET_STORED_API_REQUESTS", {}, this)
  160. .then(() => {
  161. this.log(
  162. "SUCCESS",
  163. "YOUTUBE_RESET_STORED_API_REQUESTS",
  164. `Resetting stored API requests was successful.`
  165. );
  166. return cb({ status: "success", message: "Successfully reset stored YouTube API requests" });
  167. })
  168. .catch(async err => {
  169. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  170. this.log(
  171. "ERROR",
  172. "YOUTUBE_RESET_STORED_API_REQUESTS",
  173. `Resetting stored API requests failed. "${err}"`
  174. );
  175. return cb({ status: "error", message: err });
  176. });
  177. }),
  178. /**
  179. * Remove stored API requests
  180. *
  181. * @returns {{status: string, data: object}}
  182. */
  183. removeStoredApiRequest: isAdminRequired(function removeStoredApiRequest(session, requestId, cb) {
  184. YouTubeModule.runJob("REMOVE_STORED_API_REQUEST", { requestId }, this)
  185. .then(() => {
  186. this.log(
  187. "SUCCESS",
  188. "YOUTUBE_REMOVE_STORED_API_REQUEST",
  189. `Removing stored API request "${requestId}" was successful.`
  190. );
  191. return cb({ status: "success", message: "Successfully removed stored YouTube API request" });
  192. })
  193. .catch(async err => {
  194. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  195. this.log(
  196. "ERROR",
  197. "YOUTUBE_REMOVE_STORED_API_REQUEST",
  198. `Removing stored API request "${requestId}" failed. "${err}"`
  199. );
  200. return cb({ status: "error", message: err });
  201. });
  202. }),
  203. /**
  204. * Gets videos, used in the admin youtube page by the AdvancedTable component
  205. *
  206. * @param {object} session - the session object automatically added by the websocket
  207. * @param page - the page
  208. * @param pageSize - the size per page
  209. * @param properties - the properties to return for each news item
  210. * @param sort - the sort object
  211. * @param queries - the queries array
  212. * @param operator - the operator for queries
  213. * @param cb
  214. */
  215. getVideos: isAdminRequired(async function getVideos(
  216. session,
  217. page,
  218. pageSize,
  219. properties,
  220. sort,
  221. queries,
  222. operator,
  223. cb
  224. ) {
  225. async.waterfall(
  226. [
  227. next => {
  228. DBModule.runJob(
  229. "GET_DATA",
  230. {
  231. page,
  232. pageSize,
  233. properties,
  234. sort,
  235. queries,
  236. operator,
  237. modelName: "youtubeVideo",
  238. blacklistedProperties: [],
  239. specialProperties: {},
  240. specialQueries: {},
  241. specialFilters: {
  242. importJob: importJobId => [
  243. {
  244. $lookup: {
  245. from: "importjobs",
  246. let: { youtubeId: "$youtubeId" },
  247. pipeline: [
  248. {
  249. $match: {
  250. _id: mongoose.Types.ObjectId(importJobId)
  251. }
  252. },
  253. {
  254. $addFields: {
  255. importJob: {
  256. $in: ["$$youtubeId", "$response.successfulVideoIds"]
  257. }
  258. }
  259. },
  260. {
  261. $project: {
  262. importJob: 1,
  263. _id: 0
  264. }
  265. }
  266. ],
  267. as: "importJob"
  268. }
  269. },
  270. {
  271. $unwind: "$importJob"
  272. },
  273. {
  274. $set: {
  275. importJob: "$importJob.importJob"
  276. }
  277. }
  278. ]
  279. }
  280. },
  281. this
  282. )
  283. .then(response => {
  284. next(null, response);
  285. })
  286. .catch(err => {
  287. next(err);
  288. });
  289. }
  290. ],
  291. async (err, response) => {
  292. if (err && err !== true) {
  293. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  294. this.log("ERROR", "YOUTUBE_GET_VIDEOS", `Failed to get YouTube videos. "${err}"`);
  295. return cb({ status: "error", message: err });
  296. }
  297. this.log("SUCCESS", "YOUTUBE_GET_VIDEOS", `Fetched YouTube videos successfully.`);
  298. return cb({
  299. status: "success",
  300. message: "Successfully fetched YouTube videos.",
  301. data: response
  302. });
  303. }
  304. );
  305. }),
  306. /**
  307. * Get a YouTube video
  308. *
  309. * @returns {{status: string, data: object}}
  310. */
  311. getVideo: isLoginRequired(function getVideo(session, identifier, createMissing, cb) {
  312. YouTubeModule.runJob("GET_VIDEO", { identifier, createMissing }, this)
  313. .then(res => {
  314. this.log("SUCCESS", "YOUTUBE_GET_VIDEO", `Fetching video was successful.`);
  315. return cb({ status: "success", message: "Successfully fetched YouTube video", data: res.video });
  316. })
  317. .catch(async err => {
  318. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  319. this.log("ERROR", "YOUTUBE_GET_VIDEO", `Fetching video failed. "${err}"`);
  320. return cb({ status: "error", message: err });
  321. });
  322. }),
  323. /**
  324. * Remove YouTube videos
  325. *
  326. * @returns {{status: string, data: object}}
  327. */
  328. removeVideos: isAdminRequired(function removeVideos(session, videoIds, cb) {
  329. YouTubeModule.runJob("REMOVE_VIDEOS", { videoIds }, this)
  330. .then(() => {
  331. this.log("SUCCESS", "YOUTUBE_REMOVE_VIDEOS", `Removing videos was successful.`);
  332. return cb({ status: "success", message: "Successfully removed YouTube videos" });
  333. })
  334. .catch(async err => {
  335. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  336. this.log("ERROR", "YOUTUBE_REMOVE_VIDEOS", `Removing videos failed. "${err}"`);
  337. return cb({ status: "error", message: err });
  338. });
  339. }),
  340. /**
  341. * Requests a set of YouTube videos
  342. *
  343. * @param {object} session - the session object automatically added by the websocket
  344. * @param {string} url - the url of the the YouTube playlist
  345. * @param {boolean} musicOnly - whether to only get music from the playlist
  346. * @param {boolean} musicOnly - whether to return videos
  347. * @param {Function} cb - gets called with the result
  348. */
  349. requestSet: isLoginRequired(function requestSet(session, url, musicOnly, returnVideos, cb) {
  350. YouTubeModule.runJob("REQUEST_SET", { url, musicOnly, returnVideos }, this)
  351. .then(response => {
  352. this.log(
  353. "SUCCESS",
  354. "REQUEST_SET",
  355. `Successfully imported a YouTube playlist to be requested for user "${session.userId}".`
  356. );
  357. return cb({
  358. status: "success",
  359. message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInDatabase} were already in database)`,
  360. videos: returnVideos ? response.videos : null
  361. });
  362. })
  363. .catch(async err => {
  364. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  365. this.log(
  366. "ERROR",
  367. "REQUEST_SET",
  368. `Importing a YouTube playlist to be requested failed for user "${session.userId}". "${err}"`
  369. );
  370. return cb({ status: "error", message: err });
  371. });
  372. }),
  373. /**
  374. * Requests a set of YouTube videos as an admin
  375. *
  376. * @param {object} session - the session object automatically added by the websocket
  377. * @param {string} url - the url of the the YouTube playlist
  378. * @param {boolean} musicOnly - whether to only get music from the playlist
  379. * @param {boolean} musicOnly - whether to return videos
  380. * @param {Function} cb - gets called with the result
  381. */
  382. requestSetAdmin: isAdminRequired(async function requestSetAdmin(session, url, musicOnly, returnVideos, cb) {
  383. const importJobModel = await DBModule.runJob("GET_MODEL", { modelName: "importJob" }, this);
  384. this.keepLongJob();
  385. this.publishProgress({
  386. status: "started",
  387. title: "Import playlist",
  388. message: "Importing playlist.",
  389. id: this.toString()
  390. });
  391. await CacheModule.runJob("RPUSH", { key: `longJobs.${session.userId}`, value: this.toString() }, this);
  392. await CacheModule.runJob(
  393. "PUB",
  394. {
  395. channel: "longJob.added",
  396. value: { jobId: this.toString(), userId: session.userId }
  397. },
  398. this
  399. );
  400. async.waterfall(
  401. [
  402. next => {
  403. importJobModel.create(
  404. {
  405. type: "youtube",
  406. query: {
  407. url,
  408. musicOnly
  409. },
  410. status: "in-progress",
  411. response: {},
  412. requestedBy: session.userId,
  413. requestedAt: Date.now()
  414. },
  415. next
  416. );
  417. },
  418. (importJob, next) => {
  419. YouTubeModule.runJob("REQUEST_SET", { url, musicOnly, returnVideos }, this)
  420. .then(response => {
  421. next(null, importJob, response);
  422. })
  423. .catch(err => {
  424. next(err, importJob);
  425. });
  426. },
  427. (importJob, response, next) => {
  428. importJobModel.updateOne(
  429. { _id: importJob._id },
  430. {
  431. $set: {
  432. status: "success",
  433. response: {
  434. failed: response.failed,
  435. successful: response.successful,
  436. alreadyInDatabase: response.alreadyInDatabase,
  437. successfulVideoIds: response.successfulVideoIds,
  438. failedVideoIds: response.failedVideoIds
  439. }
  440. }
  441. },
  442. err => {
  443. if (err) next(err, importJob);
  444. else
  445. MediaModule.runJob("UPDATE_IMPORT_JOBS", { jobIds: importJob._id })
  446. .then(() => next(null, importJob, response))
  447. .catch(error => next(error, importJob));
  448. }
  449. );
  450. }
  451. ],
  452. async (err, importJob, response) => {
  453. if (err) {
  454. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  455. this.log(
  456. "ERROR",
  457. "REQUEST_SET_ADMIN",
  458. `Importing a YouTube playlist to be requested failed for admin "${session.userId}". "${err}"`
  459. );
  460. importJobModel.updateOne({ _id: importJob._id }, { $set: { status: "error" } });
  461. MediaModule.runJob("UPDATE_IMPORT_JOBS", { jobIds: importJob._id });
  462. return cb({ status: "error", message: err });
  463. }
  464. this.log(
  465. "SUCCESS",
  466. "REQUEST_SET_ADMIN",
  467. `Successfully imported a YouTube playlist to be requested for admin "${session.userId}".`
  468. );
  469. this.publishProgress({
  470. status: "success",
  471. message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInDatabase} were already in database)`
  472. });
  473. return cb({
  474. status: "success",
  475. message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInDatabase} were already in database)`,
  476. videos: returnVideos ? response.videos : null
  477. });
  478. }
  479. );
  480. })
  481. };