apis.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. import config from "config";
  2. import async from "async";
  3. import axios from "axios";
  4. import isLoginRequired from "../hooks/loginRequired";
  5. import { hasPermission, useHasPermission } from "../hooks/hasPermission";
  6. // eslint-disable-next-line
  7. import moduleManager from "../../index";
  8. const UtilsModule = moduleManager.modules.utils;
  9. const WSModule = moduleManager.modules.ws;
  10. const YouTubeModule = moduleManager.modules.youtube;
  11. const SpotifyModule = moduleManager.modules.spotify;
  12. export default {
  13. /**
  14. * Fetches a list of songs from Youtube's API
  15. *
  16. * @param {object} session - user session
  17. * @param {string} query - the query we'll pass to youtubes api
  18. * @param {Function} cb - callback
  19. * @returns {{status: string, data: object}} - returns an object
  20. */
  21. searchYoutube: isLoginRequired(function searchYoutube(session, query, cb) {
  22. return YouTubeModule.runJob("SEARCH", { query }, this)
  23. .then(data => {
  24. this.log("SUCCESS", "APIS_SEARCH_YOUTUBE", `Searching YouTube successful with query "${query}".`);
  25. return cb({ status: "success", data });
  26. })
  27. .catch(async err => {
  28. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  29. this.log("ERROR", "APIS_SEARCH_YOUTUBE", `Searching youtube failed with query "${query}". "${err}"`);
  30. return cb({ status: "error", message: err });
  31. });
  32. }),
  33. /**
  34. * Fetches a specific page of search results from Youtube's API
  35. *
  36. * @param {object} session - user session
  37. * @param {string} query - the query we'll pass to youtubes api
  38. * @param {string} pageToken - identifies a specific page in the result set that should be retrieved
  39. * @param {Function} cb - callback
  40. * @returns {{status: string, data: object}} - returns an object
  41. */
  42. searchYoutubeForPage: isLoginRequired(function searchYoutubeForPage(session, query, pageToken, cb) {
  43. return YouTubeModule.runJob("SEARCH", { query, pageToken }, this)
  44. .then(data => {
  45. this.log(
  46. "SUCCESS",
  47. "APIS_SEARCH_YOUTUBE_FOR_PAGE",
  48. `Searching YouTube successful with query "${query}".`
  49. );
  50. return cb({ status: "success", data });
  51. })
  52. .catch(async err => {
  53. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  54. this.log(
  55. "ERROR",
  56. "APIS_SEARCH_YOUTUBE_FOR_PAGE",
  57. `Searching youtube failed with query "${query}". "${err}"`
  58. );
  59. return cb({ status: "error", message: err });
  60. });
  61. }),
  62. /**
  63. * Gets Discogs data
  64. *
  65. * @param session
  66. * @param query - the query
  67. * @param {Function} cb
  68. */
  69. searchDiscogs: useHasPermission("apis.searchDiscogs", function searchDiscogs(session, query, page, cb) {
  70. async.waterfall(
  71. [
  72. next => {
  73. const options = {
  74. params: { q: query, per_page: 20, page },
  75. headers: {
  76. "User-Agent": "Request",
  77. Authorization: `Discogs key=${config.get("apis.discogs.client")}, secret=${config.get(
  78. "apis.discogs.secret"
  79. )}`
  80. }
  81. };
  82. axios
  83. .get("https://api.discogs.com/database/search", options)
  84. .then(res => next(null, res.data))
  85. .catch(err => next(err));
  86. }
  87. ],
  88. async (err, body) => {
  89. if (err) {
  90. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  91. this.log(
  92. "ERROR",
  93. "APIS_SEARCH_DISCOGS",
  94. `Searching discogs failed with query "${query}". "${err}"`
  95. );
  96. return cb({ status: "error", message: err });
  97. }
  98. this.log(
  99. "SUCCESS",
  100. "APIS_SEARCH_DISCOGS",
  101. `User "${session.userId}" searched Discogs succesfully for query "${query}".`
  102. );
  103. return cb({
  104. status: "success",
  105. data: {
  106. results: body.results,
  107. pages: body.pagination.pages
  108. }
  109. });
  110. }
  111. );
  112. }),
  113. /**
  114. * Gets alternative media sources for list of Spotify tracks (media sources)
  115. *
  116. * @param session
  117. * @param trackId - the trackId
  118. * @param {Function} cb
  119. */
  120. getAlternativeMediaSourcesForTracks: useHasPermission(
  121. "spotify.getAlternativeMediaSourcesForTracks",
  122. function getAlternativeMediaSourcesForTracks(session, mediaSources, collectAlternativeMediaSourcesOrigins, cb) {
  123. async.waterfall(
  124. [
  125. next => {
  126. if (!mediaSources) {
  127. next("Invalid mediaSources provided.");
  128. return;
  129. }
  130. next();
  131. },
  132. next => {
  133. this.keepLongJob();
  134. this.publishProgress({
  135. status: "started",
  136. title: "Getting alternative media sources for Spotify tracks",
  137. message: "Starting up",
  138. id: this.toString()
  139. });
  140. SpotifyModule.runJob(
  141. "GET_ALTERNATIVE_MEDIA_SOURCES_FOR_TRACKS",
  142. {
  143. mediaSources,
  144. collectAlternativeMediaSourcesOrigins
  145. },
  146. this
  147. )
  148. .then(() => next())
  149. .catch(next);
  150. }
  151. ],
  152. async err => {
  153. if (err) {
  154. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  155. this.log(
  156. "ERROR",
  157. "APIS_GET_ALTERNATIVE_SOURCES",
  158. `Getting alternative sources failed for "${mediaSources.join(", ")}". "${err}"`
  159. );
  160. return cb({ status: "error", message: err });
  161. }
  162. this.log(
  163. "SUCCESS",
  164. "APIS_GET_ALTERNATIVE_SOURCES",
  165. `User "${session.userId}" started getting alternatives for "${mediaSources.join(", ")}".`
  166. );
  167. return cb({
  168. status: "success"
  169. });
  170. }
  171. );
  172. }
  173. ),
  174. /**
  175. * Gets alternative album sources (such as YouTube playlists) for a list of Spotify album ids
  176. *
  177. * @param session
  178. * @param trackId - the trackId
  179. * @param {Function} cb
  180. */
  181. getAlternativeAlbumSourcesForAlbums: useHasPermission(
  182. "spotify.getAlternativeAlbumSourcesForAlbums",
  183. function getAlternativeAlbumSourcesForAlbums(session, albumIds, collectAlternativeAlbumSourcesOrigins, cb) {
  184. async.waterfall(
  185. [
  186. next => {
  187. if (!albumIds) {
  188. next("Invalid albumIds provided.");
  189. return;
  190. }
  191. next();
  192. },
  193. next => {
  194. this.keepLongJob();
  195. this.publishProgress({
  196. status: "started",
  197. title: "Getting alternative album sources for Spotify albums",
  198. message: "Starting up",
  199. id: this.toString()
  200. });
  201. SpotifyModule.runJob(
  202. "GET_ALTERNATIVE_ALBUM_SOURCES_FOR_ALBUMS",
  203. { albumIds, collectAlternativeAlbumSourcesOrigins },
  204. this
  205. )
  206. .then(() => next())
  207. .catch(next);
  208. }
  209. ],
  210. async err => {
  211. if (err) {
  212. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  213. this.log(
  214. "ERROR",
  215. "APIS_GET_ALTERNATIVE_ALBUM_SOURCES",
  216. `Getting alternative album sources failed for "${albumIds.join(", ")}". "${err}"`
  217. );
  218. return cb({ status: "error", message: err });
  219. }
  220. this.log(
  221. "SUCCESS",
  222. "APIS_GET_ALTERNATIVE_ALBUM_SOURCES",
  223. `User "${session.userId}" started getting alternative album spirces for "${albumIds.join(
  224. ", "
  225. )}".`
  226. );
  227. return cb({
  228. status: "success"
  229. });
  230. }
  231. );
  232. }
  233. ),
  234. /**
  235. * Gets a list of alternative artist sources (such as YouTube channels) for a list of Spotify artist ids
  236. *
  237. * @param session
  238. * @param trackId - the trackId
  239. * @param {Function} cb
  240. */
  241. getAlternativeArtistSourcesForArtists: useHasPermission(
  242. "spotify.getAlternativeArtistSourcesForArtists",
  243. function getAlternativeArtistSourcesForArtists(session, artistIds, collectAlternativeArtistSourcesOrigins, cb) {
  244. async.waterfall(
  245. [
  246. next => {
  247. if (!artistIds) {
  248. next("Invalid artistIds provided.");
  249. return;
  250. }
  251. next();
  252. },
  253. next => {
  254. this.keepLongJob();
  255. this.publishProgress({
  256. status: "started",
  257. title: "Getting alternative artist sources for Spotify artists",
  258. message: "Starting up",
  259. id: this.toString()
  260. });
  261. SpotifyModule.runJob(
  262. "GET_ALTERNATIVE_ARTIST_SOURCES_FOR_ARTISTS",
  263. {
  264. artistIds,
  265. collectAlternativeArtistSourcesOrigins
  266. },
  267. this
  268. )
  269. .then(() => next())
  270. .catch(next);
  271. }
  272. ],
  273. async err => {
  274. if (err) {
  275. err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
  276. this.log(
  277. "ERROR",
  278. "APIS_GET_ALTERNATIVE_ARTIST_SOURCES",
  279. `Getting alternative artist sources failed for "${artistIds.join(", ")}". "${err}"`
  280. );
  281. return cb({ status: "error", message: err });
  282. }
  283. this.log(
  284. "SUCCESS",
  285. "APIS_GET_ALTERNATIVE_ARTIST_SOURCES",
  286. `User "${session.userId}" started getting alternative artist spirces for "${artistIds.join(
  287. ", "
  288. )}".`
  289. );
  290. return cb({
  291. status: "success"
  292. });
  293. }
  294. );
  295. }
  296. ),
  297. /**
  298. * Joins a room
  299. *
  300. * @param {object} session - user session
  301. * @param {string} room - the room to join
  302. * @param {Function} cb - callback
  303. */
  304. joinRoom(session, room, cb) {
  305. const roomName = room.split(".")[0];
  306. // const roomId = room.split(".")[1];
  307. const rooms = {
  308. home: null,
  309. news: null,
  310. profile: null,
  311. "view-media": null,
  312. "manage-station": null,
  313. // "manage-station": "stations.view",
  314. "edit-song": "songs.update",
  315. "edit-songs": "songs.update",
  316. "import-album": "songs.update",
  317. // "edit-playlist": "playlists.update",
  318. "view-report": "reports.get",
  319. "edit-user": "users.update",
  320. "view-api-request": "youtube.getApiRequest",
  321. "view-punishment": "punishments.get"
  322. };
  323. const join = (status, error) => {
  324. if (status === "success")
  325. WSModule.runJob("SOCKET_JOIN_ROOM", {
  326. socketId: session.socketId,
  327. room
  328. })
  329. .then(() => cb({ status: "success", message: "Successfully joined room." }))
  330. .catch(err => join("error", err.message));
  331. else {
  332. this.log("ERROR", `Joining room failed: ${error}`);
  333. cb({ status: "error", message: error });
  334. }
  335. };
  336. if (rooms[roomName] === null) join("success");
  337. else if (rooms[roomName])
  338. hasPermission(rooms[roomName], session)
  339. .then(() => join("success"))
  340. .catch(err => join("error", err));
  341. else join("error", "Room not found");
  342. },
  343. /**
  344. * Leaves a room
  345. *
  346. * @param {object} session - user session
  347. * @param {string} room - the room to leave
  348. * @param {Function} cb - callback
  349. */
  350. leaveRoom(session, room, cb) {
  351. if (
  352. room === "home" ||
  353. room.startsWith("profile.") ||
  354. room.startsWith("manage-station.") ||
  355. room.startsWith("edit-song.") ||
  356. room.startsWith("view-report.") ||
  357. room === "import-album" ||
  358. room === "edit-songs"
  359. ) {
  360. WSModule.runJob("SOCKET_LEAVE_ROOM", {
  361. socketId: session.socketId,
  362. room
  363. })
  364. .then(() => {})
  365. .catch(err => {
  366. this.log("ERROR", `Leaving room failed: ${err.message}`);
  367. });
  368. }
  369. cb({ status: "success", message: "Successfully left room." });
  370. },
  371. /**
  372. * Joins an admin room
  373. *
  374. * @param {object} session - user session
  375. * @param {string} page - the admin room to join
  376. * @param {Function} cb - callback
  377. */
  378. joinAdminRoom(session, page, cb) {
  379. if (
  380. page === "songs" ||
  381. page === "stations" ||
  382. page === "reports" ||
  383. page === "news" ||
  384. page === "playlists" ||
  385. page === "users" ||
  386. page === "statistics" ||
  387. page === "punishments" ||
  388. page === "youtube" ||
  389. page === "youtubeVideos" ||
  390. page === "youtubeChannels" ||
  391. (config.get("experimental.soundcloud") && (page === "soundcloud" || page === "soundcloudTracks")) ||
  392. page === "import" ||
  393. page === "dataRequests"
  394. ) {
  395. hasPermission(`admin.view.${page}`, session.userId)
  396. .then(() =>
  397. WSModule.runJob("SOCKET_LEAVE_ROOMS", { socketId: session.socketId }).then(() => {
  398. WSModule.runJob(
  399. "SOCKET_JOIN_ROOM",
  400. {
  401. socketId: session.socketId,
  402. room: `admin.${page}`
  403. },
  404. this
  405. ).then(() => cb({ status: "success", message: "Successfully joined admin room." }));
  406. })
  407. )
  408. .catch(() => cb({ status: "error", message: "Failed to join admin room." }));
  409. }
  410. },
  411. /**
  412. * Leaves all rooms
  413. *
  414. * @param {object} session - user session
  415. * @param {Function} cb - callback
  416. */
  417. leaveRooms(session, cb) {
  418. WSModule.runJob("SOCKET_LEAVE_ROOMS", { socketId: session.socketId });
  419. cb({ status: "success", message: "Successfully left all rooms." });
  420. },
  421. /**
  422. * Returns current date
  423. *
  424. * @param {object} session - user session
  425. * @param {Function} cb - callback
  426. */
  427. ping(session, cb) {
  428. cb({ status: "success", message: "Successfully pinged.", data: { date: Date.now() } });
  429. }
  430. };