songs.js 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484
  1. import async from "async";
  2. import { isAdminRequired, isLoginRequired } from "./hooks";
  3. import moduleManager from "../../index";
  4. const DBModule = moduleManager.modules.db;
  5. const UtilsModule = moduleManager.modules.utils;
  6. const WSModule = moduleManager.modules.ws;
  7. const CacheModule = moduleManager.modules.cache;
  8. const SongsModule = moduleManager.modules.songs;
  9. const ActivitiesModule = moduleManager.modules.activities;
  10. const YouTubeModule = moduleManager.modules.youtube;
  11. const PlaylistsModule = moduleManager.modules.playlists;
  12. CacheModule.runJob("SUB", {
  13. channel: "song.newUnverifiedSong",
  14. cb: async songId => {
  15. const songModel = await DBModule.runJob("GET_MODEL", {
  16. modelName: "song"
  17. });
  18. songModel.findOne({ _id: songId }, (err, song) =>
  19. WSModule.runJob("EMIT_TO_ROOMS", {
  20. rooms: ["admin.unverifiedSongs", `edit-song.${songId}`],
  21. args: ["event:admin.unverifiedSong.created", { data: { song } }]
  22. })
  23. );
  24. }
  25. });
  26. CacheModule.runJob("SUB", {
  27. channel: "song.removedUnverifiedSong",
  28. cb: songId => {
  29. WSModule.runJob("EMIT_TO_ROOM", {
  30. room: "admin.unverifiedSongs",
  31. args: ["event:admin.unverifiedSong.deleted", { data: { songId } }]
  32. });
  33. }
  34. });
  35. CacheModule.runJob("SUB", {
  36. channel: "song.updatedUnverifiedSong",
  37. cb: async songId => {
  38. const songModel = await DBModule.runJob("GET_MODEL", {
  39. modelName: "song"
  40. });
  41. songModel.findOne({ _id: songId }, (err, song) => {
  42. WSModule.runJob("EMIT_TO_ROOM", {
  43. room: "admin.unverifiedSongs",
  44. args: ["event:admin.unverifiedSong.updated", { data: { song } }]
  45. });
  46. });
  47. }
  48. });
  49. CacheModule.runJob("SUB", {
  50. channel: "song.newVerifiedSong",
  51. cb: async songId => {
  52. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  53. songModel.findOne({ _id: songId }, (err, song) =>
  54. WSModule.runJob("EMIT_TO_ROOMS", {
  55. rooms: ["admin.songs", `edit-song.${songId}`],
  56. args: ["event:admin.verifiedSong.created", { data: { song } }]
  57. })
  58. );
  59. }
  60. });
  61. CacheModule.runJob("SUB", {
  62. channel: "song.removedVerifiedSong",
  63. cb: songId => {
  64. WSModule.runJob("EMIT_TO_ROOM", {
  65. room: "admin.songs",
  66. args: ["event:admin.verifiedSong.deleted", { data: { songId } }]
  67. });
  68. }
  69. });
  70. CacheModule.runJob("SUB", {
  71. channel: "song.updatedVerifiedSong",
  72. cb: async songId => {
  73. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" });
  74. songModel.findOne({ _id: songId }, (err, song) => {
  75. WSModule.runJob("EMIT_TO_ROOM", {
  76. room: "admin.songs",
  77. args: ["event:admin.verifiedSong.updated", { data: { song } }]
  78. });
  79. });
  80. }
  81. });
  82. CacheModule.runJob("SUB", {
  83. channel: "song.newHiddenSong",
  84. cb: async songId => {
  85. const songModel = await DBModule.runJob("GET_MODEL", {
  86. modelName: "song"
  87. });
  88. songModel.findOne({ _id: songId }, (err, song) =>
  89. WSModule.runJob("EMIT_TO_ROOMS", {
  90. rooms: ["admin.hiddenSongs", `edit-song.${songId}`],
  91. args: ["event:admin.hiddenSong.created", { data: { song } }]
  92. })
  93. );
  94. }
  95. });
  96. CacheModule.runJob("SUB", {
  97. channel: "song.removedHiddenSong",
  98. cb: songId => {
  99. WSModule.runJob("EMIT_TO_ROOM", {
  100. room: "admin.hiddenSongs",
  101. args: ["event:admin.hiddenSong.deleted", { data: { songId } }]
  102. });
  103. }
  104. });
  105. CacheModule.runJob("SUB", {
  106. channel: "song.updatedHiddenSong",
  107. cb: async songId => {
  108. const songModel = await DBModule.runJob("GET_MODEL", {
  109. modelName: "song"
  110. });
  111. songModel.findOne({ _id: songId }, (err, song) => {
  112. WSModule.runJob("EMIT_TO_ROOM", {
  113. room: "admin.hiddenSongs",
  114. args: ["event:admin.hiddenSong.updated", { data: { song } }]
  115. });
  116. });
  117. }
  118. });
  119. CacheModule.runJob("SUB", {
  120. channel: "song.like",
  121. cb: data => {
  122. WSModule.runJob("EMIT_TO_ROOM", {
  123. room: `song.${data.youtubeId}`,
  124. args: [
  125. "event:song.liked",
  126. {
  127. data: { youtubeId: data.youtubeId, likes: data.likes, dislikes: data.dislikes }
  128. }
  129. ]
  130. });
  131. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  132. sockets.forEach(socket => {
  133. socket.dispatch("event:song.ratings.updated", {
  134. data: {
  135. youtubeId: data.youtubeId,
  136. liked: true,
  137. disliked: false
  138. }
  139. });
  140. });
  141. });
  142. }
  143. });
  144. CacheModule.runJob("SUB", {
  145. channel: "song.dislike",
  146. cb: data => {
  147. WSModule.runJob("EMIT_TO_ROOM", {
  148. room: `song.${data.youtubeId}`,
  149. args: [
  150. "event:song.disliked",
  151. {
  152. data: { youtubeId: data.youtubeId, likes: data.likes, dislikes: data.dislikes }
  153. }
  154. ]
  155. });
  156. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  157. sockets.forEach(socket => {
  158. socket.dispatch("event:song.ratings.updated", {
  159. data: {
  160. youtubeId: data.youtubeId,
  161. liked: false,
  162. disliked: true
  163. }
  164. });
  165. });
  166. });
  167. }
  168. });
  169. CacheModule.runJob("SUB", {
  170. channel: "song.unlike",
  171. cb: data => {
  172. WSModule.runJob("EMIT_TO_ROOM", {
  173. room: `song.${data.youtubeId}`,
  174. args: [
  175. "event:song.unliked",
  176. {
  177. data: { youtubeId: data.youtubeId, likes: data.likes, dislikes: data.dislikes }
  178. }
  179. ]
  180. });
  181. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  182. sockets.forEach(socket => {
  183. socket.dispatch("event:song.ratings.updated", {
  184. data: {
  185. youtubeId: data.youtubeId,
  186. liked: false,
  187. disliked: false
  188. }
  189. });
  190. });
  191. });
  192. }
  193. });
  194. CacheModule.runJob("SUB", {
  195. channel: "song.undislike",
  196. cb: data => {
  197. WSModule.runJob("EMIT_TO_ROOM", {
  198. room: `song.${data.youtubeId}`,
  199. args: [
  200. "event:song.undisliked",
  201. {
  202. data: { youtubeId: data.youtubeId, likes: data.likes, dislikes: data.dislikes }
  203. }
  204. ]
  205. });
  206. WSModule.runJob("SOCKETS_FROM_USER", { userId: data.userId }).then(sockets => {
  207. sockets.forEach(socket => {
  208. socket.dispatch("event:song.ratings.updated", {
  209. data: {
  210. youtubeId: data.youtubeId,
  211. liked: false,
  212. disliked: false
  213. }
  214. });
  215. });
  216. });
  217. }
  218. });
  219. export default {
  220. /**
  221. * Returns the length of the songs list
  222. *
  223. * @param {object} session - the session object automatically added by the websocket
  224. * @param cb
  225. */
  226. length: isAdminRequired(async function length(session, status, cb) {
  227. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  228. async.waterfall(
  229. [
  230. next => {
  231. songModel.countDocuments({ status }, next);
  232. }
  233. ],
  234. async (err, count) => {
  235. if (err) {
  236. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  237. this.log(
  238. "ERROR",
  239. "SONGS_LENGTH",
  240. `Failed to get length from songs that have the status ${status}. "${err}"`
  241. );
  242. return cb({ status: "error", message: err });
  243. }
  244. this.log(
  245. "SUCCESS",
  246. "SONGS_LENGTH",
  247. `Got length from songs that have the status ${status} successfully.`
  248. );
  249. return cb({ status: "success", message: "Successfully got length of songs.", data: { length: count } });
  250. }
  251. );
  252. }),
  253. /**
  254. * Gets a set of songs
  255. *
  256. * @param {object} session - the session object automatically added by the websocket
  257. * @param set - the set number to return
  258. * @param cb
  259. */
  260. getSet: isAdminRequired(async function getSet(session, set, status, cb) {
  261. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  262. async.waterfall(
  263. [
  264. next => {
  265. songModel
  266. .find({ status })
  267. .skip(15 * (set - 1))
  268. .limit(15)
  269. .exec(next);
  270. }
  271. ],
  272. async (err, songs) => {
  273. if (err) {
  274. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  275. this.log(
  276. "ERROR",
  277. "SONGS_GET_SET",
  278. `Failed to get set from songs that have the status ${status}. "${err}"`
  279. );
  280. return cb({ status: "error", message: err });
  281. }
  282. this.log("SUCCESS", "SONGS_GET_SET", `Got set from songs that have the status ${status} successfully.`);
  283. return cb({ status: "success", message: "Successfully got set of songs.", data: { songs } });
  284. }
  285. );
  286. }),
  287. /**
  288. * Updates all songs
  289. *
  290. * @param {object} session - the session object automatically added by the websocket
  291. * @param cb
  292. */
  293. updateAll: isAdminRequired(async function length(session, cb) {
  294. async.waterfall(
  295. [
  296. next => {
  297. SongsModule.runJob("UPDATE_ALL_SONGS", {}, this)
  298. .then(() => {
  299. next();
  300. })
  301. .catch(err => {
  302. next(err);
  303. });
  304. }
  305. ],
  306. async err => {
  307. if (err) {
  308. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  309. this.log("ERROR", "SONGS_UPDATE_ALL", `Failed to update all songs. "${err}"`);
  310. return cb({ status: "error", message: err });
  311. }
  312. this.log("SUCCESS", "SONGS_UPDATE_ALL", `Updated all songs successfully.`);
  313. return cb({ status: "success", message: "Successfully updated all songs." });
  314. }
  315. );
  316. }),
  317. /**
  318. * Gets a song from the Musare song id
  319. *
  320. * @param {object} session - the session object automatically added by the websocket
  321. * @param {string} songId - the song id
  322. * @param {Function} cb
  323. */
  324. getSongFromSongId: isAdminRequired(function getSongFromSongId(session, songId, cb) {
  325. async.waterfall(
  326. [
  327. next => {
  328. SongsModule.runJob("GET_SONG", { songId }, this)
  329. .then(response => next(null, response.song))
  330. .catch(err => next(err));
  331. }
  332. ],
  333. async (err, song) => {
  334. if (err) {
  335. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  336. this.log("ERROR", "SONGS_GET_SONG_FROM_MUSARE_ID", `Failed to get song ${songId}. "${err}"`);
  337. return cb({ status: "error", message: err });
  338. }
  339. this.log("SUCCESS", "SONGS_GET_SONG_FROM_MUSARE_ID", `Got song ${songId} successfully.`);
  340. return cb({ status: "success", data: { song } });
  341. }
  342. );
  343. }),
  344. /**
  345. * Updates a song
  346. *
  347. * @param {object} session - the session object automatically added by the websocket
  348. * @param {string} songId - the song id
  349. * @param {object} song - the updated song object
  350. * @param {Function} cb
  351. */
  352. update: isAdminRequired(async function update(session, songId, song, cb) {
  353. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  354. let existingSong = null;
  355. async.waterfall(
  356. [
  357. next => {
  358. songModel.findOne({ _id: songId }, next);
  359. },
  360. (_existingSong, next) => {
  361. existingSong = _existingSong;
  362. songModel.updateOne({ _id: songId }, song, { runValidators: true }, next);
  363. },
  364. (res, next) => {
  365. SongsModule.runJob("UPDATE_SONG", { songId }, this)
  366. .then(song => {
  367. existingSong.genres
  368. .concat(song.genres)
  369. .filter((value, index, self) => self.indexOf(value) === index)
  370. .forEach(genre => {
  371. PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
  372. .then(() => {})
  373. .catch(() => {});
  374. });
  375. next(null, song);
  376. })
  377. .catch(next);
  378. }
  379. ],
  380. async (err, song) => {
  381. if (err) {
  382. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  383. this.log("ERROR", "SONGS_UPDATE", `Failed to update song "${songId}". "${err}"`);
  384. return cb({ status: "error", message: err });
  385. }
  386. this.log("SUCCESS", "SONGS_UPDATE", `Successfully updated song "${songId}".`);
  387. if (song.status === "verified") {
  388. CacheModule.runJob("PUB", {
  389. channel: "song.updatedVerifiedSong",
  390. value: song._id
  391. });
  392. } else if (song.status === "unverified") {
  393. CacheModule.runJob("PUB", {
  394. channel: "song.updatedUnverifiedSong",
  395. value: song._id
  396. });
  397. } else if (song.status === "hidden") {
  398. CacheModule.runJob("PUB", {
  399. channel: "song.updatedHiddenSong",
  400. value: song._id
  401. });
  402. }
  403. return cb({
  404. status: "success",
  405. message: "Song has been successfully updated",
  406. data: { song }
  407. });
  408. }
  409. );
  410. }),
  411. // /**
  412. // * Removes a song
  413. // *
  414. // * @param session
  415. // * @param songId - the song id
  416. // * @param cb
  417. // */
  418. // remove: isAdminRequired(async function remove(session, songId, cb) {
  419. // const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  420. // let song = null;
  421. // async.waterfall(
  422. // [
  423. // next => {
  424. // songModel.findOne({ _id: songId }, next);
  425. // },
  426. // (_song, next) => {
  427. // song = _song;
  428. // songModel.deleteOne({ _id: songId }, next);
  429. // },
  430. // (res, next) => {
  431. // CacheModule.runJob("HDEL", { table: "songs", key: songId }, this)
  432. // .then(() => {
  433. // next();
  434. // })
  435. // .catch(next)
  436. // .finally(() => {
  437. // song.genres.forEach(genre => {
  438. // PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
  439. // .then(() => {})
  440. // .catch(() => {});
  441. // });
  442. // });
  443. // }
  444. // ],
  445. // async err => {
  446. // if (err) {
  447. // err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  448. // this.log("ERROR", "SONGS_REMOVE", `Failed to remove song "${songId}". "${err}"`);
  449. // return cb({ status: "error", message: err });
  450. // }
  451. // this.log("SUCCESS", "SONGS_REMOVE", `Successfully removed song "${songId}".`);
  452. // if (song.status === "verified") {
  453. // CacheModule.runJob("PUB", {
  454. // channel: "song.removedVerifiedSong",
  455. // value: songId
  456. // });
  457. // }
  458. // if (song.status === "unverified") {
  459. // CacheModule.runJob("PUB", {
  460. // channel: "song.removedUnverifiedSong",
  461. // value: songId
  462. // });
  463. // }
  464. // if (song.status === "hidden") {
  465. // CacheModule.runJob("PUB", {
  466. // channel: "song.removedHiddenSong",
  467. // value: songId
  468. // });
  469. // }
  470. // return cb({
  471. // status: "success",
  472. // message: "Song has been successfully removed"
  473. // });
  474. // }
  475. // );
  476. // }),
  477. /**
  478. * Searches through official songs
  479. *
  480. * @param {object} session - the session object automatically added by the websocket
  481. * @param {string} query - the query
  482. * @param {string} page - the page
  483. * @param {Function} cb - gets called with the result
  484. */
  485. searchOfficial: isLoginRequired(async function searchOfficial(session, query, page, cb) {
  486. async.waterfall(
  487. [
  488. next => {
  489. if ((!query && query !== "") || typeof query !== "string") next("Invalid query.");
  490. else next();
  491. },
  492. next => {
  493. SongsModule.runJob("SEARCH", {
  494. query,
  495. includeVerified: true,
  496. trimmed: true,
  497. page
  498. })
  499. .then(response => {
  500. next(null, response);
  501. })
  502. .catch(err => {
  503. next(err);
  504. });
  505. }
  506. ],
  507. async (err, data) => {
  508. if (err) {
  509. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  510. this.log("ERROR", "SONGS_SEARCH_OFFICIAL", `Searching songs failed. "${err}"`);
  511. return cb({ status: "error", message: err });
  512. }
  513. this.log("SUCCESS", "SONGS_SEARCH_OFFICIAL", "Searching songs successful.");
  514. return cb({ status: "success", data });
  515. }
  516. );
  517. }),
  518. /**
  519. * Requests a song
  520. *
  521. * @param {object} session - the session object automatically added by the websocket
  522. * @param {string} youtubeId - the youtube id of the song that gets requested
  523. * @param {string} returnSong - returns the simple song
  524. * @param {Function} cb - gets called with the result
  525. */
  526. request: isLoginRequired(async function add(session, youtubeId, returnSong, cb) {
  527. SongsModule.runJob("REQUEST_SONG", { youtubeId, userId: session.userId }, this)
  528. .then(response => {
  529. this.log(
  530. "SUCCESS",
  531. "SONGS_REQUEST",
  532. `User "${session.userId}" successfully requested song "${youtubeId}".`
  533. );
  534. return cb({
  535. status: "success",
  536. message: "Successfully requested that song",
  537. song: returnSong ? response.song : null
  538. });
  539. })
  540. .catch(async _err => {
  541. const err = await UtilsModule.runJob("GET_ERROR", { error: _err }, this);
  542. this.log(
  543. "ERROR",
  544. "SONGS_REQUEST",
  545. `Requesting song "${youtubeId}" failed for user ${session.userId}. "${err}"`
  546. );
  547. return cb({ status: "error", message: err, song: returnSong && _err.data ? _err.data.song : null });
  548. });
  549. }),
  550. /**
  551. * Hides a song
  552. *
  553. * @param {object} session - the session object automatically added by the websocket
  554. * @param {string} songId - the song id of the song that gets hidden
  555. * @param {Function} cb - gets called with the result
  556. */
  557. hide: isLoginRequired(async function add(session, songId, cb) {
  558. SongsModule.runJob("HIDE_SONG", { songId }, this)
  559. .then(() => {
  560. this.log("SUCCESS", "SONGS_HIDE", `User "${session.userId}" successfully hid song "${songId}".`);
  561. return cb({
  562. status: "success",
  563. message: "Successfully hid that song"
  564. });
  565. })
  566. .catch(async err => {
  567. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  568. this.log("ERROR", "SONGS_HIDE", `Hiding song "${songId}" failed for user ${session.userId}. "${err}"`);
  569. return cb({ status: "error", message: err });
  570. });
  571. }),
  572. /**
  573. * Unhides a song
  574. *
  575. * @param {object} session - the session object automatically added by the websocket
  576. * @param {string} songId - the song id of the song that gets hidden
  577. * @param {Function} cb - gets called with the result
  578. */
  579. unhide: isLoginRequired(async function add(session, songId, cb) {
  580. SongsModule.runJob("UNHIDE_SONG", { songId }, this)
  581. .then(() => {
  582. this.log("SUCCESS", "SONGS_UNHIDE", `User "${session.userId}" successfully unhid song "${songId}".`);
  583. return cb({
  584. status: "success",
  585. message: "Successfully unhid that song"
  586. });
  587. })
  588. .catch(async err => {
  589. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  590. this.log(
  591. "ERROR",
  592. "SONGS_UNHIDE",
  593. `Unhiding song "${songId}" failed for user ${session.userId}. "${err}"`
  594. );
  595. return cb({ status: "error", message: err });
  596. });
  597. }),
  598. /**
  599. * Verifies a song
  600. *
  601. * @param session
  602. * @param songId - the song id
  603. * @param cb
  604. */
  605. verify: isAdminRequired(async function add(session, songId, cb) {
  606. const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  607. async.waterfall(
  608. [
  609. next => {
  610. SongModel.findOne({ _id: songId }, next);
  611. },
  612. (song, next) => {
  613. if (!song) return next("This song is not in the database.");
  614. return next(null, song);
  615. },
  616. (song, next) => {
  617. const oldStatus = song.status;
  618. song.verifiedBy = session.userId;
  619. song.verifiedAt = Date.now();
  620. song.status = "verified";
  621. song.save(err => next(err, song, oldStatus));
  622. },
  623. (song, oldStatus, next) => {
  624. song.genres.forEach(genre => {
  625. PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
  626. .then(() => {})
  627. .catch(() => {});
  628. });
  629. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  630. next(null, song, oldStatus);
  631. }
  632. ],
  633. async (err, song, oldStatus) => {
  634. if (err) {
  635. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  636. this.log("ERROR", "SONGS_VERIFY", `User "${session.userId}" failed to verify song. "${err}"`);
  637. return cb({ status: "error", message: err });
  638. }
  639. this.log("SUCCESS", "SONGS_VERIFY", `User "${session.userId}" successfully verified song "${songId}".`);
  640. if (oldStatus === "hidden")
  641. CacheModule.runJob("PUB", {
  642. channel: "song.removedHiddenSong",
  643. value: song._id
  644. });
  645. CacheModule.runJob("PUB", {
  646. channel: "song.newVerifiedSong",
  647. value: song._id
  648. });
  649. CacheModule.runJob("PUB", {
  650. channel: "song.removedUnverifiedSong",
  651. value: song._id
  652. });
  653. return cb({
  654. status: "success",
  655. message: "Song has been verified successfully."
  656. });
  657. }
  658. );
  659. // TODO Check if video is in queue and Add the song to the appropriate stations
  660. }),
  661. /**
  662. * Un-verifies a song
  663. *
  664. * @param session
  665. * @param songId - the song id
  666. * @param cb
  667. */
  668. unverify: isAdminRequired(async function add(session, songId, cb) {
  669. const SongModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  670. async.waterfall(
  671. [
  672. next => {
  673. SongModel.findOne({ _id: songId }, next);
  674. },
  675. (song, next) => {
  676. if (!song) return next("This song is not in the database.");
  677. return next(null, song);
  678. },
  679. (song, next) => {
  680. song.status = "unverified";
  681. song.save(err => {
  682. next(err, song);
  683. });
  684. },
  685. (song, next) => {
  686. song.genres.forEach(genre => {
  687. PlaylistsModule.runJob("AUTOFILL_GENRE_PLAYLIST", { genre })
  688. .then(() => {})
  689. .catch(() => {});
  690. });
  691. SongsModule.runJob("UPDATE_SONG", { songId });
  692. next(null);
  693. }
  694. ],
  695. async err => {
  696. if (err) {
  697. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  698. this.log("ERROR", "SONGS_UNVERIFY", `User "${session.userId}" failed to verify song. "${err}"`);
  699. return cb({ status: "error", message: err });
  700. }
  701. this.log(
  702. "SUCCESS",
  703. "SONGS_UNVERIFY",
  704. `User "${session.userId}" successfully unverified song "${songId}".`
  705. );
  706. CacheModule.runJob("PUB", {
  707. channel: "song.newUnverifiedSong",
  708. value: songId
  709. });
  710. CacheModule.runJob("PUB", {
  711. channel: "song.removedVerifiedSong",
  712. value: songId
  713. });
  714. return cb({
  715. status: "success",
  716. message: "Song has been unverified successfully."
  717. });
  718. }
  719. );
  720. // TODO Check if video is in queue and Add the song to the appropriate stations
  721. }),
  722. /**
  723. * Requests a set of songs
  724. *
  725. * @param {object} session - the session object automatically added by the websocket
  726. * @param {string} url - the url of the the YouTube playlist
  727. * @param {boolean} musicOnly - whether to only get music from the playlist
  728. * @param {Function} cb - gets called with the result
  729. */
  730. requestSet: isLoginRequired(function requestSet(session, url, musicOnly, returnSongs, cb) {
  731. async.waterfall(
  732. [
  733. next => {
  734. YouTubeModule.runJob(
  735. "GET_PLAYLIST",
  736. {
  737. url,
  738. musicOnly
  739. },
  740. this
  741. )
  742. .then(res => {
  743. next(null, res.songs);
  744. })
  745. .catch(next);
  746. },
  747. (youtubeIds, next) => {
  748. let successful = 0;
  749. let songs = {};
  750. let failed = 0;
  751. let alreadyInDatabase = 0;
  752. if (youtubeIds.length === 0) next();
  753. async.eachOfLimit(
  754. youtubeIds,
  755. 1,
  756. (youtubeId, index, next) => {
  757. WSModule.runJob(
  758. "RUN_ACTION2",
  759. {
  760. session,
  761. namespace: "songs",
  762. action: "request",
  763. args: [youtubeId, returnSongs]
  764. },
  765. this
  766. )
  767. .then(res => {
  768. if (res.status === "success") successful += 1;
  769. else failed += 1;
  770. if (res.message === "This song is already in the database.") alreadyInDatabase += 1;
  771. if (res.song) songs[index] = res.song;
  772. else songs[index] = null;
  773. })
  774. .catch(() => {
  775. failed += 1;
  776. })
  777. .finally(() => {
  778. next();
  779. });
  780. },
  781. () => {
  782. if (returnSongs)
  783. songs = Object.keys(songs)
  784. .sort()
  785. .map(key => songs[key]);
  786. next(null, { successful, failed, alreadyInDatabase, songs });
  787. }
  788. );
  789. }
  790. ],
  791. async (err, response) => {
  792. if (err) {
  793. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  794. this.log(
  795. "ERROR",
  796. "REQUEST_SET",
  797. `Importing a YouTube playlist to be requested failed for user "${session.userId}". "${err}"`
  798. );
  799. return cb({ status: "error", message: err });
  800. }
  801. this.log(
  802. "SUCCESS",
  803. "REQUEST_SET",
  804. `Successfully imported a YouTube playlist to be requested for user "${session.userId}".`
  805. );
  806. return cb({
  807. status: "success",
  808. message: `Playlist is done importing. ${response.successful} were added succesfully, ${response.failed} failed (${response.alreadyInDatabase} were already in database)`,
  809. songs: returnSongs ? response.songs : null
  810. });
  811. }
  812. );
  813. }),
  814. /**
  815. * Likes a song
  816. *
  817. * @param session
  818. * @param youtubeId - the youtube id
  819. * @param cb
  820. */
  821. like: isLoginRequired(async function like(session, youtubeId, cb) {
  822. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  823. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  824. async.waterfall(
  825. [
  826. next => songModel.findOne({ youtubeId }, next),
  827. (song, next) => {
  828. if (!song) return next("No song found with that id.");
  829. return next(null, song);
  830. },
  831. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  832. (song, user, next) => {
  833. if (!user) return next("User does not exist.");
  834. return this.module
  835. .runJob(
  836. "RUN_ACTION2",
  837. {
  838. session,
  839. namespace: "playlists",
  840. action: "addSongToPlaylist",
  841. args: [false, youtubeId, user.likedSongsPlaylist]
  842. },
  843. this
  844. )
  845. .then(res => {
  846. if (res.status === "error") {
  847. if (res.message === "That song is already in the playlist")
  848. return next("You have already liked this song.");
  849. return next("Unable to add song to the 'Liked Songs' playlist.");
  850. }
  851. return next(null, song, user.dislikedSongsPlaylist);
  852. })
  853. .catch(err => next(err));
  854. },
  855. (song, dislikedSongsPlaylist, next) => {
  856. this.module
  857. .runJob(
  858. "RUN_ACTION2",
  859. {
  860. session,
  861. namespace: "playlists",
  862. action: "removeSongFromPlaylist",
  863. args: [youtubeId, dislikedSongsPlaylist]
  864. },
  865. this
  866. )
  867. .then(res => {
  868. if (res.status === "error")
  869. return next("Unable to remove song from the 'Disliked Songs' playlist.");
  870. return next(null, song);
  871. })
  872. .catch(err => next(err));
  873. },
  874. (song, next) => {
  875. SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, youtubeId })
  876. .then(ratings => next(null, song, ratings))
  877. .catch(err => next(err));
  878. }
  879. ],
  880. async (err, song, { likes, dislikes }) => {
  881. if (err) {
  882. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  883. this.log(
  884. "ERROR",
  885. "SONGS_LIKE",
  886. `User "${session.userId}" failed to like song ${youtubeId}. "${err}"`
  887. );
  888. return cb({ status: "error", message: err });
  889. }
  890. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  891. CacheModule.runJob("PUB", {
  892. channel: "song.like",
  893. value: JSON.stringify({
  894. youtubeId,
  895. userId: session.userId,
  896. likes,
  897. dislikes
  898. })
  899. });
  900. ActivitiesModule.runJob("ADD_ACTIVITY", {
  901. userId: session.userId,
  902. type: "song__like",
  903. payload: {
  904. message: `Liked song <youtubeId>${song.title} by ${song.artists.join(", ")}</youtubeId>`,
  905. youtubeId,
  906. thumbnail: song.thumbnail
  907. }
  908. });
  909. return cb({
  910. status: "success",
  911. message: "You have successfully liked this song."
  912. });
  913. }
  914. );
  915. }),
  916. /**
  917. * Dislikes a song
  918. *
  919. * @param session
  920. * @param youtubeId - the youtube id
  921. * @param cb
  922. */
  923. dislike: isLoginRequired(async function dislike(session, youtubeId, cb) {
  924. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  925. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  926. async.waterfall(
  927. [
  928. next => {
  929. songModel.findOne({ youtubeId }, next);
  930. },
  931. (song, next) => {
  932. if (!song) return next("No song found with that id.");
  933. return next(null, song);
  934. },
  935. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  936. (song, user, next) => {
  937. if (!user) return next("User does not exist.");
  938. return this.module
  939. .runJob(
  940. "RUN_ACTION2",
  941. {
  942. session,
  943. namespace: "playlists",
  944. action: "addSongToPlaylist",
  945. args: [false, youtubeId, user.dislikedSongsPlaylist]
  946. },
  947. this
  948. )
  949. .then(res => {
  950. if (res.status === "error") {
  951. if (res.message === "That song is already in the playlist")
  952. return next("You have already disliked this song.");
  953. return next("Unable to add song to the 'Disliked Songs' playlist.");
  954. }
  955. return next(null, song, user.likedSongsPlaylist);
  956. })
  957. .catch(err => next(err));
  958. },
  959. (song, likedSongsPlaylist, next) => {
  960. this.module
  961. .runJob(
  962. "RUN_ACTION2",
  963. {
  964. session,
  965. namespace: "playlists",
  966. action: "removeSongFromPlaylist",
  967. args: [youtubeId, likedSongsPlaylist]
  968. },
  969. this
  970. )
  971. .then(res => {
  972. if (res.status === "error")
  973. return next("Unable to remove song from the 'Liked Songs' playlist.");
  974. return next(null, song);
  975. })
  976. .catch(err => next(err));
  977. },
  978. (song, next) => {
  979. SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, youtubeId })
  980. .then(ratings => next(null, song, ratings))
  981. .catch(err => next(err));
  982. }
  983. ],
  984. async (err, song, { likes, dislikes }) => {
  985. if (err) {
  986. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  987. this.log(
  988. "ERROR",
  989. "SONGS_DISLIKE",
  990. `User "${session.userId}" failed to dislike song ${youtubeId}. "${err}"`
  991. );
  992. return cb({ status: "error", message: err });
  993. }
  994. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  995. CacheModule.runJob("PUB", {
  996. channel: "song.dislike",
  997. value: JSON.stringify({
  998. youtubeId,
  999. userId: session.userId,
  1000. likes,
  1001. dislikes
  1002. })
  1003. });
  1004. ActivitiesModule.runJob("ADD_ACTIVITY", {
  1005. userId: session.userId,
  1006. type: "song__dislike",
  1007. payload: {
  1008. message: `Disliked song <youtubeId>${song.title} by ${song.artists.join(", ")}</youtubeId>`,
  1009. youtubeId,
  1010. thumbnail: song.thumbnail
  1011. }
  1012. });
  1013. return cb({
  1014. status: "success",
  1015. message: "You have successfully disliked this song."
  1016. });
  1017. }
  1018. );
  1019. }),
  1020. /**
  1021. * Undislikes a song
  1022. *
  1023. * @param session
  1024. * @param youtubeId - the youtube id
  1025. * @param cb
  1026. */
  1027. undislike: isLoginRequired(async function undislike(session, youtubeId, cb) {
  1028. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1029. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  1030. async.waterfall(
  1031. [
  1032. next => {
  1033. songModel.findOne({ youtubeId }, next);
  1034. },
  1035. (song, next) => {
  1036. if (!song) return next("No song found with that id.");
  1037. return next(null, song);
  1038. },
  1039. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  1040. (song, user, next) => {
  1041. if (!user) return next("User does not exist.");
  1042. return this.module
  1043. .runJob(
  1044. "RUN_ACTION2",
  1045. {
  1046. session,
  1047. namespace: "playlists",
  1048. action: "removeSongFromPlaylist",
  1049. args: [youtubeId, user.dislikedSongsPlaylist]
  1050. },
  1051. this
  1052. )
  1053. .then(res => {
  1054. if (res.status === "error")
  1055. return next("Unable to remove song from the 'Disliked Songs' playlist.");
  1056. return next(null, song, user.likedSongsPlaylist);
  1057. })
  1058. .catch(err => next(err));
  1059. },
  1060. (song, likedSongsPlaylist, next) => {
  1061. this.module
  1062. .runJob(
  1063. "RUN_ACTION2",
  1064. {
  1065. session,
  1066. namespace: "playlists",
  1067. action: "removeSongFromPlaylist",
  1068. args: [youtubeId, likedSongsPlaylist]
  1069. },
  1070. this
  1071. )
  1072. .then(res => {
  1073. if (res.status === "error")
  1074. return next("Unable to remove song from the 'Liked Songs' playlist.");
  1075. return next(null, song);
  1076. })
  1077. .catch(err => next(err));
  1078. },
  1079. (song, next) => {
  1080. SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, youtubeId })
  1081. .then(ratings => next(null, song, ratings))
  1082. .catch(err => next(err));
  1083. }
  1084. ],
  1085. async (err, song, { likes, dislikes }) => {
  1086. if (err) {
  1087. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1088. this.log(
  1089. "ERROR",
  1090. "SONGS_UNDISLIKE",
  1091. `User "${session.userId}" failed to undislike song ${youtubeId}. "${err}"`
  1092. );
  1093. return cb({ status: "error", message: err });
  1094. }
  1095. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  1096. CacheModule.runJob("PUB", {
  1097. channel: "song.undislike",
  1098. value: JSON.stringify({
  1099. youtubeId,
  1100. userId: session.userId,
  1101. likes,
  1102. dislikes
  1103. })
  1104. });
  1105. ActivitiesModule.runJob("ADD_ACTIVITY", {
  1106. userId: session.userId,
  1107. type: "song__undislike",
  1108. payload: {
  1109. message: `Removed <youtubeId>${song.title} by ${song.artists.join(
  1110. ", "
  1111. )}</youtubeId> from your Disliked Songs`,
  1112. youtubeId,
  1113. thumbnail: song.thumbnail
  1114. }
  1115. });
  1116. return cb({
  1117. status: "success",
  1118. message: "You have successfully undisliked this song."
  1119. });
  1120. }
  1121. );
  1122. }),
  1123. /**
  1124. * Unlikes a song
  1125. *
  1126. * @param session
  1127. * @param youtubeId - the youtube id
  1128. * @param cb
  1129. */
  1130. unlike: isLoginRequired(async function unlike(session, youtubeId, cb) {
  1131. const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
  1132. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  1133. async.waterfall(
  1134. [
  1135. next => {
  1136. songModel.findOne({ youtubeId }, next);
  1137. },
  1138. (song, next) => {
  1139. if (!song) return next("No song found with that id.");
  1140. return next(null, song);
  1141. },
  1142. (song, next) => userModel.findOne({ _id: session.userId }, (err, user) => next(err, song, user)),
  1143. (song, user, next) => {
  1144. if (!user) return next("User does not exist.");
  1145. return this.module
  1146. .runJob(
  1147. "RUN_ACTION2",
  1148. {
  1149. session,
  1150. namespace: "playlists",
  1151. action: "removeSongFromPlaylist",
  1152. args: [youtubeId, user.dislikedSongsPlaylist]
  1153. },
  1154. this
  1155. )
  1156. .then(res => {
  1157. if (res.status === "error")
  1158. return next("Unable to remove song from the 'Disliked Songs' playlist.");
  1159. return next(null, song, user.likedSongsPlaylist);
  1160. })
  1161. .catch(err => next(err));
  1162. },
  1163. (song, likedSongsPlaylist, next) => {
  1164. this.module
  1165. .runJob(
  1166. "RUN_ACTION2",
  1167. {
  1168. session,
  1169. namespace: "playlists",
  1170. action: "removeSongFromPlaylist",
  1171. args: [youtubeId, likedSongsPlaylist]
  1172. },
  1173. this
  1174. )
  1175. .then(res => {
  1176. if (res.status === "error")
  1177. return next("Unable to remove song from the 'Liked Songs' playlist.");
  1178. return next(null, song);
  1179. })
  1180. .catch(err => next(err));
  1181. },
  1182. (song, next) => {
  1183. SongsModule.runJob("RECALCULATE_SONG_RATINGS", { songId: song._id, youtubeId })
  1184. .then(ratings => next(null, song, ratings))
  1185. .catch(err => next(err));
  1186. }
  1187. ],
  1188. async (err, song, { likes, dislikes }) => {
  1189. if (err) {
  1190. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1191. this.log(
  1192. "ERROR",
  1193. "SONGS_UNLIKE",
  1194. `User "${session.userId}" failed to unlike song ${youtubeId}. "${err}"`
  1195. );
  1196. return cb({ status: "error", message: err });
  1197. }
  1198. SongsModule.runJob("UPDATE_SONG", { songId: song._id });
  1199. CacheModule.runJob("PUB", {
  1200. channel: "song.unlike",
  1201. value: JSON.stringify({
  1202. youtubeId,
  1203. userId: session.userId,
  1204. likes,
  1205. dislikes
  1206. })
  1207. });
  1208. ActivitiesModule.runJob("ADD_ACTIVITY", {
  1209. userId: session.userId,
  1210. type: "song__unlike",
  1211. payload: {
  1212. message: `Removed <youtubeId>${song.title} by ${song.artists.join(
  1213. ", "
  1214. )}</youtubeId> from your Liked Songs`,
  1215. youtubeId,
  1216. thumbnail: song.thumbnail
  1217. }
  1218. });
  1219. return cb({
  1220. status: "success",
  1221. message: "You have successfully unliked this song."
  1222. });
  1223. }
  1224. );
  1225. }),
  1226. /**
  1227. * Gets song ratings
  1228. *
  1229. * @param session
  1230. * @param songId - the Musare song id
  1231. * @param cb
  1232. */
  1233. getSongRatings: isLoginRequired(async function getSongRatings(session, songId, cb) {
  1234. async.waterfall(
  1235. [
  1236. next => {
  1237. SongsModule.runJob("GET_SONG", { songId }, this)
  1238. .then(res => next(null, res.song))
  1239. .catch(next);
  1240. },
  1241. (song, next) => {
  1242. next(null, {
  1243. likes: song.likes,
  1244. dislikes: song.dislikes
  1245. });
  1246. }
  1247. ],
  1248. async (err, ratings) => {
  1249. if (err) {
  1250. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1251. this.log(
  1252. "ERROR",
  1253. "SONGS_GET_RATINGS",
  1254. `User "${session.userId}" failed to get ratings for ${songId}. "${err}"`
  1255. );
  1256. return cb({ status: "error", message: err });
  1257. }
  1258. const { likes, dislikes } = ratings;
  1259. return cb({
  1260. status: "success",
  1261. data: {
  1262. likes,
  1263. dislikes
  1264. }
  1265. });
  1266. }
  1267. );
  1268. }),
  1269. /**
  1270. * Gets user's own song ratings
  1271. *
  1272. * @param session
  1273. * @param youtubeId - the youtube id
  1274. * @param cb
  1275. */
  1276. getOwnSongRatings: isLoginRequired(async function getOwnSongRatings(session, youtubeId, cb) {
  1277. const playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this);
  1278. const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
  1279. async.waterfall(
  1280. [
  1281. next => songModel.findOne({ youtubeId }, next),
  1282. (song, next) => {
  1283. if (!song) return next("No song found with that id.");
  1284. return next(null);
  1285. },
  1286. next =>
  1287. playlistModel.findOne(
  1288. { createdBy: session.userId, displayName: "Liked Songs" },
  1289. (err, playlist) => {
  1290. if (err) return next(err);
  1291. if (!playlist) return next("'Liked Songs' playlist does not exist.");
  1292. let isLiked = false;
  1293. Object.values(playlist.songs).forEach(song => {
  1294. // song is found in 'liked songs' playlist
  1295. if (song.youtubeId === youtubeId) isLiked = true;
  1296. });
  1297. return next(null, isLiked);
  1298. }
  1299. ),
  1300. (isLiked, next) =>
  1301. playlistModel.findOne(
  1302. { createdBy: session.userId, displayName: "Disliked Songs" },
  1303. (err, playlist) => {
  1304. if (err) return next(err);
  1305. if (!playlist) return next("'Disliked Songs' playlist does not exist.");
  1306. const ratings = { isLiked, isDisliked: false };
  1307. Object.values(playlist.songs).forEach(song => {
  1308. // song is found in 'disliked songs' playlist
  1309. if (song.youtubeId === youtubeId) ratings.isDisliked = true;
  1310. });
  1311. return next(null, ratings);
  1312. }
  1313. )
  1314. ],
  1315. async (err, ratings) => {
  1316. if (err) {
  1317. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  1318. this.log(
  1319. "ERROR",
  1320. "SONGS_GET_OWN_RATINGS",
  1321. `User "${session.userId}" failed to get ratings for ${youtubeId}. "${err}"`
  1322. );
  1323. return cb({ status: "error", message: err });
  1324. }
  1325. const { isLiked, isDisliked } = ratings;
  1326. return cb({
  1327. status: "success",
  1328. data: {
  1329. youtubeId,
  1330. liked: isLiked,
  1331. disliked: isDisliked
  1332. }
  1333. });
  1334. }
  1335. );
  1336. })
  1337. };