stations.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  1. 'use strict';
  2. const async = require('async'),
  3. request = require('request'),
  4. config = require('config'),
  5. _ = require('underscore')._;
  6. const io = require('../io');
  7. const db = require('../db');
  8. const cache = require('../cache');
  9. const notifications = require('../notifications');
  10. const utils = require('../utils');
  11. const logger = require('../logger');
  12. const stations = require('../stations');
  13. const songs = require('../songs');
  14. const hooks = require('./hooks');
  15. let userList = {};
  16. let usersPerStation = {};
  17. let usersPerStationCount = {};
  18. setInterval(() => {
  19. let stationsCountUpdated = [];
  20. let stationsUpdated = [];
  21. let oldUsersPerStation = usersPerStation;
  22. usersPerStation = {};
  23. let oldUsersPerStationCount = usersPerStationCount;
  24. usersPerStationCount = {};
  25. async.each(Object.keys(userList), function(socketId, next) {
  26. let socket = utils.socketFromSession(socketId);
  27. let stationId = userList[socketId];
  28. if (!socket || Object.keys(socket.rooms).indexOf(`station.${stationId}`) === -1) {
  29. if (stationsCountUpdated.indexOf(stationId) === -1) stationsCountUpdated.push(stationId);
  30. if (stationsUpdated.indexOf(stationId) === -1) stationsUpdated.push(stationId);
  31. delete userList[socketId];
  32. return next();
  33. }
  34. if (!usersPerStationCount[stationId]) usersPerStationCount[stationId] = 0;
  35. usersPerStationCount[stationId]++;
  36. if (!usersPerStation[stationId]) usersPerStation[stationId] = [];
  37. async.waterfall([
  38. (next) => {
  39. if (!socket.session || !socket.session.sessionId) return next('No session found.');
  40. cache.hget('sessions', socket.session.sessionId, next);
  41. },
  42. (session, next) => {
  43. if (!session) return next('Session not found.');
  44. db.models.user.findOne({_id: session.userId}, next);
  45. },
  46. (user, next) => {
  47. if (!user) return next('User not found.');
  48. if (usersPerStation[stationId].indexOf(user.username) !== -1) return next('User already in the list.');
  49. next(null, user.username);
  50. }
  51. ], (err, username) => {
  52. if (!err) {
  53. usersPerStation[stationId].push(username);
  54. }
  55. next();
  56. });
  57. //TODO Code to show users
  58. }, (err) => {
  59. for (let stationId in usersPerStationCount) {
  60. if (oldUsersPerStationCount[stationId] !== usersPerStationCount[stationId]) {
  61. if (stationsCountUpdated.indexOf(stationId) === -1) stationsCountUpdated.push(stationId);
  62. }
  63. }
  64. for (let stationId in usersPerStation) {
  65. if (_.difference(usersPerStation[stationId], oldUsersPerStation[stationId]).length > 0 || _.difference(oldUsersPerStation[stationId], usersPerStation[stationId]).length > 0) {
  66. if (stationsUpdated.indexOf(stationId) === -1) stationsUpdated.push(stationId);
  67. }
  68. }
  69. stationsCountUpdated.forEach((stationId) => {
  70. console.log("Updating count of ", stationId);
  71. cache.pub('station.updateUserCount', stationId);
  72. });
  73. stationsUpdated.forEach((stationId) => {
  74. console.log("Updating ", stationId);
  75. cache.pub('station.updateUsers', stationId);
  76. });
  77. //console.log("Userlist", usersPerStation);
  78. });
  79. }, 3000);
  80. cache.sub('station.updateUsers', stationId => {
  81. let list = usersPerStation[stationId] || [];
  82. utils.emitToRoom(`station.${stationId}`, "event:users.updated", list);
  83. });
  84. cache.sub('station.updateUserCount', stationId => {
  85. let count = usersPerStationCount[stationId] || 0;
  86. utils.emitToRoom(`station.${stationId}`, "event:userCount.updated", count);
  87. stations.getStation(stationId, (err, station) => {
  88. if (station.privacy === 'public') utils.emitToRoom('home', "event:userCount.updated", stationId, count);
  89. else {
  90. let sockets = utils.getRoomSockets('home');
  91. for (let socketId in sockets) {
  92. let socket = sockets[socketId];
  93. let session = sockets[socketId].session;
  94. if (session.sessionId) {
  95. cache.hget('sessions', session.sessionId, (err, session) => {
  96. if (!err && session) {
  97. db.models.user.findOne({_id: session.userId}, (err, user) => {
  98. if (user.role === 'admin') socket.emit("event:userCount.updated", stationId, count);
  99. else if (station.type === "community" && station.owner === session.userId) socket.emit("event:userCount.updated", stationId, count);
  100. });
  101. }
  102. });
  103. }
  104. }
  105. }
  106. })
  107. });
  108. cache.sub('station.updatePartyMode', data => {
  109. utils.emitToRoom(`station.${data.stationId}`, "event:partyMode.updated", data.partyMode);
  110. });
  111. cache.sub('privatePlaylist.selected', data => {
  112. utils.emitToRoom(`station.${data.stationId}`, "event:privatePlaylist.selected", data.playlistId);
  113. });
  114. cache.sub('station.pause', stationId => {
  115. utils.emitToRoom(`station.${stationId}`, "event:stations.pause");
  116. });
  117. cache.sub('station.resume', stationId => {
  118. stations.getStation(stationId, (err, station) => {
  119. utils.emitToRoom(`station.${stationId}`, "event:stations.resume", { timePaused: station.timePaused });
  120. });
  121. });
  122. cache.sub('station.queueUpdate', stationId => {
  123. stations.getStation(stationId, (err, station) => {
  124. if (!err) utils.emitToRoom(`station.${stationId}`, "event:queue.update", station.queue);
  125. });
  126. });
  127. cache.sub('station.voteSkipSong', stationId => {
  128. utils.emitToRoom(`station.${stationId}`, "event:song.voteSkipSong");
  129. });
  130. cache.sub('station.remove', stationId => {
  131. utils.emitToRoom('admin.stations', 'event:admin.station.removed', stationId);
  132. });
  133. cache.sub('station.create', stationId => {
  134. stations.initializeStation(stationId, (err, station) => {
  135. station.userCount = usersPerStationCount[stationId] || 0;
  136. if (err) console.error(err);
  137. utils.emitToRoom('admin.stations', 'event:admin.station.added', station);
  138. // TODO If community, check if on whitelist
  139. if (station.privacy === 'public') utils.emitToRoom('home', "event:stations.created", station);
  140. else {
  141. let sockets = utils.getRoomSockets('home');
  142. for (let socketId in sockets) {
  143. let socket = sockets[socketId];
  144. let session = sockets[socketId].session;
  145. if (session.sessionId) {
  146. cache.hget('sessions', session.sessionId, (err, session) => {
  147. if (!err && session) {
  148. db.models.user.findOne({_id: session.userId}, (err, user) => {
  149. if (user.role === 'admin') socket.emit("event:stations.created", station);
  150. else if (station.type === "community" && station.owner === session.userId) socket.emit("event:stations.created", station);
  151. });
  152. }
  153. });
  154. }
  155. }
  156. }
  157. });
  158. });
  159. module.exports = {
  160. /**
  161. * Get a list of all the stations
  162. *
  163. * @param session
  164. * @param cb
  165. * @return {{ status: String, stations: Array }}
  166. */
  167. index: (session, cb) => {
  168. async.waterfall([
  169. (next) => {
  170. cache.hgetall('stations', next);
  171. },
  172. (stations, next) => {
  173. let resultStations = [];
  174. for (let id in stations) {
  175. resultStations.push(stations[id]);
  176. }
  177. next(null, stations);
  178. },
  179. (stations, next) => {
  180. let resultStations = [];
  181. async.each(stations, (station, next) => {
  182. async.waterfall([
  183. (next) => {
  184. if (station.privacy === 'public') return next(true);
  185. if (!session.sessionId) return next(`Insufficient permissions.`);
  186. cache.hget('sessions', session.sessionId, next);
  187. },
  188. (session, next) => {
  189. if (!session) return next(`Insufficient permissions.`);
  190. db.models.user.findOne({_id: session.userId}, next);
  191. },
  192. (user, next) => {
  193. if (!user) return next(`Insufficient permissions.`);
  194. if (user.role === 'admin') return next(true);
  195. if (station.type === 'official') return next(`Insufficient permissions.`);
  196. if (station.owner === session.userId) return next(true);
  197. next(`Insufficient permissions.`);
  198. }
  199. ], (err) => {
  200. station.userCount = usersPerStationCount[station._id] || 0;
  201. if (err === true) resultStations.push(station);
  202. next();
  203. });
  204. }, () => {
  205. next(null, resultStations);
  206. });
  207. }
  208. ], (err, stations) => {
  209. if (err) {
  210. err = utils.getError(err);
  211. logger.error("STATIONS_INDEX", `Indexing stations failed. "${err}"`);
  212. return cb({'status': 'failure', 'message': err});
  213. }
  214. logger.success("STATIONS_INDEX", `Indexing stations successful.`);
  215. return cb({'status': 'success', 'stations': stations});
  216. });
  217. },
  218. /**
  219. * Finds a station by name
  220. *
  221. * @param session
  222. * @param stationName - the station name
  223. * @param cb
  224. */
  225. findByName: (session, stationName, cb) => {
  226. async.waterfall([
  227. (next) => {
  228. stations.getStationByName(stationName, next);
  229. },
  230. (station, next) => {
  231. if (!station) return next('Station not found.');
  232. next(null, station);
  233. }
  234. ], (err, station) => {
  235. if (err) {
  236. err = utils.getError(err);
  237. logger.error("STATIONS_FIND_BY_NAME", `Finding station "${stationName}" failed. "${err}"`);
  238. return cb({'status': 'failure', 'message': err});
  239. }
  240. logger.success("STATIONS_FIND_BY_NAME", `Found station "${stationName}" successfully.`);
  241. cb({status: 'success', data: station});
  242. });
  243. },
  244. /**
  245. * Gets the official playlist for a station
  246. *
  247. * @param session
  248. * @param stationId - the station id
  249. * @param cb
  250. */
  251. getPlaylist: (session, stationId, cb) => {
  252. async.waterfall([
  253. (next) => {
  254. stations.getStation(stationId, next);
  255. },
  256. (station, next) => {
  257. if (!station) return next('Station not found.');
  258. if (station.type !== 'official') return next('This is not an official station.');
  259. next();
  260. },
  261. (next) => {
  262. cache.hget("officialPlaylists", stationId, next);
  263. },
  264. (playlist, next) => {
  265. if (!playlist) return next('Playlist not found.');
  266. next(null, playlist);
  267. }
  268. ], (err, playlist) => {
  269. if (err) {
  270. err = utils.getError(err);
  271. logger.error("STATIONS_GET_PLAYLIST", `Getting playlist for station "${stationId}" failed. "${err}"`);
  272. return cb({'status': 'failure', 'message': err});
  273. }
  274. logger.success("STATIONS_GET_PLAYLIST", `Got playlist for station "${stationId}" successfully.`);
  275. cb({status: 'success', data: playlist.songs})
  276. });
  277. },
  278. /**
  279. * Joins the station by its name
  280. *
  281. * @param session
  282. * @param stationName - the station name
  283. * @param cb
  284. * @return {{ status: String, userCount: Integer }}
  285. */
  286. join: (session, stationName, cb) => {
  287. async.waterfall([
  288. (next) => {
  289. stations.getStationByName(stationName, next);
  290. },
  291. (station, next) => {
  292. if (!station) return next('Station not found.');
  293. async.waterfall([
  294. (next) => {
  295. if (station.privacy !== 'private') return next(true);
  296. if (!session.userId) return next('An error occurred while joining the station.');
  297. next();
  298. },
  299. (next) => {
  300. db.models.user.findOne({_id: session.userId}, next);
  301. },
  302. (user, next) => {
  303. if (!user) return next('An error occurred while joining the station.');
  304. if (user.role === 'admin') return next(true);
  305. if (station.type === 'official') return next('An error occurred while joining the station.');
  306. if (station.owner === session.userId) return next(true);
  307. next('An error occurred while joining the station.');
  308. }
  309. ], (err) => {
  310. if (err === true) return next(null, station);
  311. next(utils.getError(err));
  312. });
  313. },
  314. (station, next) => {
  315. utils.socketJoinRoom(session.socketId, `station.${station._id}`);
  316. let data = {
  317. _id: station._id,
  318. type: station.type,
  319. currentSong: station.currentSong,
  320. startedAt: station.startedAt,
  321. paused: station.paused,
  322. timePaused: station.timePaused,
  323. description: station.description,
  324. displayName: station.displayName,
  325. privacy: station.privacy,
  326. partyMode: station.partyMode,
  327. owner: station.owner,
  328. privatePlaylist: station.privatePlaylist
  329. };
  330. userList[session.socketId] = station._id;
  331. next(null, data);
  332. },
  333. (data, next) => {
  334. data.userCount = usersPerStationCount[data._id] || 0;
  335. data.users = usersPerStation[data._id] || [];
  336. if (!data.currentSong || !data.currentSong.title) return next(null, data);
  337. utils.socketJoinSongRoom(session.socketId, `song.${data.currentSong.songId}`);
  338. data.currentSong.skipVotes = data.currentSong.skipVotes.length;
  339. songs.getSong(data.currentSong.songId, (err, song) => {
  340. if (!err && song) {
  341. data.currentSong.likes = song.likes;
  342. data.currentSong.dislikes = song.dislikes;
  343. } else {
  344. data.currentSong.likes = -1;
  345. data.currentSong.dislikes = -1;
  346. }
  347. next(null, data);
  348. });
  349. }
  350. ], (err, data) => {
  351. if (err) {
  352. err = utils.getError(err);
  353. logger.error("STATIONS_JOIN", `Joining station "${stationName}" failed. "${err}"`);
  354. return cb({'status': 'failure', 'message': err});
  355. }
  356. logger.success("STATIONS_JOIN", `Joined station "${data._id}" successfully.`);
  357. cb({status: 'success', data});
  358. });
  359. },
  360. /**
  361. * Votes to skip a station
  362. *
  363. * @param session
  364. * @param stationId - the station id
  365. * @param cb
  366. * @param userId
  367. */
  368. voteSkip: hooks.loginRequired((session, stationId, cb, userId) => {
  369. async.waterfall([
  370. (next) => {
  371. stations.getStation(stationId, next);
  372. },
  373. (station, next) => {
  374. if (!station) return next('Station not found.');
  375. if (!station.currentSong) return next('There is currently no song to skip.');
  376. if (station.currentSong.skipVotes.indexOf(userId) !== -1) return next('You have already voted to skip this song.');
  377. next(null, station);
  378. },
  379. (station, next) => {
  380. db.models.station.update({_id: stationId}, {$push: {"currentSong.skipVotes": userId}}, next)
  381. },
  382. (res, next) => {
  383. stations.updateStation(stationId, next);
  384. },
  385. (station, next) => {
  386. if (!station) return next('Station not found.');
  387. next(null, station);
  388. }
  389. ], (err, station) => {
  390. if (err) {
  391. err = utils.getError(err);
  392. logger.error("STATIONS_VOTE_SKIP", `Vote skipping station "${stationId}" failed. "${err}"`);
  393. return cb({'status': 'failure', 'message': err});
  394. }
  395. logger.success("STATIONS_VOTE_SKIP", `Vote skipping "${stationId}" successful.`);
  396. cache.pub('station.voteSkipSong', stationId);
  397. if (station.currentSong && station.currentSong.skipVotes.length >= 3) stations.skipStation(stationId)();
  398. cb({ status: 'success', message: 'Successfully voted to skip the song.' });
  399. });
  400. }),
  401. /**
  402. * Force skips a station
  403. *
  404. * @param session
  405. * @param stationId - the station id
  406. * @param cb
  407. */
  408. forceSkip: hooks.ownerRequired((session, stationId, cb) => {
  409. async.waterfall([
  410. (next) => {
  411. stations.getStation(stationId, next);
  412. },
  413. (station, next) => {
  414. if (!station) return next('Station not found.');
  415. next();
  416. }
  417. ], (err) => {
  418. if (err) {
  419. err = utils.getError(err);
  420. logger.error("STATIONS_FORCE_SKIP", `Force skipping station "${stationId}" failed. "${err}"`);
  421. return cb({'status': 'failure', 'message': err});
  422. }
  423. notifications.unschedule(`stations.nextSong?id=${stationId}`);
  424. stations.skipStation(stationId)();
  425. logger.success("STATIONS_FORCE_SKIP", `Force skipped station "${stationId}" successfully.`);
  426. return cb({'status': 'success', 'message': 'Successfully skipped station.'});
  427. });
  428. }),
  429. /**
  430. * Leaves the user's current station
  431. *
  432. * @param session
  433. * @param stationId
  434. * @param cb
  435. * @return {{ status: String, userCount: Integer }}
  436. */
  437. leave: (session, stationId, cb) => {
  438. async.waterfall([
  439. (next) => {
  440. stations.getStation(stationId, next);
  441. },
  442. (station, next) => {
  443. if (!station) return next('Station not found.');
  444. next();
  445. },
  446. (next) => {
  447. cache.client.hincrby('station.userCounts', stationId, -1, next);
  448. }
  449. ], (err, userCount) => {
  450. if (err) {
  451. err = utils.getError(err);
  452. logger.error("STATIONS_LEAVE", `Leaving station "${stationId}" failed. "${err}"`);
  453. return cb({'status': 'failure', 'message': err});
  454. }
  455. logger.success("STATIONS_LEAVE", `Left station "${stationId}" successfully.`);
  456. utils.socketLeaveRooms(session);
  457. delete userList[session.socketId];
  458. return cb({'status': 'success', 'message': 'Successfully left station.', userCount});
  459. });
  460. },
  461. /**
  462. * Updates a station's name
  463. *
  464. * @param session
  465. * @param stationId - the station id
  466. * @param newName - the new station name
  467. * @param cb
  468. */
  469. updateName: hooks.ownerRequired((session, stationId, newName, cb) => {
  470. async.waterfall([
  471. (next) => {
  472. db.models.station.update({_id: stationId}, {$set: {name: newName}}, next);
  473. },
  474. (res, next) => {
  475. stations.updateStation(stationId, next);
  476. }
  477. ], (err) => {
  478. if (err) {
  479. err = utils.getError(err);
  480. logger.error("STATIONS_UPDATE_DISPLAY_NAME", `Updating station "${stationId}" displayName to "${newName}" failed. "${err}"`);
  481. return cb({'status': 'failure', 'message': err});
  482. }
  483. logger.success("STATIONS_UPDATE_DISPLAY_NAME", `Updated station "${stationId}" displayName to "${newName}" successfully.`);
  484. return cb({'status': 'success', 'message': 'Successfully updated the name.'});
  485. });
  486. }),
  487. /**
  488. * Updates a station's display name
  489. *
  490. * @param session
  491. * @param stationId - the station id
  492. * @param newDisplayName - the new station display name
  493. * @param cb
  494. */
  495. updateDisplayName: hooks.ownerRequired((session, stationId, newDisplayName, cb) => {
  496. async.waterfall([
  497. (next) => {
  498. db.models.station.update({_id: stationId}, {$set: {displayName: newDisplayName}}, next);
  499. },
  500. (res, next) => {
  501. stations.updateStation(stationId, next);
  502. }
  503. ], (err) => {
  504. if (err) {
  505. err = utils.getError(err);
  506. logger.error("STATIONS_UPDATE_DISPLAY_NAME", `Updating station "${stationId}" displayName to "${newDisplayName}" failed. "${err}"`);
  507. return cb({'status': 'failure', 'message': err});
  508. }
  509. logger.success("STATIONS_UPDATE_DISPLAY_NAME", `Updated station "${stationId}" displayName to "${newDisplayName}" successfully.`);
  510. return cb({'status': 'success', 'message': 'Successfully updated the display name.'});
  511. });
  512. }),
  513. /**
  514. * Updates a station's description
  515. *
  516. * @param session
  517. * @param stationId - the station id
  518. * @param newDescription - the new station description
  519. * @param cb
  520. */
  521. updateDescription: hooks.ownerRequired((session, stationId, newDescription, cb) => {
  522. async.waterfall([
  523. (next) => {
  524. db.models.station.update({_id: stationId}, {$set: {description: newDescription}}, next);
  525. },
  526. (res, next) => {
  527. stations.updateStation(stationId, next);
  528. }
  529. ], (err) => {
  530. if (err) {
  531. err = utils.getError(err);
  532. logger.error("STATIONS_UPDATE_DESCRIPTION", `Updating station "${stationId}" description to "${newDescription}" failed. "${err}"`);
  533. return cb({'status': 'failure', 'message': err});
  534. }
  535. logger.success("STATIONS_UPDATE_DESCRIPTION", `Updated station "${stationId}" description to "${newDescription}" successfully.`);
  536. return cb({'status': 'success', 'message': 'Successfully updated the description.'});
  537. });
  538. }),
  539. /**
  540. * Updates a station's privacy
  541. *
  542. * @param session
  543. * @param stationId - the station id
  544. * @param newPrivacy - the new station privacy
  545. * @param cb
  546. */
  547. updatePrivacy: hooks.ownerRequired((session, stationId, newPrivacy, cb) => {
  548. async.waterfall([
  549. (next) => {
  550. db.models.station.update({_id: stationId}, {$set: {privacy: newPrivacy}}, next);
  551. },
  552. (res, next) => {
  553. stations.updateStation(stationId, next);
  554. }
  555. ], (err) => {
  556. if (err) {
  557. err = utils.getError(err);
  558. logger.error("STATIONS_UPDATE_PRIVACY", `Updating station "${stationId}" privacy to "${newPrivacy}" failed. "${err}"`);
  559. return cb({'status': 'failure', 'message': err});
  560. }
  561. logger.success("STATIONS_UPDATE_PRIVACY", `Updated station "${stationId}" privacy to "${newPrivacy}" successfully.`);
  562. return cb({'status': 'success', 'message': 'Successfully updated the privacy.'});
  563. });
  564. }),
  565. /**
  566. * Updates a station's party mode
  567. *
  568. * @param session
  569. * @param stationId - the station id
  570. * @param newPartyMode - the new station party mode
  571. * @param cb
  572. */
  573. updatePartyMode: hooks.ownerRequired((session, stationId, newPartyMode, cb) => {
  574. async.waterfall([
  575. (next) => {
  576. stations.getStation(stationId, next);
  577. },
  578. (station, next) => {
  579. if (!station) return next('Station not found.');
  580. if (station.partyMode === newPartyMode) return next('The party mode was already ' + ((newPartyMode) ? 'enabled.' : 'disabled.'));
  581. db.models.station.update({_id: stationId}, {$set: {partyMode: newPartyMode}}, next);
  582. },
  583. (res, next) => {
  584. stations.updateStation(stationId, next);
  585. }
  586. ], (err) => {
  587. if (err) {
  588. err = utils.getError(err);
  589. logger.error("STATIONS_UPDATE_PARTY_MODE", `Updating station "${stationId}" party mode to "${newPartyMode}" failed. "${err}"`);
  590. return cb({'status': 'failure', 'message': err});
  591. }
  592. logger.success("STATIONS_UPDATE_PARTY_MODE", `Updated station "${stationId}" party mode to "${newPartyMode}" successfully.`);
  593. cache.pub('station.updatePartyMode', {stationId: stationId, partyMode: newPartyMode});
  594. stations.skipStation(stationId)();
  595. return cb({'status': 'success', 'message': 'Successfully updated the party mode.'});
  596. });
  597. }),
  598. /**
  599. * Pauses a station
  600. *
  601. * @param session
  602. * @param stationId - the station id
  603. * @param cb
  604. */
  605. pause: hooks.ownerRequired((session, stationId, cb) => {
  606. async.waterfall([
  607. (next) => {
  608. stations.getStation(stationId, next);
  609. },
  610. (station, next) => {
  611. if (!station) return next('Station not found.');
  612. if (station.paused) return next('That station was already paused.');
  613. db.models.station.update({_id: stationId}, {$set: {paused: true, pausedAt: Date.now()}}, next);
  614. },
  615. (res, next) => {
  616. stations.updateStation(stationId, next);
  617. }
  618. ], (err) => {
  619. if (err) {
  620. err = utils.getError(err);
  621. logger.error("STATIONS_PAUSE", `Pausing station "${stationId}" failed. "${err}"`);
  622. return cb({'status': 'failure', 'message': err});
  623. }
  624. logger.success("STATIONS_PAUSE", `Paused station "${stationId}" successfully.`);
  625. cache.pub('station.pause', stationId);
  626. notifications.unschedule(`stations.nextSong?id=${stationId}`);
  627. return cb({'status': 'success', 'message': 'Successfully paused.'});
  628. });
  629. }),
  630. /**
  631. * Resumes a station
  632. *
  633. * @param session
  634. * @param stationId - the station id
  635. * @param cb
  636. */
  637. resume: hooks.ownerRequired((session, stationId, cb) => {
  638. async.waterfall([
  639. (next) => {
  640. stations.getStation(stationId, next);
  641. },
  642. (station, next) => {
  643. if (!station) return next('Station not found.');
  644. if (!station.paused) return next('That station is not paused.');
  645. station.timePaused += (Date.now() - station.pausedAt);
  646. db.models.station.update({_id: stationId}, {$set: {paused: false}, $inc: {timePaused: Date.now() - station.pausedAt}}, next);
  647. },
  648. (next) => {
  649. stations.updateStation(stationId, next);
  650. }
  651. ], (err) => {
  652. if (err) {
  653. err = utils.getError(err);
  654. logger.error("STATIONS_RESUME", `Resuming station "${stationId}" failed. "${err}"`);
  655. return cb({'status': 'failure', 'message': err});
  656. }
  657. logger.success("STATIONS_RESUME", `Resuming station "${stationId}" successfully.`);
  658. cache.pub('station.resume', stationId);
  659. return cb({'status': 'success', 'message': 'Successfully resumed.'});
  660. });
  661. }),
  662. /**
  663. * Removes a station
  664. *
  665. * @param session
  666. * @param stationId - the station id
  667. * @param cb
  668. */
  669. remove: hooks.ownerRequired((session, stationId, cb) => {
  670. async.waterfall([
  671. (next) => {
  672. db.models.station.remove({ _id: stationId }, err => next(err));
  673. },
  674. (next) => {
  675. cache.hdel('stations', stationId, err => next(err));
  676. }
  677. ], (err) => {
  678. if (err) {
  679. err = utils.getError(err);
  680. logger.error("STATIONS_REMOVE", `Removing station "${stationId}" failed. "${err}"`);
  681. return cb({ 'status': 'failure', 'message': err });
  682. }
  683. logger.success("STATIONS_REMOVE", `Removing station "${stationId}" successfully.`);
  684. cache.pub('station.remove', stationId);
  685. return cb({ 'status': 'success', 'message': 'Successfully removed.' });
  686. });
  687. }),
  688. /**
  689. * Create a station
  690. *
  691. * @param session
  692. * @param data - the station data
  693. * @param cb
  694. * @param userId
  695. */
  696. create: hooks.loginRequired((session, data, cb, userId) => {
  697. console.log(data);
  698. data.name = data.name.toLowerCase();
  699. let blacklist = ["country", "edm", "musare", "hip-hop", "rap", "top-hits", "todays-hits", "old-school", "christmas", "about", "support", "staff", "help", "news", "terms", "privacy", "profile", "c", "community", "tos", "login", "register", "p", "official", "o", "trap", "faq", "team", "donate", "buy", "shop", "forums", "explore", "settings", "admin", "auth", "reset_password"];
  700. async.waterfall([
  701. (next) => {
  702. if (!data) return next('Invalid data.');
  703. next();
  704. },
  705. (next) => {
  706. db.models.station.findOne({ $or: [{name: data.name}, {displayName: new RegExp(`^${data.displayName}$`, 'i')}] }, next);
  707. },
  708. (station, next) => {
  709. if (station) return next('A station with that name or display name already exists.');
  710. const { name, displayName, description, genres, playlist, type, blacklistedGenres } = data;
  711. if (type === 'official') {
  712. db.models.user.findOne({_id: userId}, (err, user) => {
  713. if (err) return next(err);
  714. if (!user) return next('User not found.');
  715. if (user.role !== 'admin') return next('Admin required.');
  716. db.models.station.create({
  717. name,
  718. displayName,
  719. description,
  720. type,
  721. privacy: 'private',
  722. playlist,
  723. genres,
  724. blacklistedGenres,
  725. currentSong: stations.defaultSong
  726. }, next);
  727. });
  728. } else if (type === 'community') {
  729. if (blacklist.indexOf(name) !== -1) return next('That name is blacklisted. Please use a different name.');
  730. db.models.station.create({
  731. name,
  732. displayName,
  733. description,
  734. type,
  735. privacy: 'private',
  736. owner: userId,
  737. queue: [],
  738. currentSong: null
  739. }, next);
  740. }
  741. }
  742. ], (err, station) => {
  743. if (err) {
  744. console.log(err);
  745. err = utils.getError(err);
  746. logger.error("STATIONS_CREATE", `Creating station failed. "${err}"`);
  747. return cb({'status': 'failure', 'message': err});
  748. }
  749. logger.success("STATIONS_CREATE", `Created station "${station._id}" successfully.`);
  750. cache.pub('station.create', station._id);
  751. return cb({'status': 'success', 'message': 'Successfully created station.'});
  752. });
  753. }),
  754. /**
  755. * Adds song to station queue
  756. *
  757. * @param session
  758. * @param stationId - the station id
  759. * @param songId - the song id
  760. * @param cb
  761. * @param userId
  762. */
  763. addToQueue: hooks.loginRequired((session, stationId, songId, cb, userId) => {
  764. async.waterfall([
  765. (next) => {
  766. stations.getStation(stationId, next);
  767. },
  768. (station, next) => {
  769. if (!station) return next('Station not found.');
  770. if (station.type !== 'community') return next('That station is not a community station.');
  771. if (station.currentSong && station.currentSong.songId === songId) return next('That song is currently playing.');
  772. async.each(station.queue, (queueSong, next) => {
  773. if (queueSong.songId === songId) return next('That song is already in the queue.');
  774. next();
  775. }, (err) => {
  776. next(err, station);
  777. });
  778. },
  779. (station, next) => {
  780. songs.getSong(songId, (err, song) => {
  781. if (!err && song) return next(null, song);
  782. console.log(53, songId);
  783. utils.getSongFromYouTube(songId, (song) => {
  784. song.artists = [];
  785. song.skipDuration = 0;
  786. song.likes = -1;
  787. song.dislikes = -1;
  788. song.thumbnail = "empty";
  789. song.explicit = false;
  790. next(null, song);
  791. });
  792. });
  793. },
  794. (song, next) => {
  795. song.requestedBy = userId;
  796. db.models.station.update({_id: stationId}, {$push: {queue: song}}, next);
  797. },
  798. (res, next) => {
  799. stations.updateStation(stationId, next);
  800. }
  801. ], (err, station) => {
  802. if (err) {
  803. err = utils.getError(err);
  804. logger.error("STATIONS_ADD_SONG_TO_QUEUE", `Adding song "${songId}" to station "${stationId}" queue failed. "${err}"`);
  805. return cb({'status': 'failure', 'message': err});
  806. }
  807. logger.success("STATIONS_ADD_SONG_TO_QUEUE", `Added song "${songId}" to station "${stationId}" successfully.`);
  808. cache.pub('station.queueUpdate', stationId);
  809. return cb({'status': 'success', 'message': 'Successfully added song to queue.'});
  810. });
  811. }),
  812. /**
  813. * Removes song from station queue
  814. *
  815. * @param session
  816. * @param stationId - the station id
  817. * @param songId - the song id
  818. * @param cb
  819. * @param userId
  820. */
  821. removeFromQueue: hooks.ownerRequired((session, stationId, songId, cb, userId) => {
  822. async.waterfall([
  823. (next) => {
  824. if (!songId) return next('Invalid song id.');
  825. stations.getStation(stationId, next);
  826. },
  827. (station, next) => {
  828. if (!station) return next('Station not found.');
  829. if (station.type !== 'community') return next('Station is not a community station.');
  830. async.each(station.queue, (queueSong, next) => {
  831. if (queueSong.songId === songId) return next(true);
  832. next();
  833. }, (err) => {
  834. if (err === true) return next();
  835. next('Song is not currently in the queue.');
  836. });
  837. },
  838. (next) => {
  839. db.models.update({_id: stationId}, {$pull: {queue: {songId: songId}}}, next);
  840. },
  841. (next) => {
  842. stations.updateStation(stationId, next);
  843. }
  844. ], (err, station) => {
  845. if (err) {
  846. err = utils.getError(err);
  847. logger.error("STATIONS_REMOVE_SONG_TO_QUEUE", `Removing song "${songId}" from station "${stationId}" queue failed. "${err}"`);
  848. return cb({'status': 'failure', 'message': err});
  849. }
  850. logger.success("STATIONS_REMOVE_SONG_TO_QUEUE", `Removed song "${songId}" from station "${stationId}" successfully.`);
  851. cache.pub('station.queueUpdate', stationId);
  852. return cb({'status': 'success', 'message': 'Successfully removed song from queue.'});
  853. });
  854. }),
  855. /**
  856. * Gets the queue from a station
  857. *
  858. * @param session
  859. * @param stationId - the station id
  860. * @param cb
  861. */
  862. getQueue: hooks.adminRequired((session, stationId, cb) => {
  863. async.waterfall([
  864. (next) => {
  865. stations.getStation(stationId, next);
  866. },
  867. (station, next) => {
  868. if (!station) return next('Station not found.');
  869. if (station.type !== 'community') return next('Station is not a community station.');
  870. next(null, station);
  871. }
  872. ], (err, station) => {
  873. if (err) {
  874. err = utils.getError(err);
  875. logger.error("STATIONS_GET_QUEUE", `Getting queue for station "${stationId}" failed. "${err}"`);
  876. return cb({'status': 'failure', 'message': err});
  877. }
  878. logger.success("STATIONS_GET_QUEUE", `Got queue for station "${stationId}" successfully.`);
  879. return cb({'status': 'success', 'message': 'Successfully got queue.', queue: station.queue});
  880. });
  881. }),
  882. /**
  883. * Selects a private playlist for a station
  884. *
  885. * @param session
  886. * @param stationId - the station id
  887. * @param playlistId - the private playlist id
  888. * @param cb
  889. * @param userId
  890. */
  891. selectPrivatePlaylist: hooks.ownerRequired((session, stationId, playlistId, cb, userId) => {
  892. async.waterfall([
  893. (next) => {
  894. stations.getStation(stationId, next);
  895. },
  896. (station, next) => {
  897. if (!station) return next('Station not found.');
  898. if (station.type !== 'community') return next('Station is not a community station.');
  899. if (station.privatePlaylist === playlistId) return next('That private playlist is already selected.');
  900. db.models.playlist.findOne({_id: playlistId}, next);
  901. },
  902. (playlist, next) => {
  903. if (!playlist) return next('Playlist not found.');
  904. let currentSongIndex = (playlist.songs.length > 0) ? playlist.songs.length - 1 : 0;
  905. db.models.station.update({_id: stationId}, {$set: {privatePlaylist: playlistId, currentSongIndex: currentSongIndex}}, next);
  906. },
  907. (res, next) => {
  908. stations.updateStation(stationId, next);
  909. }
  910. ], (err, station) => {
  911. if (err) {
  912. err = utils.getError(err);
  913. logger.error("STATIONS_SELECT_PRIVATE_PLAYLIST", `Selecting private playlist "${playlistId}" for station "${stationId}" failed. "${err}"`);
  914. return cb({'status': 'failure', 'message': err});
  915. }
  916. logger.success("STATIONS_SELECT_PRIVATE_PLAYLIST", `Selected private playlist "${playlistId}" for station "${stationId}" successfully.`);
  917. if (!station.partyMode) stations.skipStation(stationId)();
  918. cache.pub('privatePlaylist.selected', {playlistId, stationId});
  919. return cb({'status': 'success', 'message': 'Successfully selected playlist.'});
  920. });
  921. }),
  922. };