utils.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. import async from "async";
  2. import crypto from "crypto";
  3. import CoreClass from "../core";
  4. let UtilsModule;
  5. let IOModule;
  6. let CacheModule;
  7. class _UtilsModule extends CoreClass {
  8. // eslint-disable-next-line require-jsdoc
  9. constructor() {
  10. super("utils");
  11. this.youtubeRequestCallbacks = [];
  12. this.youtubeRequestsPending = 0;
  13. this.youtubeRequestsActive = false;
  14. UtilsModule = this;
  15. }
  16. /**
  17. * Initialises the utils module
  18. *
  19. * @returns {Promise} - returns promise (reject, resolve)
  20. */
  21. initialize() {
  22. return new Promise(resolve => {
  23. IOModule = this.moduleManager.modules.io;
  24. CacheModule = this.moduleManager.modules.cache;
  25. resolve();
  26. });
  27. }
  28. /**
  29. * Parses the cookie into a readable object
  30. *
  31. * @param {object} payload - object that contains the payload
  32. * @param {string} payload.cookieString - the cookie string
  33. * @returns {Promise} - returns promise (reject, resolve)
  34. */
  35. PARSE_COOKIES(payload) {
  36. return new Promise((resolve, reject) => {
  37. const cookies = {};
  38. if (typeof payload.cookieString !== "string") return reject(new Error("Cookie string is not a string"));
  39. // eslint-disable-next-line array-callback-return
  40. payload.cookieString.split("; ").map(cookie => {
  41. cookies[cookie.substring(0, cookie.indexOf("="))] = cookie.substring(
  42. cookie.indexOf("=") + 1,
  43. cookie.length
  44. );
  45. });
  46. return resolve(cookies);
  47. });
  48. }
  49. // COOKIES_TO_STRING() {//cookies
  50. // return new Promise((resolve, reject) => {
  51. // let newCookie = [];
  52. // for (let prop in cookie) {
  53. // newCookie.push(prop + "=" + cookie[prop]);
  54. // }
  55. // return newCookie.join("; ");
  56. // });
  57. // }
  58. /**
  59. * Removes a cookie by name
  60. *
  61. * @param {object} payload - object that contains the payload
  62. * @param {object} payload.cookieString - the cookie string
  63. * @param {string} payload.cookieName - the unique name of the cookie
  64. * @returns {Promise} - returns promise (reject, resolve)
  65. */
  66. REMOVE_COOKIE(payload) {
  67. return new Promise((resolve, reject) => {
  68. let cookies;
  69. try {
  70. cookies = UtilsModule.runJob(
  71. "PARSE_COOKIES",
  72. {
  73. cookieString: payload.cookieString
  74. },
  75. this
  76. );
  77. } catch (err) {
  78. return reject(err);
  79. }
  80. delete cookies[payload.cookieName];
  81. return resolve();
  82. });
  83. }
  84. /**
  85. * Replaces any html reserved characters in a string with html entities
  86. *
  87. * @param {object} payload - object that contains the payload
  88. * @param {string} payload.str - the string to replace characters with html entities
  89. * @returns {Promise} - returns promise (reject, resolve)
  90. */
  91. HTML_ENTITIES(payload) {
  92. return new Promise(resolve => {
  93. resolve(
  94. String(payload.str)
  95. .replace(/&/g, "&")
  96. .replace(/</g, "&lt;")
  97. .replace(/>/g, "&gt;")
  98. .replace(/"/g, "&quot;")
  99. );
  100. });
  101. }
  102. /**
  103. * Generates a random string of a specified length
  104. *
  105. * @param {object} payload - object that contains the payload
  106. * @param {number} payload.length - the length the random string should be
  107. * @returns {Promise} - returns promise (reject, resolve)
  108. */
  109. async GENERATE_RANDOM_STRING(payload) {
  110. const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split("");
  111. const promises = [];
  112. for (let i = 0; i < payload.length; i += 1) {
  113. promises.push(
  114. UtilsModule.runJob(
  115. "GET_RANDOM_NUMBER",
  116. {
  117. min: 0,
  118. max: chars.length - 1
  119. },
  120. this
  121. )
  122. );
  123. }
  124. const randomNums = await Promise.all(promises);
  125. const randomChars = [];
  126. for (let i = 0; i < payload.length; i += 1) {
  127. randomChars.push(chars[randomNums[i]]);
  128. }
  129. return new Promise(resolve => resolve(randomChars.join("")));
  130. }
  131. /**
  132. * Returns a socket object from a socket identifier
  133. *
  134. * @param {object} payload - object that contains the payload
  135. * @param {string} payload.socketId - the socket id
  136. * @returns {Promise} - returns promise (reject, resolve)
  137. */
  138. async GET_SOCKET_FROM_ID(payload) {
  139. // socketId
  140. const io = await IOModule.runJob("IO", {}, this);
  141. return new Promise(resolve => resolve(io.sockets.sockets[payload.socketId]));
  142. }
  143. /**
  144. * Creates a random number within a range
  145. *
  146. * @param {object} payload - object that contains the payload
  147. * @param {number} payload.min - the minimum number the result should be
  148. * @param {number} payload.max - the maximum number the result should be
  149. * @returns {Promise} - returns promise (reject, resolve)
  150. */
  151. GET_RANDOM_NUMBER(payload) {
  152. // min, max
  153. return new Promise(resolve =>
  154. resolve(Math.floor(Math.random() * (payload.max - payload.min + 1)) + payload.min)
  155. );
  156. }
  157. /**
  158. * Converts ISO8601 time format (YouTube API) to HH:MM:SS
  159. *
  160. * @param {object} payload - object contaiing the payload
  161. * @param {string} payload.duration - string in the format of ISO8601
  162. * @returns {Promise} - returns a promise (resolve, reject)
  163. */
  164. CONVERT_TIME(payload) {
  165. // duration
  166. return new Promise(resolve => {
  167. let { duration } = payload;
  168. let a = duration.match(/\d+/g);
  169. if (duration.indexOf("M") >= 0 && duration.indexOf("H") === -1 && duration.indexOf("S") === -1) {
  170. a = [0, a[0], 0];
  171. }
  172. if (duration.indexOf("H") >= 0 && duration.indexOf("M") === -1) {
  173. a = [a[0], 0, a[1]];
  174. }
  175. if (duration.indexOf("H") >= 0 && duration.indexOf("M") === -1 && duration.indexOf("S") === -1) {
  176. a = [a[0], 0, 0];
  177. }
  178. duration = 0;
  179. if (a.length === 3) {
  180. duration += parseInt(a[0]) * 3600;
  181. duration += parseInt(a[1]) * 60;
  182. duration += parseInt(a[2]);
  183. }
  184. if (a.length === 2) {
  185. duration += parseInt(a[0]) * 60;
  186. duration += parseInt(a[1]);
  187. }
  188. if (a.length === 1) {
  189. duration += parseInt(a[0]);
  190. }
  191. const hours = Math.floor(duration / 3600);
  192. const minutes = Math.floor((duration % 3600) / 60);
  193. const seconds = Math.floor((duration % 3600) % 60);
  194. resolve(
  195. (hours < 10 ? `0${hours}:` : `${hours}:`) +
  196. (minutes < 10 ? `0${minutes}:` : `${minutes}:`) +
  197. (seconds < 10 ? `0${seconds}` : seconds)
  198. );
  199. });
  200. }
  201. /**
  202. * Creates a random identifier for e.g. sessionId
  203. *
  204. * @returns {Promise} - returns promise (reject, resolve)
  205. */
  206. GUID() {
  207. return new Promise(resolve => {
  208. resolve(
  209. [1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1]
  210. .map(b =>
  211. b
  212. ? Math.floor((1 + Math.random()) * 0x10000)
  213. .toString(16)
  214. .substring(1)
  215. : "-"
  216. )
  217. .join("")
  218. );
  219. });
  220. }
  221. // UNKNOWN
  222. // eslint-disable-next-line require-jsdoc
  223. async SOCKET_FROM_SESSION(payload) {
  224. // socketId
  225. const io = await IOModule.runJob("IO", {}, this);
  226. return new Promise((resolve, reject) => {
  227. const ns = io.of("/");
  228. if (ns) {
  229. return resolve(ns.connected[payload.socketId]);
  230. }
  231. return reject();
  232. });
  233. }
  234. /**
  235. * Gets all sockets for a specified session id
  236. *
  237. * @param {object} payload - object containing the payload
  238. * @param {string} payload.sessionId - user session id
  239. * @returns {Promise} - returns promise (reject, resolve)
  240. */
  241. async SOCKETS_FROM_SESSION_ID(payload) {
  242. const io = await IOModule.runJob("IO", {}, this);
  243. return new Promise(resolve => {
  244. const ns = io.of("/");
  245. const sockets = [];
  246. if (ns) {
  247. return async.each(
  248. Object.keys(ns.connected),
  249. (id, next) => {
  250. const { session } = ns.connected[id];
  251. if (session.sessionId === payload.sessionId) sockets.push(session.sessionId);
  252. next();
  253. },
  254. () => {
  255. resolve({ sockets });
  256. }
  257. );
  258. }
  259. return resolve();
  260. });
  261. }
  262. /**
  263. * Returns any sockets for a specific user
  264. *
  265. * @param {object} payload - object that contains the payload
  266. * @param {string} payload.userId - the user id
  267. * @returns {Promise} - returns promise (reject, resolve)
  268. */
  269. async SOCKETS_FROM_USER(payload) {
  270. const io = await IOModule.runJob("IO", {}, this);
  271. return new Promise((resolve, reject) => {
  272. const ns = io.of("/");
  273. const sockets = [];
  274. if (ns) {
  275. return async.each(
  276. Object.keys(ns.connected),
  277. (id, next) => {
  278. const { session } = ns.connected[id];
  279. CacheModule.runJob(
  280. "HGET",
  281. {
  282. table: "sessions",
  283. key: session.sessionId
  284. },
  285. this
  286. )
  287. .then(session => {
  288. if (session && session.userId === payload.userId) sockets.push(ns.connected[id]);
  289. next();
  290. })
  291. .catch(err => {
  292. next(err);
  293. });
  294. },
  295. err => {
  296. if (err) return reject(err);
  297. return resolve({ sockets });
  298. }
  299. );
  300. }
  301. return resolve();
  302. });
  303. }
  304. /**
  305. * Returns any sockets from a specific ip address
  306. *
  307. * @param {object} payload - object that contains the payload
  308. * @param {string} payload.ip - the ip address in question
  309. * @returns {Promise} - returns promise (reject, resolve)
  310. */
  311. async SOCKETS_FROM_IP(payload) {
  312. const io = await IOModule.runJob("IO", {}, this);
  313. return new Promise(resolve => {
  314. const ns = io.of("/");
  315. const sockets = [];
  316. if (ns) {
  317. return async.each(
  318. Object.keys(ns.connected),
  319. (id, next) => {
  320. const { session } = ns.connected[id];
  321. CacheModule.runJob(
  322. "HGET",
  323. {
  324. table: "sessions",
  325. key: session.sessionId
  326. },
  327. this
  328. )
  329. .then(session => {
  330. if (session && ns.connected[id].ip === payload.ip) sockets.push(ns.connected[id]);
  331. next();
  332. })
  333. .catch(() => next());
  334. },
  335. () => {
  336. resolve({ sockets });
  337. }
  338. );
  339. }
  340. return resolve();
  341. });
  342. }
  343. /**
  344. * Returns any sockets from a specific user without using redis/cache
  345. *
  346. * @param {object} payload - object that contains the payload
  347. * @param {string} payload.userId - the id of the user in question
  348. * @returns {Promise} - returns promise (reject, resolve)
  349. */
  350. async SOCKETS_FROM_USER_WITHOUT_CACHE(payload) {
  351. const io = await IOModule.runJob("IO", {}, this);
  352. return new Promise(resolve => {
  353. const ns = io.of("/");
  354. const sockets = [];
  355. if (ns) {
  356. return async.each(
  357. Object.keys(ns.connected),
  358. (id, next) => {
  359. const { session } = ns.connected[id];
  360. if (session.userId === payload.userId) sockets.push(ns.connected[id]);
  361. next();
  362. },
  363. () => {
  364. resolve({ sockets });
  365. }
  366. );
  367. }
  368. return resolve();
  369. });
  370. }
  371. /**
  372. * Allows a socket to leave any rooms they are connected to
  373. *
  374. * @param {object} payload - object that contains the payload
  375. * @param {string} payload.socketId - the id of the socket which should leave all their rooms
  376. * @returns {Promise} - returns promise (reject, resolve)
  377. */
  378. async SOCKET_LEAVE_ROOMS(payload) {
  379. const socket = await UtilsModule.runJob(
  380. "SOCKET_FROM_SESSION",
  381. {
  382. socketId: payload.socketId
  383. },
  384. this
  385. );
  386. return new Promise(resolve => {
  387. const { rooms } = socket;
  388. Object.keys(rooms).forEach(roomKey => {
  389. const room = rooms[roomKey];
  390. socket.leave(room);
  391. });
  392. return resolve();
  393. });
  394. }
  395. /**
  396. * Allows a socket to join a specified room
  397. *
  398. * @param {object} payload - object that contains the payload
  399. * @param {string} payload.socketId - the id of the socket which should join the room
  400. * @param {object} payload.room - the object representing the room the socket should join
  401. * @returns {Promise} - returns promise (reject, resolve)
  402. */
  403. async SOCKET_JOIN_ROOM(payload) {
  404. const socket = await UtilsModule.runJob(
  405. "SOCKET_FROM_SESSION",
  406. {
  407. socketId: payload.socketId
  408. },
  409. this
  410. );
  411. return new Promise(resolve => {
  412. const { rooms } = socket;
  413. Object.keys(rooms).forEach(roomKey => {
  414. const room = rooms[roomKey];
  415. socket.leave(room);
  416. });
  417. socket.join(payload.room);
  418. return resolve();
  419. });
  420. }
  421. // UNKNOWN
  422. // eslint-disable-next-line require-jsdoc
  423. async SOCKET_JOIN_SONG_ROOM(payload) {
  424. // socketId, room
  425. const socket = await UtilsModule.runJob(
  426. "SOCKET_FROM_SESSION",
  427. {
  428. socketId: payload.socketId
  429. },
  430. this
  431. );
  432. return new Promise(resolve => {
  433. const { rooms } = socket;
  434. Object.keys(rooms).forEach(roomKey => {
  435. const room = rooms[roomKey];
  436. if (room.indexOf("song.") !== -1) socket.leave(room);
  437. });
  438. socket.join(payload.room);
  439. return resolve();
  440. });
  441. }
  442. // UNKNOWN
  443. // eslint-disable-next-line require-jsdoc
  444. SOCKETS_JOIN_SONG_ROOM(payload) {
  445. // sockets, room
  446. return new Promise(resolve => {
  447. Object.keys(payload.sockets).forEach(socketKey => {
  448. const socket = payload.sockets[socketKey];
  449. const { rooms } = socket;
  450. Object.keys(rooms).forEach(roomKey => {
  451. const room = rooms[roomKey];
  452. if (room.indexOf("song.") !== -1) socket.leave(room);
  453. });
  454. socket.join(payload.room);
  455. });
  456. return resolve();
  457. });
  458. }
  459. // UNKNOWN
  460. // eslint-disable-next-line require-jsdoc
  461. SOCKETS_LEAVE_SONG_ROOMS(payload) {
  462. // sockets
  463. return new Promise(resolve => {
  464. Object.keys(payload.sockets).forEach(socketKey => {
  465. const socket = payload.sockets[socketKey];
  466. const { rooms } = socket;
  467. Object.keys(rooms).forEach(roomKey => {
  468. const room = rooms[roomKey];
  469. if (room.indexOf("song.") !== -1) socket.leave(room);
  470. });
  471. });
  472. resolve();
  473. });
  474. }
  475. /**
  476. * Emits arguments to any sockets that are in a specified a room
  477. *
  478. * @param {object} payload - object that contains the payload
  479. * @param {string} payload.room - the name of the room to emit arguments
  480. * @param {object} payload.args - any arguments to be emitted to the sockets in the specific room
  481. * @returns {Promise} - returns promise (reject, resolve)
  482. */
  483. async EMIT_TO_ROOM(payload) {
  484. const io = await IOModule.runJob("IO", {}, this);
  485. return new Promise(resolve => {
  486. const { sockets } = io.sockets;
  487. Object.keys(sockets).forEach(socketKey => {
  488. const socket = sockets[socketKey];
  489. if (socket.rooms[payload.room]) {
  490. socket.emit(...payload.args);
  491. }
  492. });
  493. return resolve();
  494. });
  495. }
  496. /**
  497. * Gets any sockets connected to a room
  498. *
  499. * @param {object} payload - object that contains the payload
  500. * @param {string} payload.room - the name of the room
  501. * @returns {Promise} - returns promise (reject, resolve)
  502. */
  503. async GET_ROOM_SOCKETS(payload) {
  504. const io = await IOModule.runJob("IO", {}, this);
  505. return new Promise(resolve => {
  506. const { sockets } = io.sockets;
  507. const roomSockets = [];
  508. Object.keys(sockets).forEach(socketKey => {
  509. const socket = sockets[socketKey];
  510. if (socket.rooms[payload.room]) roomSockets.push(socket);
  511. });
  512. return resolve(roomSockets);
  513. });
  514. }
  515. /**
  516. * Shuffles an array of songs
  517. *
  518. * @param {object} payload - object that contains the payload
  519. * @param {object} payload.array - an array of songs that should be shuffled
  520. * @returns {Promise} - returns promise (reject, resolve)
  521. */
  522. SHUFFLE(payload) {
  523. // array
  524. return new Promise(resolve => {
  525. const array = payload.array.slice();
  526. let currentIndex = payload.array.length;
  527. let temporaryValue;
  528. let randomIndex;
  529. // While there remain elements to shuffle...
  530. while (currentIndex !== 0) {
  531. // Pick a remaining element...
  532. randomIndex = Math.floor(Math.random() * currentIndex);
  533. currentIndex -= 1;
  534. // And swap it with the current element.
  535. temporaryValue = array[currentIndex];
  536. array[currentIndex] = array[randomIndex];
  537. array[randomIndex] = temporaryValue;
  538. }
  539. resolve({ array });
  540. });
  541. }
  542. /**
  543. * Creates an error
  544. *
  545. * @param {object} payload - object that contains the payload
  546. * @param {object} payload.error - object that contains the error
  547. * @param {string} payload.message - possible error message
  548. * @param {object} payload.errors - possible object that contains multiple errors
  549. * @returns {Promise} - returns promise (reject, resolve)
  550. */
  551. GET_ERROR(payload) {
  552. return new Promise(resolve => {
  553. let error = "An error occurred.";
  554. if (typeof payload.error === "string") error = payload.error;
  555. else if (payload.error.message) {
  556. if (payload.error.message !== "Validation failed") error = payload.error.message;
  557. else error = payload.error.errors[Object.keys(payload.error.errors)].message;
  558. }
  559. resolve(error);
  560. });
  561. }
  562. /**
  563. * Creates the gravatar url for a specified email address
  564. *
  565. * @param {object} payload - object that contains the payload
  566. * @param {string} payload.email - the email address
  567. * @returns {Promise} - returns promise (reject, resolve)
  568. */
  569. CREATE_GRAVATAR(payload) {
  570. return new Promise(resolve => {
  571. const hash = crypto.createHash("md5").update(payload.email).digest("hex");
  572. resolve(`https://www.gravatar.com/avatar/${hash}`);
  573. });
  574. }
  575. /**
  576. * @returns {Promise} - returns promise (reject, resolve)
  577. */
  578. DEBUG() {
  579. return new Promise(resolve => resolve());
  580. }
  581. }
  582. export default new _UtilsModule();