utils.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. 'use strict';
  2. const coreClass = require("../core");
  3. const config = require('config'),
  4. async = require('async'),
  5. request = require('request');
  6. class Timer {
  7. constructor(callback, delay, paused) {
  8. this.callback = callback;
  9. this.timerId = undefined;
  10. this.start = undefined;
  11. this.paused = paused;
  12. this.remaining = delay;
  13. this.timeWhenPaused = 0;
  14. this.timePaused = Date.now();
  15. if (!paused) {
  16. this.resume();
  17. }
  18. }
  19. pause() {
  20. clearTimeout(this.timerId);
  21. this.remaining -= Date.now() - this.start;
  22. this.timePaused = Date.now();
  23. this.paused = true;
  24. }
  25. ifNotPaused() {
  26. if (!this.paused) {
  27. this.resume();
  28. }
  29. }
  30. resume() {
  31. this.start = Date.now();
  32. clearTimeout(this.timerId);
  33. this.timerId = setTimeout(this.callback, this.remaining);
  34. this.timeWhenPaused = Date.now() - this.timePaused;
  35. this.paused = false;
  36. }
  37. resetTimeWhenPaused() {
  38. this.timeWhenPaused = 0;
  39. }
  40. getTimePaused() {
  41. if (!this.paused) {
  42. return this.timeWhenPaused;
  43. } else {
  44. return Date.now() - this.timePaused;
  45. }
  46. }
  47. }
  48. let youtubeRequestCallbacks = [];
  49. let youtubeRequestsPending = 0;
  50. let youtubeRequestsActive = false;
  51. module.exports = class extends coreClass {
  52. initialize() {
  53. return new Promise((resolve, reject) => {
  54. this.io = this.moduleManager.modules["io"];
  55. this.db = this.moduleManager.modules["db"];
  56. this.spotify = this.moduleManager.modules["spotify"];
  57. this.cache = this.moduleManager.modules["cache"];
  58. this.Timer = Timer;
  59. resolve();
  60. });
  61. }
  62. async parseCookies(cookieString) {
  63. try { await this._validateHook(); } catch { return; }
  64. let cookies = {};
  65. if (cookieString) cookieString.split("; ").map((cookie) => {
  66. (cookies[cookie.substring(0, cookie.indexOf("="))] = cookie.substring(cookie.indexOf("=") + 1, cookie.length));
  67. });
  68. return cookies;
  69. }
  70. async cookiesToString(cookies) {
  71. try { await this._validateHook(); } catch { return; }
  72. let newCookie = [];
  73. for (let prop in cookie) {
  74. newCookie.push(prop + "=" + cookie[prop]);
  75. }
  76. return newCookie.join("; ");
  77. }
  78. async removeCookie(cookieString, cookieName) {
  79. try { await this._validateHook(); } catch { return; }
  80. var cookies = this.parseCookies(cookieString);
  81. delete cookies[cookieName];
  82. return this.toString(cookies);
  83. }
  84. async htmlEntities(str) {
  85. try { await this._validateHook(); } catch { return; }
  86. return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
  87. }
  88. async generateRandomString(len) {
  89. try { await this._validateHook(); } catch { return; }
  90. let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split("");
  91. let result = [];
  92. for (let i = 0; i < len; i++) {
  93. result.push(chars[await this.getRandomNumber(0, chars.length - 1)]);
  94. }
  95. return result.join("");
  96. }
  97. async getSocketFromId(socketId) {
  98. try { await this._validateHook(); } catch { return; }
  99. return globals.io.sockets.sockets[socketId];
  100. }
  101. async getRandomNumber(min, max) {
  102. try { await this._validateHook(); } catch { return; }
  103. return Math.floor(Math.random() * (max - min + 1)) + min
  104. }
  105. async convertTime(duration) {
  106. try { await this._validateHook(); } catch { return; }
  107. let a = duration.match(/\d+/g);
  108. if (duration.indexOf('M') >= 0 && duration.indexOf('H') == -1 && duration.indexOf('S') == -1) {
  109. a = [0, a[0], 0];
  110. }
  111. if (duration.indexOf('H') >= 0 && duration.indexOf('M') == -1) {
  112. a = [a[0], 0, a[1]];
  113. }
  114. if (duration.indexOf('H') >= 0 && duration.indexOf('M') == -1 && duration.indexOf('S') == -1) {
  115. a = [a[0], 0, 0];
  116. }
  117. duration = 0;
  118. if (a.length == 3) {
  119. duration = duration + parseInt(a[0]) * 3600;
  120. duration = duration + parseInt(a[1]) * 60;
  121. duration = duration + parseInt(a[2]);
  122. }
  123. if (a.length == 2) {
  124. duration = duration + parseInt(a[0]) * 60;
  125. duration = duration + parseInt(a[1]);
  126. }
  127. if (a.length == 1) {
  128. duration = duration + parseInt(a[0]);
  129. }
  130. let hours = Math.floor(duration / 3600);
  131. let minutes = Math.floor(duration % 3600 / 60);
  132. let seconds = Math.floor(duration % 3600 % 60);
  133. return (hours < 10 ? ("0" + hours + ":") : (hours + ":")) + (minutes < 10 ? ("0" + minutes + ":") : (minutes + ":")) + (seconds < 10 ? ("0" + seconds) : seconds);
  134. }
  135. async guid () {
  136. try { await this._validateHook(); } catch { return; }
  137. return [1,1,0,1,0,1,0,1,0,1,1,1].map(b => b ? Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1) : '-').join('');
  138. }
  139. async socketFromSession(socketId) {
  140. try { await this._validateHook(); } catch { return; }
  141. let ns = this.io.io.of("/");
  142. if (ns) {
  143. return ns.connected[socketId];
  144. }
  145. }
  146. async socketsFromSessionId(sessionId, cb) {
  147. try { await this._validateHook(); } catch { return; }
  148. let ns = this.io.io.of("/");
  149. let sockets = [];
  150. if (ns) {
  151. async.each(Object.keys(ns.connected), (id, next) => {
  152. let session = ns.connected[id].session;
  153. if (session.sessionId === sessionId) sockets.push(session.sessionId);
  154. next();
  155. }, () => {
  156. cb(sockets);
  157. });
  158. }
  159. }
  160. async socketsFromUser(userId, cb) {
  161. try { await this._validateHook(); } catch { return; }
  162. let ns = this.io.io.of("/");
  163. let sockets = [];
  164. if (ns) {
  165. async.each(Object.keys(ns.connected), (id, next) => {
  166. let session = ns.connected[id].session;
  167. this.cache.hget('sessions', session.sessionId, (err, session) => {
  168. if (!err && session && session.userId === userId) sockets.push(ns.connected[id]);
  169. next();
  170. });
  171. }, () => {
  172. cb(sockets);
  173. });
  174. }
  175. }
  176. async socketsFromIP(ip, cb) {
  177. try { await this._validateHook(); } catch { return; }
  178. let ns = this.io.io.of("/");
  179. let sockets = [];
  180. if (ns) {
  181. async.each(Object.keys(ns.connected), (id, next) => {
  182. let session = ns.connected[id].session;
  183. this.cache.hget('sessions', session.sessionId, (err, session) => {
  184. if (!err && session && ns.connected[id].ip === ip) sockets.push(ns.connected[id]);
  185. next();
  186. });
  187. }, () => {
  188. cb(sockets);
  189. });
  190. }
  191. }
  192. async socketsFromUserWithoutCache(userId, cb) {
  193. try { await this._validateHook(); } catch { return; }
  194. let ns = this.io.io.of("/");
  195. let sockets = [];
  196. if (ns) {
  197. async.each(Object.keys(ns.connected), (id, next) => {
  198. let session = ns.connected[id].session;
  199. if (session.userId === userId) sockets.push(ns.connected[id]);
  200. next();
  201. }, () => {
  202. cb(sockets);
  203. });
  204. }
  205. }
  206. async socketLeaveRooms(socketid) {
  207. try { await this._validateHook(); } catch { return; }
  208. let socket = await this.socketFromSession(socketid);
  209. let rooms = socket.rooms;
  210. for (let room in rooms) {
  211. socket.leave(room);
  212. }
  213. }
  214. async socketJoinRoom(socketId, room) {
  215. try { await this._validateHook(); } catch { return; }
  216. let socket = await this.socketFromSession(socketId);
  217. let rooms = socket.rooms;
  218. for (let room in rooms) {
  219. socket.leave(room);
  220. }
  221. socket.join(room);
  222. }
  223. async socketJoinSongRoom(socketId, room) {
  224. try { await this._validateHook(); } catch { return; }
  225. let socket = await this.socketFromSession(socketId);
  226. let rooms = socket.rooms;
  227. for (let room in rooms) {
  228. if (room.indexOf('song.') !== -1) socket.leave(rooms);
  229. }
  230. socket.join(room);
  231. }
  232. async socketsJoinSongRoom(sockets, room) {
  233. try { await this._validateHook(); } catch { return; }
  234. for (let id in sockets) {
  235. let socket = sockets[id];
  236. let rooms = socket.rooms;
  237. for (let room in rooms) {
  238. if (room.indexOf('song.') !== -1) socket.leave(room);
  239. }
  240. socket.join(room);
  241. }
  242. }
  243. async socketsLeaveSongRooms(sockets) {
  244. try { await this._validateHook(); } catch { return; }
  245. for (let id in sockets) {
  246. let socket = sockets[id];
  247. let rooms = socket.rooms;
  248. for (let room in rooms) {
  249. if (room.indexOf('song.') !== -1) socket.leave(room);
  250. }
  251. }
  252. }
  253. async emitToRoom(room) {
  254. try { await this._validateHook(); } catch { return; }
  255. let sockets = this.io.io.sockets.sockets;
  256. for (let id in sockets) {
  257. let socket = sockets[id];
  258. if (socket.rooms[room]) {
  259. let args = [];
  260. for (let i = 1; i < Object.keys(arguments).length; i++) {
  261. args.push(arguments[i]);
  262. }
  263. socket.emit.apply(socket, args);
  264. }
  265. }
  266. }
  267. async getRoomSockets(room) {
  268. try { await this._validateHook(); } catch { return; }
  269. let sockets = this.io.io.sockets.sockets;
  270. let roomSockets = [];
  271. for (let id in sockets) {
  272. let socket = sockets[id];
  273. if (socket.rooms[room]) roomSockets.push(socket);
  274. }
  275. return roomSockets;
  276. }
  277. async getSongFromYouTube(songId, cb) {
  278. try { await this._validateHook(); } catch { return; }
  279. youtubeRequestCallbacks.push({cb: (test) => {
  280. youtubeRequestsActive = true;
  281. const youtubeParams = [
  282. 'part=snippet,contentDetails,statistics,status',
  283. `id=${encodeURIComponent(songId)}`,
  284. `key=${config.get('apis.youtube.key')}`
  285. ].join('&');
  286. request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
  287. youtubeRequestCallbacks.splice(0, 1);
  288. if (youtubeRequestCallbacks.length > 0) {
  289. youtubeRequestCallbacks[0].cb(youtubeRequestCallbacks[0].songId);
  290. } else youtubeRequestsActive = false;
  291. if (err) {
  292. console.error(err);
  293. return null;
  294. }
  295. body = JSON.parse(body);
  296. //TODO Clean up duration converter
  297. let dur = body.items[0].contentDetails.duration;
  298. dur = dur.replace('PT', '');
  299. let duration = 0;
  300. dur = dur.replace(/([\d]*)H/, (v, v2) => {
  301. v2 = Number(v2);
  302. duration = (v2 * 60 * 60);
  303. return '';
  304. });
  305. dur = dur.replace(/([\d]*)M/, (v, v2) => {
  306. v2 = Number(v2);
  307. duration += (v2 * 60);
  308. return '';
  309. });
  310. dur = dur.replace(/([\d]*)S/, (v, v2) => {
  311. v2 = Number(v2);
  312. duration += v2;
  313. return '';
  314. });
  315. let song = {
  316. songId: body.items[0].id,
  317. title: body.items[0].snippet.title,
  318. duration
  319. };
  320. cb(song);
  321. });
  322. }, songId});
  323. if (!youtubeRequestsActive) {
  324. youtubeRequestCallbacks[0].cb(youtubeRequestCallbacks[0].songId);
  325. }
  326. }
  327. async getPlaylistFromYouTube(url, cb) {
  328. try { await this._validateHook(); } catch { return; }
  329. let name = 'list'.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  330. var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
  331. let playlistId = regex.exec(url)[1];
  332. function getPage(pageToken, songs) {
  333. let nextPageToken = (pageToken) ? `pageToken=${pageToken}` : '';
  334. const youtubeParams = [
  335. 'part=contentDetails',
  336. `playlistId=${encodeURIComponent(playlistId)}`,
  337. `maxResults=5`,
  338. `key=${config.get('apis.youtube.key')}`,
  339. nextPageToken
  340. ].join('&');
  341. request(`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`, (err, res, body) => {
  342. if (err) {
  343. console.error(err);
  344. return next('Failed to find playlist from YouTube');
  345. }
  346. body = JSON.parse(body);
  347. songs = songs.concat(body.items);
  348. if (body.nextPageToken) getPage(body.nextPageToken, songs);
  349. else {
  350. console.log(songs);
  351. cb(songs);
  352. }
  353. });
  354. }
  355. getPage(null, []);
  356. }
  357. async getSongFromSpotify(song, cb) {
  358. try { await this._validateHook(); } catch { return; }
  359. if (!config.get("apis.spotify.enabled")) return cb("Spotify is not enabled", null);
  360. const spotifyParams = [
  361. `q=${encodeURIComponent(song.title)}`,
  362. `type=track`
  363. ].join('&');
  364. const token = await this.spotify.getToken();
  365. const options = {
  366. url: `https://api.spotify.com/v1/search?${spotifyParams}`,
  367. headers: {
  368. Authorization: `Bearer ${token}`
  369. }
  370. };
  371. request(options, (err, res, body) => {
  372. if (err) console.error(err);
  373. body = JSON.parse(body);
  374. if (body.error) console.error(body.error);
  375. durationArtistLoop:
  376. for (let i in body) {
  377. let items = body[i].items;
  378. for (let j in items) {
  379. let item = items[j];
  380. let hasArtist = false;
  381. for (let k = 0; k < item.artists.length; k++) {
  382. let artist = item.artists[k];
  383. if (song.title.indexOf(artist.name) !== -1) hasArtist = true;
  384. }
  385. if (hasArtist && song.title.indexOf(item.name) !== -1) {
  386. song.duration = item.duration_ms / 1000;
  387. song.artists = item.artists.map(artist => {
  388. return artist.name;
  389. });
  390. song.title = item.name;
  391. song.explicit = item.explicit;
  392. song.thumbnail = item.album.images[1].url;
  393. break durationArtistLoop;
  394. }
  395. }
  396. }
  397. cb(null, song);
  398. });
  399. }
  400. async getSongsFromSpotify(title, artist, cb) {
  401. try { await this._validateHook(); } catch { return; }
  402. if (!config.get("apis.spotify.enabled")) return cb([]);
  403. const spotifyParams = [
  404. `q=${encodeURIComponent(title)}`,
  405. `type=track`
  406. ].join('&');
  407. const token = await this.spotify.getToken();
  408. const options = {
  409. url: `https://api.spotify.com/v1/search?${spotifyParams}`,
  410. headers: {
  411. Authorization: `Bearer ${token}`
  412. }
  413. };
  414. request(options, (err, res, body) => {
  415. if (err) return console.error(err);
  416. body = JSON.parse(body);
  417. if (body.error) return console.error(body.error);
  418. let songs = [];
  419. for (let i in body) {
  420. let items = body[i].items;
  421. for (let j in items) {
  422. let item = items[j];
  423. let hasArtist = false;
  424. for (let k = 0; k < item.artists.length; k++) {
  425. let localArtist = item.artists[k];
  426. if (artist.toLowerCase() === localArtist.name.toLowerCase()) hasArtist = true;
  427. }
  428. if (hasArtist && (title.indexOf(item.name) !== -1 || item.name.indexOf(title) !== -1)) {
  429. let song = {};
  430. song.duration = item.duration_ms / 1000;
  431. song.artists = item.artists.map(artist => {
  432. return artist.name;
  433. });
  434. song.title = item.name;
  435. song.explicit = item.explicit;
  436. song.thumbnail = item.album.images[1].url;
  437. songs.push(song);
  438. }
  439. }
  440. }
  441. cb(songs);
  442. });
  443. }
  444. async shuffle(array) {
  445. try { await this._validateHook(); } catch { return; }
  446. let currentIndex = array.length, temporaryValue, randomIndex;
  447. // While there remain elements to shuffle...
  448. while (0 !== currentIndex) {
  449. // Pick a remaining element...
  450. randomIndex = Math.floor(Math.random() * currentIndex);
  451. currentIndex -= 1;
  452. // And swap it with the current element.
  453. temporaryValue = array[currentIndex];
  454. array[currentIndex] = array[randomIndex];
  455. array[randomIndex] = temporaryValue;
  456. }
  457. return array;
  458. }
  459. async getError(err) {
  460. try { await this._validateHook(); } catch { return; }
  461. let error = 'An error occurred.';
  462. if (typeof err === "string") error = err;
  463. else if (err.message) {
  464. if (err.message !== 'Validation failed') error = err.message;
  465. else error = err.errors[Object.keys(err.errors)].message;
  466. }
  467. return error;
  468. }
  469. async canUserBeInStation(station, userId, cb) {
  470. try { await this._validateHook(); } catch { return; }
  471. async.waterfall([
  472. (next) => {
  473. if (station.privacy !== 'private') return next(true);
  474. if (!userId) return next(false);
  475. next();
  476. },
  477. (next) => {
  478. this.db.models.user.findOne({_id: userId}, next);
  479. },
  480. (user, next) => {
  481. if (!user) return next(false);
  482. if (user.role === 'admin') return next(true);
  483. if (station.type === 'official') return next(false);
  484. if (station.owner === userId) return next(true);
  485. next(false);
  486. }
  487. ], (err) => {
  488. if (err === true) return cb(true);
  489. return cb(false);
  490. });
  491. }
  492. }