youtube.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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. this.keepLongJob();
  160. this.publishProgress({
  161. status: "started",
  162. title: "Reset stored API requests",
  163. message: "Resetting stored API requests.",
  164. id: this.toString()
  165. });
  166. await CacheModule.runJob("RPUSH", { key: `longJobs.${session.userId}`, value: this.toString() }, this);
  167. await CacheModule.runJob(
  168. "PUB",
  169. {
  170. channel: "longJob.added",
  171. value: { jobId: this.toString(), userId: session.userId }
  172. },
  173. this
  174. );
  175. YouTubeModule.runJob("RESET_STORED_API_REQUESTS", {}, this)
  176. .then(() => {
  177. this.log(
  178. "SUCCESS",
  179. "YOUTUBE_RESET_STORED_API_REQUESTS",
  180. `Resetting stored API requests was successful.`
  181. );
  182. this.publishProgress({
  183. status: "success",
  184. message: "Successfully reset stored YouTube API requests."
  185. });
  186. return cb({ status: "success", message: "Successfully reset stored YouTube API requests" });
  187. })
  188. .catch(async err => {
  189. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  190. this.log(
  191. "ERROR",
  192. "YOUTUBE_RESET_STORED_API_REQUESTS",
  193. `Resetting stored API requests failed. "${err}"`
  194. );
  195. this.publishProgress({
  196. status: "error",
  197. message: err
  198. });
  199. return cb({ status: "error", message: err });
  200. });
  201. }),
  202. /**
  203. * Remove stored API requests
  204. *
  205. * @returns {{status: string, data: object}}
  206. */
  207. removeStoredApiRequest: isAdminRequired(function removeStoredApiRequest(session, requestId, cb) {
  208. YouTubeModule.runJob("REMOVE_STORED_API_REQUEST", { requestId }, this)
  209. .then(() => {
  210. this.log(
  211. "SUCCESS",
  212. "YOUTUBE_REMOVE_STORED_API_REQUEST",
  213. `Removing stored API request "${requestId}" was successful.`
  214. );
  215. return cb({ status: "success", message: "Successfully removed stored YouTube API request" });
  216. })
  217. .catch(async err => {
  218. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  219. this.log(
  220. "ERROR",
  221. "YOUTUBE_REMOVE_STORED_API_REQUEST",
  222. `Removing stored API request "${requestId}" failed. "${err}"`
  223. );
  224. return cb({ status: "error", message: err });
  225. });
  226. }),
  227. /**
  228. * Gets videos, used in the admin youtube page by the AdvancedTable component
  229. *
  230. * @param {object} session - the session object automatically added by the websocket
  231. * @param page - the page
  232. * @param pageSize - the size per page
  233. * @param properties - the properties to return for each news item
  234. * @param sort - the sort object
  235. * @param queries - the queries array
  236. * @param operator - the operator for queries
  237. * @param cb
  238. */
  239. getVideos: isAdminRequired(async function getVideos(
  240. session,
  241. page,
  242. pageSize,
  243. properties,
  244. sort,
  245. queries,
  246. operator,
  247. cb
  248. ) {
  249. async.waterfall(
  250. [
  251. next => {
  252. DBModule.runJob(
  253. "GET_DATA",
  254. {
  255. page,
  256. pageSize,
  257. properties,
  258. sort,
  259. queries,
  260. operator,
  261. modelName: "youtubeVideo",
  262. blacklistedProperties: [],
  263. specialProperties: {},
  264. specialQueries: {},
  265. specialFilters: {
  266. importJob: importJobId => [
  267. {
  268. $lookup: {
  269. from: "importjobs",
  270. let: { youtubeId: "$youtubeId" },
  271. pipeline: [
  272. {
  273. $match: {
  274. _id: mongoose.Types.ObjectId(importJobId)
  275. }
  276. },
  277. {
  278. $addFields: {
  279. importJob: {
  280. $in: ["$$youtubeId", "$response.successfulVideoIds"]
  281. }
  282. }
  283. },
  284. {
  285. $project: {
  286. importJob: 1,
  287. _id: 0
  288. }
  289. }
  290. ],
  291. as: "importJob"
  292. }
  293. },
  294. {
  295. $unwind: "$importJob"
  296. },
  297. {
  298. $set: {
  299. importJob: "$importJob.importJob"
  300. }
  301. }
  302. ]
  303. }
  304. },
  305. this
  306. )
  307. .then(response => {
  308. next(null, response);
  309. })
  310. .catch(err => {
  311. next(err);
  312. });
  313. }
  314. ],
  315. async (err, response) => {
  316. if (err && err !== true) {
  317. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  318. this.log("ERROR", "YOUTUBE_GET_VIDEOS", `Failed to get YouTube videos. "${err}"`);
  319. return cb({ status: "error", message: err });
  320. }
  321. this.log("SUCCESS", "YOUTUBE_GET_VIDEOS", `Fetched YouTube videos successfully.`);
  322. return cb({
  323. status: "success",
  324. message: "Successfully fetched YouTube videos.",
  325. data: response
  326. });
  327. }
  328. );
  329. }),
  330. /**
  331. * Get a YouTube video
  332. *
  333. * @returns {{status: string, data: object}}
  334. */
  335. getVideo: isLoginRequired(function getVideo(session, identifier, createMissing, cb) {
  336. YouTubeModule.runJob("GET_VIDEO", { identifier, createMissing }, this)
  337. .then(res => {
  338. this.log("SUCCESS", "YOUTUBE_GET_VIDEO", `Fetching video was successful.`);
  339. return cb({ status: "success", message: "Successfully fetched YouTube video", data: res.video });
  340. })
  341. .catch(async err => {
  342. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  343. this.log("ERROR", "YOUTUBE_GET_VIDEO", `Fetching video failed. "${err}"`);
  344. return cb({ status: "error", message: err });
  345. });
  346. }),
  347. /**
  348. * Remove YouTube videos
  349. *
  350. * @returns {{status: string, data: object}}
  351. */
  352. removeVideos: isAdminRequired(async function removeVideos(session, videoIds, cb) {
  353. this.keepLongJob();
  354. this.publishProgress({
  355. status: "started",
  356. title: "Bulk remove YouTube videos",
  357. message: "Bulk removing YouTube videos.",
  358. id: this.toString()
  359. });
  360. await CacheModule.runJob("RPUSH", { key: `longJobs.${session.userId}`, value: this.toString() }, this);
  361. await CacheModule.runJob(
  362. "PUB",
  363. {
  364. channel: "longJob.added",
  365. value: { jobId: this.toString(), userId: session.userId }
  366. },
  367. this
  368. );
  369. YouTubeModule.runJob("REMOVE_VIDEOS", { videoIds }, this)
  370. .then(() => {
  371. this.log("SUCCESS", "YOUTUBE_REMOVE_VIDEOS", `Removing videos was successful.`);
  372. this.publishProgress({
  373. status: "success",
  374. message: "Successfully removed YouTube videos."
  375. });
  376. return cb({ status: "success", message: "Successfully removed YouTube videos" });
  377. })
  378. .catch(async err => {
  379. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  380. this.log("ERROR", "YOUTUBE_REMOVE_VIDEOS", `Removing videos failed. "${err}"`);
  381. this.publishProgress({
  382. status: "error",
  383. message: err
  384. });
  385. return cb({ status: "error", message: err });
  386. });
  387. }),
  388. /**
  389. * Requests a set of YouTube videos
  390. *
  391. * @param {object} session - the session object automatically added by the websocket
  392. * @param {string} url - the url of the the YouTube playlist
  393. * @param {boolean} musicOnly - whether to only get music from the playlist
  394. * @param {boolean} musicOnly - whether to return videos
  395. * @param {Function} cb - gets called with the result
  396. */
  397. requestSet: isLoginRequired(function requestSet(session, url, musicOnly, returnVideos, cb) {
  398. YouTubeModule.runJob("REQUEST_SET", { url, musicOnly, returnVideos }, this)
  399. .then(response => {
  400. this.log(
  401. "SUCCESS",
  402. "REQUEST_SET",
  403. `Successfully imported a YouTube playlist to be requested for user "${session.userId}".`
  404. );
  405. return cb({
  406. status: "success",
  407. message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInDatabase} were already in database)`,
  408. videos: returnVideos ? response.videos : null
  409. });
  410. })
  411. .catch(async err => {
  412. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  413. this.log(
  414. "ERROR",
  415. "REQUEST_SET",
  416. `Importing a YouTube playlist to be requested failed for user "${session.userId}". "${err}"`
  417. );
  418. return cb({ status: "error", message: err });
  419. });
  420. }),
  421. /**
  422. * Requests a set of YouTube videos as an admin
  423. *
  424. * @param {object} session - the session object automatically added by the websocket
  425. * @param {string} url - the url of the the YouTube playlist
  426. * @param {boolean} musicOnly - whether to only get music from the playlist
  427. * @param {boolean} musicOnly - whether to return videos
  428. * @param {Function} cb - gets called with the result
  429. */
  430. requestSetAdmin: isAdminRequired(async function requestSetAdmin(session, url, musicOnly, returnVideos, cb) {
  431. const importJobModel = await DBModule.runJob("GET_MODEL", { modelName: "importJob" }, this);
  432. this.keepLongJob();
  433. this.publishProgress({
  434. status: "started",
  435. title: "Import playlist",
  436. message: "Importing playlist.",
  437. id: this.toString()
  438. });
  439. await CacheModule.runJob("RPUSH", { key: `longJobs.${session.userId}`, value: this.toString() }, this);
  440. await CacheModule.runJob(
  441. "PUB",
  442. {
  443. channel: "longJob.added",
  444. value: { jobId: this.toString(), userId: session.userId }
  445. },
  446. this
  447. );
  448. async.waterfall(
  449. [
  450. next => {
  451. importJobModel.create(
  452. {
  453. type: "youtube",
  454. query: {
  455. url,
  456. musicOnly
  457. },
  458. status: "in-progress",
  459. response: {},
  460. requestedBy: session.userId,
  461. requestedAt: Date.now()
  462. },
  463. next
  464. );
  465. },
  466. (importJob, next) => {
  467. YouTubeModule.runJob("REQUEST_SET", { url, musicOnly, returnVideos }, this)
  468. .then(response => {
  469. next(null, importJob, response);
  470. })
  471. .catch(err => {
  472. next(err, importJob);
  473. });
  474. },
  475. (importJob, response, next) => {
  476. importJobModel.updateOne(
  477. { _id: importJob._id },
  478. {
  479. $set: {
  480. status: "success",
  481. response: {
  482. failed: response.failed,
  483. successful: response.successful,
  484. alreadyInDatabase: response.alreadyInDatabase,
  485. successfulVideoIds: response.successfulVideoIds,
  486. failedVideoIds: response.failedVideoIds
  487. }
  488. }
  489. },
  490. err => {
  491. if (err) next(err, importJob);
  492. else
  493. MediaModule.runJob("UPDATE_IMPORT_JOBS", { jobIds: importJob._id })
  494. .then(() => next(null, importJob, response))
  495. .catch(error => next(error, importJob));
  496. }
  497. );
  498. }
  499. ],
  500. async (err, importJob, response) => {
  501. if (err) {
  502. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  503. this.log(
  504. "ERROR",
  505. "REQUEST_SET_ADMIN",
  506. `Importing a YouTube playlist to be requested failed for admin "${session.userId}". "${err}"`
  507. );
  508. importJobModel.updateOne({ _id: importJob._id }, { $set: { status: "error" } });
  509. MediaModule.runJob("UPDATE_IMPORT_JOBS", { jobIds: importJob._id });
  510. return cb({ status: "error", message: err });
  511. }
  512. this.log(
  513. "SUCCESS",
  514. "REQUEST_SET_ADMIN",
  515. `Successfully imported a YouTube playlist to be requested for admin "${session.userId}".`
  516. );
  517. this.publishProgress({
  518. status: "success",
  519. message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInDatabase} were already in database)`
  520. });
  521. return cb({
  522. status: "success",
  523. message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInDatabase} were already in database)`,
  524. videos: returnVideos ? response.videos : null
  525. });
  526. }
  527. );
  528. })
  529. };