utils.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. import config from "config";
  2. import async from "async";
  3. import crypto from "crypto";
  4. import request from "request";
  5. import CoreClass from "../core";
  6. class UtilsModule extends CoreClass {
  7. constructor() {
  8. super("utils");
  9. this.youtubeRequestCallbacks = [];
  10. this.youtubeRequestsPending = 0;
  11. this.youtubeRequestsActive = false;
  12. }
  13. initialize() {
  14. return new Promise(resolve => {
  15. this.io = this.moduleManager.modules.io;
  16. this.db = this.moduleManager.modules.db;
  17. this.spotify = this.moduleManager.modules.spotify;
  18. this.cache = this.moduleManager.modules.cache;
  19. resolve();
  20. });
  21. }
  22. PARSE_COOKIES(payload) {
  23. // cookieString
  24. return new Promise((resolve, reject) => {
  25. const cookies = {};
  26. if (typeof payload.cookieString !== "string") return reject(new Error("Cookie string is not a string"));
  27. // eslint-disable-next-line array-callback-return
  28. payload.cookieString.split("; ").map(cookie => {
  29. cookies[cookie.substring(0, cookie.indexOf("="))] = cookie.substring(
  30. cookie.indexOf("=") + 1,
  31. cookie.length
  32. );
  33. });
  34. return resolve(cookies);
  35. });
  36. }
  37. // COOKIES_TO_STRING() {//cookies
  38. // return new Promise((resolve, reject) => {
  39. // let newCookie = [];
  40. // for (let prop in cookie) {
  41. // newCookie.push(prop + "=" + cookie[prop]);
  42. // }
  43. // return newCookie.join("; ");
  44. // });
  45. // }
  46. REMOVE_COOKIE(payload) {
  47. // cookieString, cookieName
  48. return new Promise((resolve, reject) => {
  49. let cookies;
  50. try {
  51. cookies = this.runJob("PARSE_COOKIES", {
  52. cookieString: payload.cookieString
  53. });
  54. } catch (err) {
  55. return reject(err);
  56. }
  57. delete cookies[payload.cookieName];
  58. return resolve(this.toString(cookies));
  59. });
  60. }
  61. HTML_ENTITIES(payload) {
  62. // str
  63. return new Promise(resolve => {
  64. resolve(
  65. String(payload.str)
  66. .replace(/&/g, "&")
  67. .replace(/</g, "&lt;")
  68. .replace(/>/g, "&gt;")
  69. .replace(/"/g, "&quot;")
  70. );
  71. });
  72. }
  73. async GENERATE_RANDOM_STRING(payload) {
  74. // length
  75. const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split("");
  76. const promises = [];
  77. for (let i = 0; i < payload.length; i += 1) {
  78. promises.push(
  79. this.runJob("GET_RANDOM_NUMBER", {
  80. min: 0,
  81. max: chars.length - 1
  82. })
  83. );
  84. }
  85. const randomNums = await Promise.all(promises);
  86. const randomChars = [];
  87. for (let i = 0; i < payload.length; i += 1) {
  88. randomChars.push(chars[randomNums[i]]);
  89. }
  90. return new Promise(resolve => resolve(randomChars.join("")));
  91. }
  92. async GET_SOCKET_FROM_ID(payload) {
  93. // socketId
  94. const io = await this.io.runJob("IO", {});
  95. return new Promise(resolve => resolve(io.sockets.sockets[payload.socketId]));
  96. }
  97. GET_RANDOM_NUMBER(payload) {
  98. // min, max
  99. return new Promise(resolve =>
  100. resolve(Math.floor(Math.random() * (payload.max - payload.min + 1)) + payload.min)
  101. );
  102. }
  103. CONVERT_TIME(payload) {
  104. // duration
  105. return new Promise(resolve => {
  106. let { duration } = payload;
  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 += parseInt(a[0]) * 3600;
  120. duration += parseInt(a[1]) * 60;
  121. duration += parseInt(a[2]);
  122. }
  123. if (a.length === 2) {
  124. duration += parseInt(a[0]) * 60;
  125. duration += parseInt(a[1]);
  126. }
  127. if (a.length === 1) {
  128. duration += parseInt(a[0]);
  129. }
  130. const hours = Math.floor(duration / 3600);
  131. const minutes = Math.floor((duration % 3600) / 60);
  132. const seconds = Math.floor((duration % 3600) % 60);
  133. resolve(
  134. (hours < 10 ? `0${hours}:` : `${hours}:`) +
  135. (minutes < 10 ? `0${minutes}:` : `${minutes}:`) +
  136. (seconds < 10 ? `0${seconds}` : seconds)
  137. );
  138. });
  139. }
  140. GUID() {
  141. return new Promise(resolve => {
  142. resolve(
  143. [1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1]
  144. .map(b =>
  145. b
  146. ? Math.floor((1 + Math.random()) * 0x10000)
  147. .toString(16)
  148. .substring(1)
  149. : "-"
  150. )
  151. .join("")
  152. );
  153. });
  154. }
  155. async SOCKET_FROM_SESSION(payload) {
  156. // socketId
  157. const io = await this.io.runJob("IO", {});
  158. return new Promise((resolve, reject) => {
  159. const ns = io.of("/");
  160. if (ns) {
  161. return resolve(ns.connected[payload.socketId]);
  162. }
  163. return reject();
  164. });
  165. }
  166. async SOCKETS_FROM_SESSION_ID(payload) {
  167. const io = await this.io.runJob("IO", {});
  168. return new Promise(resolve => {
  169. const ns = io.of("/");
  170. const sockets = [];
  171. if (ns) {
  172. return async.each(
  173. Object.keys(ns.connected),
  174. (id, next) => {
  175. const { session } = ns.connected[id];
  176. if (session.sessionId === payload.sessionId) sockets.push(session.sessionId);
  177. next();
  178. },
  179. () => {
  180. resolve({ sockets });
  181. }
  182. );
  183. }
  184. return resolve();
  185. });
  186. }
  187. async SOCKETS_FROM_USER(payload) {
  188. const io = await this.io.runJob("IO", {});
  189. return new Promise((resolve, reject) => {
  190. const ns = io.of("/");
  191. const sockets = [];
  192. if (ns) {
  193. return async.each(
  194. Object.keys(ns.connected),
  195. (id, next) => {
  196. const { session } = ns.connected[id];
  197. this.cache
  198. .runJob("HGET", {
  199. table: "sessions",
  200. key: session.sessionId
  201. })
  202. .then(session => {
  203. if (session && session.userId === payload.userId) sockets.push(ns.connected[id]);
  204. next();
  205. })
  206. .catch(err => {
  207. next(err);
  208. });
  209. },
  210. err => {
  211. if (err) return reject(err);
  212. return resolve({ sockets });
  213. }
  214. );
  215. }
  216. return resolve();
  217. });
  218. }
  219. async SOCKETS_FROM_IP(payload) {
  220. const io = await this.io.runJob("IO", {});
  221. return new Promise(resolve => {
  222. const ns = io.of("/");
  223. const sockets = [];
  224. if (ns) {
  225. return async.each(
  226. Object.keys(ns.connected),
  227. (id, next) => {
  228. const { session } = ns.connected[id];
  229. this.cache
  230. .runJob("HGET", {
  231. table: "sessions",
  232. key: session.sessionId
  233. })
  234. .then(session => {
  235. if (session && ns.connected[id].ip === payload.ip) sockets.push(ns.connected[id]);
  236. next();
  237. })
  238. .catch(() => next());
  239. },
  240. () => {
  241. resolve({ sockets });
  242. }
  243. );
  244. }
  245. return resolve();
  246. });
  247. }
  248. async SOCKETS_FROM_USER_WITHOUT_CACHE(payload) {
  249. const io = await this.io.runJob("IO", {});
  250. return new Promise(resolve => {
  251. const ns = io.of("/");
  252. const sockets = [];
  253. if (ns) {
  254. return async.each(
  255. Object.keys(ns.connected),
  256. (id, next) => {
  257. const { session } = ns.connected[id];
  258. if (session.userId === payload.userId) sockets.push(ns.connected[id]);
  259. next();
  260. },
  261. () => {
  262. resolve({ sockets });
  263. }
  264. );
  265. }
  266. return resolve();
  267. });
  268. }
  269. SOCKET_LEAVE_ROOMS(payload) {
  270. // socketId
  271. return new Promise((resolve, reject) => {
  272. let socket;
  273. try {
  274. socket = this.runJob("SOCKET_FROM_SESSION", {
  275. socketId: payload.socketId
  276. });
  277. } catch (err) {
  278. return reject(err);
  279. }
  280. const { rooms } = socket;
  281. for (let room = 0, roomKeys = Object.keys(rooms); room < roomKeys.length; room += 1) {
  282. socket.leave(room);
  283. }
  284. return resolve();
  285. });
  286. }
  287. async SOCKET_JOIN_ROOM(payload) {
  288. const socket = await this.runJob("SOCKET_FROM_SESSION", {
  289. socketId: payload.socketId
  290. });
  291. // socketId, room
  292. return new Promise(resolve => {
  293. const { rooms } = socket;
  294. for (let room = 0, roomKeys = Object.keys(rooms); room < roomKeys.length; room += 1) {
  295. socket.leave(room);
  296. }
  297. socket.join(payload.room);
  298. return resolve();
  299. });
  300. }
  301. async SOCKET_JOIN_SONG_ROOM(payload) {
  302. // socketId, room
  303. const socket = await this.runJob("SOCKET_FROM_SESSION", {
  304. socketId: payload.socketId
  305. });
  306. return new Promise(resolve => {
  307. const { rooms } = socket;
  308. for (let room = 0, roomKeys = Object.keys(rooms); room < roomKeys.length; room += 1) {
  309. if (room.indexOf("song.") !== -1) socket.leave(rooms);
  310. }
  311. socket.join(payload.room);
  312. return resolve();
  313. });
  314. }
  315. SOCKETS_JOIN_SONG_ROOM(payload) {
  316. // sockets, room
  317. return new Promise(resolve => {
  318. for (let id = 0, socketKeys = Object.keys(payload.sockets); id < socketKeys.length; id += 1) {
  319. const socket = payload.sockets[socketKeys[id]];
  320. const { rooms } = socket;
  321. for (let room = 0, roomKeys = Object.keys(rooms); room < roomKeys.length; room += 1) {
  322. if (room.indexOf("song.") !== -1) socket.leave(room);
  323. }
  324. socket.join(payload.room);
  325. }
  326. return resolve();
  327. });
  328. }
  329. SOCKETS_LEAVE_SONG_ROOMS(payload) {
  330. // sockets
  331. return new Promise(resolve => {
  332. for (let id = 0, socketKeys = Object.keys(payload.sockets); id < socketKeys.length; id += 1) {
  333. const socket = payload.sockets[socketKeys[id]];
  334. const { rooms } = socket;
  335. for (let room = 0, roomKeys = Object.keys(rooms); room < roomKeys.length; room += 1) {
  336. if (room.indexOf("song.") !== -1) socket.leave(room);
  337. }
  338. }
  339. resolve();
  340. });
  341. }
  342. async EMIT_TO_ROOM(payload) {
  343. // room, ...args
  344. const io = await this.io.runJob("IO", {});
  345. return new Promise(resolve => {
  346. const { sockets } = io.sockets;
  347. for (let id = 0, socketKeys = Object.keys(sockets); id < socketKeys.length; id += 1) {
  348. const socket = sockets[socketKeys[id]];
  349. if (socket.rooms[payload.room]) {
  350. socket.emit(...payload.args);
  351. }
  352. }
  353. return resolve();
  354. });
  355. }
  356. async GET_ROOM_SOCKETS(payload) {
  357. const io = await this.io.runJob("IO", {});
  358. // room
  359. return new Promise(resolve => {
  360. const { sockets } = io.sockets;
  361. const roomSockets = [];
  362. for (let id = 0, socketKeys = Object.keys(sockets); id < socketKeys.length; id += 1) {
  363. const socket = sockets[socketKeys[id]];
  364. if (socket.rooms[payload.room]) roomSockets.push(socket);
  365. }
  366. return resolve(roomSockets);
  367. });
  368. }
  369. GET_SONG_FROM_YOUTUBE(payload) {
  370. // songId, cb
  371. return new Promise((resolve, reject) => {
  372. this.youtubeRequestCallbacks.push({
  373. cb: () => {
  374. this.youtubeRequestsActive = true;
  375. const youtubeParams = [
  376. "part=snippet,contentDetails,statistics,status",
  377. `id=${encodeURIComponent(payload.songId)}`,
  378. `key=${config.get("apis.youtube.key")}`
  379. ].join("&");
  380. request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
  381. this.youtubeRequestCallbacks.splice(0, 1);
  382. if (this.youtubeRequestCallbacks.length > 0) {
  383. this.youtubeRequestCallbacks[0].cb(this.youtubeRequestCallbacks[0].songId);
  384. } else this.youtubeRequestsActive = false;
  385. if (err) {
  386. console.error(err);
  387. return null;
  388. }
  389. body = JSON.parse(body);
  390. if (body.error) {
  391. console.log("ERROR", "GET_SONG_FROM_YOUTUBE", `${body.error.message}`);
  392. return reject(new Error("An error has occured. Please try again later."));
  393. }
  394. if (body.items[0] === undefined)
  395. return reject(
  396. new Error("The specified video does not exist or cannot be publicly accessed.")
  397. );
  398. // TODO Clean up duration converter
  399. let dur = body.items[0].contentDetails.duration;
  400. dur = dur.replace("PT", "");
  401. let duration = 0;
  402. dur = dur.replace(/([\d]*)H/, (v, v2) => {
  403. v2 = Number(v2);
  404. duration = v2 * 60 * 60;
  405. return "";
  406. });
  407. dur = dur.replace(/([\d]*)M/, (v, v2) => {
  408. v2 = Number(v2);
  409. duration += v2 * 60;
  410. return "";
  411. });
  412. // eslint-disable-next-line no-unused-vars
  413. dur = dur.replace(/([\d]*)S/, (v, v2) => {
  414. v2 = Number(v2);
  415. duration += v2;
  416. return "";
  417. });
  418. const song = {
  419. songId: body.items[0].id,
  420. title: body.items[0].snippet.title,
  421. duration
  422. };
  423. return resolve({ song });
  424. });
  425. },
  426. songId: payload.songId
  427. });
  428. if (!this.youtubeRequestsActive) {
  429. this.youtubeRequestCallbacks[0].cb(this.youtubeRequestCallbacks[0].songId);
  430. }
  431. });
  432. }
  433. FILTER_MUSIC_VIDEOS_YOUTUBE(payload) {
  434. // videoIds, cb
  435. return new Promise((resolve, reject) => {
  436. /**
  437. * @param {Function} cb2 - callback
  438. */
  439. function getNextPage(cb2) {
  440. const localVideoIds = payload.videoIds.splice(0, 50);
  441. const youtubeParams = [
  442. "part=topicDetails",
  443. `id=${encodeURIComponent(localVideoIds.join(","))}`,
  444. `maxResults=50`,
  445. `key=${config.get("apis.youtube.key")}`
  446. ].join("&");
  447. request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
  448. if (err) {
  449. console.error(err);
  450. return reject(new Error("Failed to find playlist from YouTube"));
  451. }
  452. body = JSON.parse(body);
  453. if (body.error) {
  454. console.log("ERROR", "FILTER_MUSIC_VIDEOS_YOUTUBE", `${body.error.message}`);
  455. return reject(new Error("An error has occured. Please try again later."));
  456. }
  457. const songIds = [];
  458. body.items.forEach(item => {
  459. const songId = item.id;
  460. if (!item.topicDetails) return;
  461. if (item.topicDetails.relevantTopicIds.indexOf("/m/04rlf") !== -1) {
  462. songIds.push(songId);
  463. }
  464. });
  465. if (payload.videoIds.length > 0) {
  466. return getNextPage(newSongIds => {
  467. cb2(songIds.concat(newSongIds));
  468. });
  469. }
  470. return cb2(songIds);
  471. });
  472. }
  473. if (payload.videoIds.length === 0) resolve({ songIds: [] });
  474. else getNextPage(songIds => resolve({ songIds }));
  475. });
  476. }
  477. GET_PLAYLIST_FROM_YOUTUBE(payload) {
  478. // payload includes: url, musicOnly
  479. return new Promise((resolve, reject) => {
  480. const local = this;
  481. const name = "list".replace(/[\\[]/, "\\[").replace(/[\]]/, "\\]");
  482. const regex = new RegExp(`[\\?&]${name}=([^&#]*)`);
  483. const splitQuery = regex.exec(payload.url);
  484. if (!splitQuery) {
  485. console.log("ERROR", "GET_PLAYLIST_FROM_YOUTUBE", "Invalid YouTube playlist URL query.");
  486. return reject(new Error("An error has occured. Please try again later."));
  487. }
  488. const playlistId = splitQuery[1];
  489. /**
  490. * @param {string} pageToken - page token for YouTube API
  491. * @param {Array} songs - array of sogns
  492. */
  493. function getPage(pageToken, songs) {
  494. const nextPageToken = pageToken ? `pageToken=${pageToken}` : "";
  495. const youtubeParams = [
  496. "part=contentDetails",
  497. `playlistId=${encodeURIComponent(playlistId)}`,
  498. `maxResults=50`,
  499. `key=${config.get("apis.youtube.key")}`,
  500. nextPageToken
  501. ].join("&");
  502. request(
  503. `https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`,
  504. async (err, res, body) => {
  505. if (err) {
  506. console.error(err);
  507. return reject(new Error("Failed to find playlist from YouTube"));
  508. }
  509. body = JSON.parse(body);
  510. if (body.error) {
  511. console.log("ERROR", "GET_PLAYLIST_FROM_YOUTUBE", `${body.error.message}`);
  512. return reject(new Error("An error has occured. Please try again later."));
  513. }
  514. songs = songs.concat(body.items);
  515. if (body.nextPageToken) return getPage(body.nextPageToken, songs);
  516. songs = songs.map(song => song.contentDetails.videoId);
  517. if (!payload.musicOnly) return resolve({ songs });
  518. return local
  519. .runJob("FILTER_MUSIC_VIDEOS_YOUTUBE", {
  520. videoIds: songs.slice()
  521. })
  522. .then(filteredSongs => {
  523. resolve({ filteredSongs, songs });
  524. });
  525. }
  526. );
  527. }
  528. return getPage(null, []);
  529. });
  530. }
  531. async GET_SONG_FROM_SPOTIFY(payload) {
  532. // song
  533. const token = await this.spotify.runJob("GET_TOKEN", {});
  534. return new Promise((resolve, reject) => {
  535. if (!config.get("apis.spotify.enabled")) return reject(new Error("Spotify is not enabled."));
  536. const song = { ...payload.song };
  537. const spotifyParams = [`q=${encodeURIComponent(payload.song.title)}`, `type=track`].join("&");
  538. const options = {
  539. url: `https://api.spotify.com/v1/search?${spotifyParams}`,
  540. headers: {
  541. Authorization: `Bearer ${token}`
  542. }
  543. };
  544. return request(options, (err, res, body) => {
  545. if (err) console.error(err);
  546. body = JSON.parse(body);
  547. if (body.error) console.error(body.error);
  548. for (let i = 0, bodyKeys = Object.keys(body); i < bodyKeys.length; i += 1) {
  549. const { items } = body[i];
  550. for (let j = 0, itemKeys = Object.keys(body); j < itemKeys.length; j += 1) {
  551. const item = items[j];
  552. let hasArtist = false;
  553. for (let k = 0; k < item.artists.length; k += 1) {
  554. const artist = item.artists[k];
  555. if (song.title.indexOf(artist.name) !== -1) hasArtist = true;
  556. }
  557. if (hasArtist && song.title.indexOf(item.name) !== -1) {
  558. song.duration = item.duration_ms / 1000;
  559. song.artists = item.artists.map(artist => artist.name);
  560. song.title = item.name;
  561. song.explicit = item.explicit;
  562. song.thumbnail = item.album.images[1].url;
  563. break;
  564. }
  565. }
  566. }
  567. resolve({ song });
  568. });
  569. });
  570. }
  571. async GET_SONGS_FROM_SPOTIFY(payload) {
  572. // title, artist
  573. const token = await this.spotify.runJob("GET_TOKEN", {});
  574. return new Promise((resolve, reject) => {
  575. if (!config.get("apis.spotify.enabled")) return reject(new Error("Spotify is not enabled."));
  576. const spotifyParams = [`q=${encodeURIComponent(payload.title)}`, `type=track`].join("&");
  577. const options = {
  578. url: `https://api.spotify.com/v1/search?${spotifyParams}`,
  579. headers: {
  580. Authorization: `Bearer ${token}`
  581. }
  582. };
  583. return request(options, (err, res, body) => {
  584. if (err) return console.error(err);
  585. body = JSON.parse(body);
  586. if (body.error) return console.error(body.error);
  587. const songs = [];
  588. for (let i = 0, bodyKeys = Object.keys(body); i < bodyKeys.length; i += 1) {
  589. const { items } = body[i];
  590. for (let j = 0, itemKeys = Object.keys(body); j < itemKeys.length; j += 1) {
  591. const item = items[j];
  592. let hasArtist = false;
  593. for (let k = 0; k < item.artists.length; k += 1) {
  594. const localArtist = item.artists[k];
  595. if (payload.artist.toLowerCase() === localArtist.name.toLowerCase()) hasArtist = true;
  596. }
  597. if (
  598. hasArtist &&
  599. (payload.title.indexOf(item.name) !== -1 || item.name.indexOf(payload.title) !== -1)
  600. ) {
  601. const song = {};
  602. song.duration = item.duration_ms / 1000;
  603. song.artists = item.artists.map(artist => artist.name);
  604. song.title = item.name;
  605. song.explicit = item.explicit;
  606. song.thumbnail = item.album.images[1].url;
  607. songs.push(song);
  608. }
  609. }
  610. }
  611. return resolve({ songs });
  612. });
  613. });
  614. }
  615. SHUFFLE(payload) {
  616. // array
  617. return new Promise(resolve => {
  618. const array = payload.array.slice();
  619. let currentIndex = payload.array.length;
  620. let temporaryValue;
  621. let randomIndex;
  622. // While there remain elements to shuffle...
  623. while (currentIndex !== 0) {
  624. // Pick a remaining element...
  625. randomIndex = Math.floor(Math.random() * currentIndex);
  626. currentIndex -= 1;
  627. // And swap it with the current element.
  628. temporaryValue = array[currentIndex];
  629. array[currentIndex] = array[randomIndex];
  630. array[randomIndex] = temporaryValue;
  631. }
  632. resolve({ array });
  633. });
  634. }
  635. GET_ERROR(payload) {
  636. // err
  637. return new Promise(resolve => {
  638. let error = "An error occurred.";
  639. if (typeof payload.error === "string") error = payload.error;
  640. else if (payload.error.message) {
  641. if (payload.error.message !== "Validation failed") error = payload.error.message;
  642. else error = payload.error.errors[Object.keys(payload.error.errors)].message;
  643. }
  644. resolve(error);
  645. });
  646. }
  647. CREATE_GRAVATAR(payload) {
  648. // email
  649. return new Promise(resolve => {
  650. const hash = crypto.createHash("md5").update(payload.email).digest("hex");
  651. resolve(`https://www.gravatar.com/avatar/${hash}`);
  652. });
  653. }
  654. DEBUG() {
  655. return new Promise(resolve => resolve());
  656. }
  657. }
  658. export default new UtilsModule();