Guest User

Untitled

a guest
Aug 1st, 2017
136
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. "use strict";
  2.  
  3. /*
  4. *   AiurBot
  5. *   expandable plug.dj NodeJS bot with some basicBot adaptations (I did not write basicBot!) and then some.
  6. *   written by zeratul- (https://github.com/zeratul0) specially for https://plug.dj/its-a-trap-and-edm
  7. *   version 0.4.8
  8. *   ALPHA TESTING
  9. *   Copyright 2016-2017 zeratul0
  10. *   You may edit and redistribute this program for your own personal (not commercial) use as long as the author remains credited. Any profit or monetary gain
  11. *   must be redirected to the author.
  12. *
  13. *   Remember to regularly backup your data/seenUsers.json and blacklists/*.json files in case an error screws them up! That shouldn't happen,
  14. *   but hey, it's alpha.
  15. *
  16. *   Referred to https://github.com/SooYou/plugged for endpoint info, and stackoverflow for help with creating a working CLI input
  17. */
  18.  
  19. const cc = require('cli-color');
  20. const fs = require('graceful-fs');
  21. const ent = require('html-entities').XmlEntities;
  22. const req = require('request');
  23. const WebSocket = require('ws');
  24. const plugLogin = require('plug-login');
  25. const util = require('util');
  26. const readline = require('readline');
  27. const weather = require('Openweather-Node')
  28. const openWeatherMapAppId = 'd178018e284b0f6173cd1ad56b9d28f2';
  29. const userdata = function() {
  30.     try {
  31.         const data = require('./userdata.js');
  32.         if (data.username && data.username.trim() !== "" && data.password && data.password.trim() !== "")
  33.             return data;
  34.     } catch (e) {
  35.         if (e.code !== "MODULE_NOT_FOUND")
  36.             error(cc.red(e));
  37.     }
  38.     return null;
  39. };
  40. const core = require('./coreOptions.js');
  41.  
  42. const HOME = core.HOME;
  43. const STARTASNORMALUSER = core.STARTASNORMALUSER;
  44. const EXTERNALSETTINGS = core.EXTERNALSETTINGS;
  45. const blacklists = core.blacklists;
  46. const DEBUG = core.DEBUG;
  47. let TRIGGER = core.TRIGGER;
  48.  
  49. const PLATFORM = ((process && process.platform) ? process.platform : "win32");  //host platform OS identifier string
  50. const SC_CLIENT_ID = 'f4fdc0512b7990d11ffc782b2c17e8c2';  //SoundCloud Client ID
  51. const YT_API_KEY = 'AIzaSyAd50UWayYE17Wn7V6NhsnAfoj25wyrkCw';  //YouTube API Key
  52. const OWM_API_KEY = 'd178018e284b0f6173cd1ad56b9d28f2';  //Open Weather Map API Key  
  53. const TITLE = 'MthBot';  //bot title
  54. const VER = '0.2.3 beta';  //bot version
  55. const AUTHOR = 'zeratul0';  //bot author (github)
  56. const STARTTIME = Date.now();  //the time the bot was started
  57. const MAX_DISC_TIME = 3600000;  //1 hour; MUST keep above 1000ms; time after a user disconnects when they can use !dc
  58. const commands = {};  //holds chat commands, defined at the bottom
  59. const HEARTBEAT = {  //heartbeat
  60.     last: 0,
  61.     timer: null
  62. };
  63.  
  64. let HIGHWAY = 21; // space in chat between UID/CID and username where user symbols are placed. don't change
  65.  
  66. //*TIME: timers
  67. let MEMORYTIME = null;
  68. let SAVESEENTIME = null;
  69. let AFKCHECKTIME = null;
  70. let AUTODISABLETIME = null;
  71. let ANNOUNCEMENTTIME = null;
  72. let MOTDTIME = null;
  73. let STUCKSKIPTIME = null;
  74. let wss = null;  //websocket
  75. let room = null;  //room
  76. let MENTIONREGEX = null;  //regex for mentions in chat, see handleChat
  77. let DIDAUTOLOGIN = false;
  78.  
  79. let sessJar = {};  //user session jar generated on login
  80. let me = {};  //your user data, grabbed each time you join a room
  81. let seen = {};  //holds user records, gets populated with data/seenUsers.json
  82. let disconnects = {};  //holds disconnect time/last waitlist position if someone leaves while on the waitlist
  83. let MEMORY = {rss:0, heapTotal:0, heapUsed:0};  //memory usage
  84. let LASTSENTMSG = 0;  //last sent message
  85. let STARTEDINPUT = false;  //don't change
  86. const constSettings = ["announcements", "messagecommands", "eightballchoices", "_fn", "skipreasons"];
  87. let sentAnnouncements = [];
  88.  
  89. const BotSettings = {
  90.  
  91.     doJoinMessage:false, //if true, sends a message upon joining a room; ":: AiurBot vx.xx loaded ::"
  92.  
  93.     autoWoot:true,
  94.  
  95.     welcomeUsers:true, //welcomes users joining the room
  96.  
  97.     timestampUse:true,  //show timestamp in console
  98.     timestampSeconds:true,  //show seconds on timestamp
  99.     timestampTwelveHours:false, //if true, shows timestamp in 12h format instead of 24h
  100.     timestampColor:'cyan',  //color of timestamp
  101.  
  102.     titleShowRoom:true,     //show current room in title
  103.     titleShowMemory:true,   //show memory usage in title
  104.     titleShowUsers:true,    //show user count in title
  105.  
  106.     chatHighlightMention:true,  //use @ highlights
  107.     chatShowCID:true,       //shows full CID of chat message; if false, just shows UID
  108.     chatShowWoot:false,
  109.     chatShowRepeatWoot:true,
  110.     chatShowGrab:true,
  111.     chatShowRepeatGrab:true,
  112.     chatShowMeh:true,
  113.     chatShowRepeatMeh:true,
  114.     chatDeleteTriggerMessages:true,  //delete ALL messages beginning with TRIGGER 1 second after they are sent
  115.     chatDeleteResponses:true,   //delete afkdisable/joindisable response messages 1 second after they are sent
  116.     promptColor:'redBright',  //color of $ on the cmd prompt
  117.  
  118.     seenAutoSave:true,  //saves user records to data/seenUsers.json every 10 minutes
  119.  
  120.     acceptRemoteCmd:false,  //allow bot creator to use local commands from the chat
  121.     allowRemoteBlacklistEdit:true,  //if true, allow "blacklist" command (that command must also be enabled to use it)
  122.  
  123.     announcementInterval: 540000, //ms; 9 minutes
  124.     announcementRandom:false,   //if true, picks random announcements instead of going in order
  125.     sendAnnouncements:true,    //if true, send announcements. if false, it's off
  126.  
  127.     allowChatCommands:true,  //if false, prevents any chat command from being used
  128.     useMessageCommands:true,  //if true, allows users to use messageCommands below
  129.  
  130.     doAFKCheck:false,  //if true, removes users from the waitlist if they have not sent a chat message in 2 hours
  131.  
  132.     doRoulette:false,  //if true, begins a roulette every hour
  133.  
  134.     doAutoDisable:true,  //if true, sends !joindisable and !afkdisable every hour
  135.  
  136.     autoStuckSkip:true, //if true, skips songs 30 seconds after their length if they are stuck.
  137.  
  138.     doSkipCheck:true,  //if true, checks skip conditions when a new song is played, but doesn't skip. MUST BE true for doAutoSkip to work; exists only to avoid using soundcloud/youtube APIs for checking unavailability if unnecessary
  139.     doAutoSkip:true,  //if true, skips songs if they meet skip conditions. doSkipCheck MUST BE true
  140.     hostBypassAutoSkip:true,  //if true, allows the host of the room to bypass autoskip conditions, unless the video is unavailable
  141.  
  142.     sendMOTD:false,  //if true, sends motd after each motdInterval
  143.     motdInterval: 1800000, //ms; 30 minutes
  144.     motd:"",  //motd message to send
  145.  
  146.     announcements:[
  147.         //"announcements are comprised simply of strings. these will be cycled through and sent whenever the announcementInterval passes.",
  148.         //"second announcement here"
  149.         //...
  150.     ],
  151.     messageCommands:{
  152.         //"name of command (make sure it has no spaces and does not conflict with an existing command!)" : "this will be sent in chat",
  153.         //"messagecmd2":"output this"
  154.         //...
  155.     "changelog":"/me Find the AiurBot changelog here: https://git.io/vM9FP"
  156.     },
  157.     skipReasons:{
  158.         //"reason" : "message that is sent",
  159.         "theme":"this song does not fit the room theme.",
  160.         "op":"this song is on the OP blacklist.",
  161.         "blacklisted":"this song was found on a blacklist.",
  162.         "history":"this song is already in the room's song history.",
  163.         "mix":"you played a mix, which is against the rules.",
  164.         "sound":"the song you played has bad sound quality, or no sound.",
  165.         "nsfw":"the song you played contains NSFW imagery or sound.",
  166.         "unavailable":"the song you played is not available for some users.",
  167.         "stuck":"the song you played was stuck."
  168.     },
  169.     eightBallChoices:[
  170.         "It is certain",
  171.         "It is decidedly so",
  172.         "Without a doubt",
  173.         "Yes, definitely",
  174.         "You may rely on it",
  175.         "As I see it, yes",
  176.         "Most likely",
  177.         "Outlook good",
  178.         "Yes",
  179.         "Signs point to yes",
  180.         "Reply hazy, try again",
  181.         "Ask again later",
  182.         "Better not tell you now",
  183.         "Cannot predict now",
  184.         "Concentrate and ask again",
  185.         "Don't count on it",
  186.         "My reply is no",
  187.         "My sources say no",
  188.         "Outlook not so good",
  189.         "Very doubtful"
  190.     ],
  191.     '_fn': {
  192.         "apply":()=>{
  193.             let j;
  194.             PROMPT = cc[BotSettings.promptColor]('$ ');
  195.             if (STARTEDINPUT) {
  196.                 rl.setPrompt(PROMPT + cc.blackBright('[chat] '), 2);
  197.             }
  198.             if (BotSettings.chatShowCID)
  199.                 HIGHWAY = 36;
  200.             else
  201.                 HIGHWAY = 21;
  202.             for (j of ["memory", "seen", "roulette", "announcements", "MOTD"]) {
  203.                 startTimer(j);
  204.             }
  205.             setTitle();
  206.             sentAnnouncements = [];
  207.             validateTrigger();
  208.         },
  209.         "loadFromFile":()=>{
  210.             if (!EXTERNALSETTINGS) return;
  211.             fs.readFile('settings.json', (e,data)=>{
  212.                 if (e) error(cc.red(e));
  213.                 else {
  214.                     try { JSON.parse(data); } catch (err) { error(cc.red('There was an error parsing the settings file.')); error(err); return; };
  215.                     if (JSON.parse(data)) {
  216.                         let i;
  217.  
  218.                         data = JSON.parse(data);
  219.                         for (i in data) {
  220.                             if (i.substr(0,1) !== '_') {
  221.                                 if (BotSettings.hasOwnProperty(i) && typeof BotSettings[i] === typeof data[i]) {
  222.                                     if ((i === 'promptColor' || i === 'timestampColor') && !cc[data[i]]) {
  223.                                         error(cc.red('settings.json: ')+cc.redBright(i)+cc.red(' does not have a valid color name.'));
  224.                                     } else if ((i === 'announcementInterval' || i === 'motdInterval') && (typeof data[i] !== "number" || data[i] < 5000)) {
  225.                                         error(cc.red('settings.json: ')+cc.redBright(i)+cc.red(' does not have a valid amount. Must be 5000+.'));
  226.                                     } else BotSettings[i] = data[i];
  227.                                 }
  228.                             }
  229.                         }
  230.                         BotSettings._fn.apply();
  231.                         log(cc.blueBright('settings.json loaded.'));
  232.                     }
  233.                 }
  234.             });
  235.         },
  236.         'listSettings':()=>{
  237.             let x = [],
  238.                 i;
  239.             for (i in BotSettings) {
  240.                 if (i.substr(0,1) !== '_' && !~arrFind(constSettings, i.toLowerCase())) {
  241.                     x.push(cc.green(i));
  242.                 }
  243.             }
  244.             nodeLog(cc.greenBright('List of bot options:')+' '+x.join(cc.blackBright(", ")));
  245.         },
  246.         'printSettings':()=>{
  247.             let i,
  248.                 val;
  249.             info(cc.cyanBright("OPTION") + cc.cyan(" | ") + cc.cyanBright("VALUE"));
  250.             for (i in BotSettings) {
  251.                 if (i.substr(0,1) !== '_') {
  252.                     if (typeof BotSettings[i] === "object")
  253.                         val = "[This is an ARRAY or OBJECT]";
  254.                     else
  255.                         val = BotSettings[i];
  256.                     info(cc.blueBright(i) + " " + cc.blue(val));
  257.                 }
  258.             }
  259.         },
  260.         'changeSetting':(key,value)=>{
  261.             if (value === 'true') value = true;
  262.             else if (value === 'false') value = false;
  263.             if (~arrFind(constSettings, key.toLowerCase())) {
  264.                 error(cc.redBright(key) + cc.red(" cannot be changed."));
  265.                 return;
  266.             } else {
  267.                 let i;
  268.                 for (i in BotSettings) {
  269.                     if (i.substr(0,1) !== '_' && i.toLowerCase() === key.toLowerCase()) {
  270.                         if (strIsNum(value)) value = parseInt(value);
  271.                         if (typeof value === typeof BotSettings[i]) {
  272.                             if (i === 'promptColor' || i === 'timestampColor')
  273.                                 if (!cc[value]) {
  274.                                     error(cc.redBright(value)+cc.red(' is not a valid color name.'));
  275.                                     return;
  276.                                 }
  277.                             if (i === 'announcementInterval' || i === 'motdInterval')
  278.                                 if (value < 5000) {
  279.                                     error(cc.redBright(value)+cc.red(' is not a valid interval amount. Must be 5000+.'));
  280.                                     return;
  281.                                 }
  282.                             if (i === 'motd') {
  283.  
  284.                             }
  285.                             BotSettings[i] = value;
  286.                             if (i === 'promptColor') {
  287.                                 PROMPT = cc[BotSettings.promptColor]('$ ');
  288.                                 if (STARTEDINPUT)
  289.                                     rl.setPrompt(PROMPT + cc.blackBright('[chat] '), 2);
  290.                             } else if (i === 'chatShowCID') {
  291.                                 if (value)
  292.                                     HIGHWAY = 36;
  293.                                 else
  294.                                     HIGHWAY = 21;
  295.                             } else if (i === 'titleShowMemory') {
  296.                                 setTitle();
  297.                                 startTimer("memory");
  298.                             } else if (i === 'titleShowRoom' || i === 'titleShowUsers') {
  299.                                 setTitle();
  300.                             } else if (i === 'seenAutoSave') {
  301.                                 startTimer("seen");
  302.                             } else if (i === 'doRoulette') {
  303.                                 startTimer("roulette");
  304.                             } else if (i === 'sendAnnouncements' || i === 'announcementInterval') {
  305.                                 startTimer("announcements");
  306.                             } else if (i === 'sendMOTD' || i === 'motdInterval') {
  307.                                 startTimer("MOTD");
  308.                             } else if (i === 'doAutoDisable') {
  309.                                 startTimer("autodisable");
  310.                             }
  311.                             nodeLog(cc.greenBright(i) + cc.green(' changed value to ') + cc.greenBright(value));
  312.                         } else if (typeof value !== typeof BotSettings[i]) {
  313.                             error(cc.redBright(value)+cc.red(' is not a valid datatype for ')+cc.redBright(i));
  314.                         } return;
  315.                     }
  316.                 }
  317.             }
  318.         }
  319.     }
  320. };
  321. let PROMPT = cc[BotSettings.promptColor]('$ ');
  322. const LOGTYPES = {
  323.     WAITLIST:cc.blueBright('[waitlist] '),
  324.     SKIP:cc.yellowBright('[skip] '),
  325.     WOOT:cc.greenBright('[woot] '),
  326.     WOOTAGAIN:cc.green('[woot] '),
  327.     GRAB:cc.magentaBright('[grab] '),
  328.     GRABAGAIN:cc.magenta('[grab] '),
  329.     MEH:cc.redBright('[meh] '),
  330.     MEHAGAIN:cc.red('[meh] '),
  331.     GIFT:cc.blue('[gift] '),
  332.     USER:cc.blueBright('[user] '),
  333.     NOTIFY:cc.yellowBright('[notify] '),
  334.     KILLSESSION:cc.redBright('[killSession] '),
  335.     STAFF:cc.magentaBright('[staff] '),
  336.     DELETE:cc.redBright('[delete] '),
  337.     ROOM:cc.blue('[room] '),
  338.     JOIN:cc.green('[join] '),
  339.     LEAVE:cc.red('[leave] '),
  340.     FRIEND:cc.cyanBright('[friend] '),
  341.     PLUG:cc.cyanBright('[plug.dj] '),
  342.     BAN:cc.redBright('[ban] '),
  343.     MUTE:cc.yellow('[mute] '),
  344. }
  345. const REASONS = [
  346.     { // ban reasons
  347.         1:'Spamming or trolling',
  348.         2:'Verbal abuse or offensive language',
  349.         3:'Playing offensive videos/songs',
  350.         4:'Repeatedly playing inappropriate genre(s)',
  351.         5:'Negative attitude'
  352.     },
  353.     { // mute reasons
  354.         1:'Violating community rules',
  355.         2:'Verbal abuse or harassment',
  356.         3:'Spamming or trolling',
  357.         4:'Offensive language',
  358.         5:'Negative attitude'
  359.     }
  360. ];
  361. const DURATIONS = [
  362.     {   //ban durations
  363.         'h':'One hour',
  364.         'd':'One day',
  365.         'f':'Forever'
  366.     },
  367.     {   //mute durations
  368.         's':'15 minutes',
  369.         'm':'30 minutes',
  370.         'l':'45 minutes'
  371.     }
  372. ];
  373.  
  374. function validateTrigger() {
  375.     if (typeof TRIGGER !== "string" || TRIGGER.length !== 1 || !~'!#$%^&*()_+-=`~.,?'.indexOf(TRIGGER)) {
  376.         TRIGGER = '!';
  377.         error(cc.red("Invalid trigger found. Reverted trigger to ") + cc.redBright("!"));
  378.     }
  379. }
  380.  
  381. function loadBlacklists() {
  382.  
  383.     let i,
  384.         item,
  385.         list;
  386.  
  387.     const LOAD = function(name, item) {
  388.         fs.readFile(item[0], (e,data)=>{
  389.             if (e) console.log(cc.red("loadBlacklist: ") + cc.redBright(e));
  390.             else {
  391.                 data+="";
  392.  
  393.                 let action = function(parsedData) {
  394.                     blacklists[name][1] = parsedData;
  395.                     console.log(cc.green('Successfully loaded blacklist "') + cc.greenBright(name) + cc.green('"'));
  396.                 };
  397.  
  398.                 try {
  399.                     list = JSON.parse(data);
  400.                     action(list);
  401.                 } catch (err) {
  402.                     console.log(cc.red('There was an error parsing a blacklist file: ') + cc.redBright(item[0]));
  403.                     if (err.stack) console.log(cc.red(err.stack));
  404.                 }
  405.             }
  406.         });
  407.     };
  408.  
  409.     for (i in blacklists) {
  410.         item = blacklists[i];
  411.         if (i.trim() !== "" && item.length === 2 && item[0] && typeof item[0] === "string" && /^blacklists\/.+?\.json$/gi.test(item[0])) {
  412.             LOAD(i, item);
  413.         } else {
  414.             console.log(cc.red('Error loading blacklists.'));
  415.         }
  416.     }
  417. }
  418.  
  419. function saveBlacklist(name) {
  420.     if (blacklists.hasOwnProperty(name) && typeof blacklists[name][0] === "string" && /^blacklists\/.+?\.json$/gi.test(blacklists[name][0]) && Object.prototype.toString.apply(blacklists[name][1]) === "[object Array]") {
  421.         fs.writeFile(blacklists[name][0], JSON.stringify(blacklists[name][1]), (e)=>{
  422.             if (e) error(cc.red("Cannot write blacklist " + name + ": " + e.stack));
  423.             else nodeLog(cc.green("Successfully saved blacklist \"" + name + "\""));
  424.         });
  425.     }
  426. }
  427.  
  428. function setTitle() {
  429.     let title = "";
  430.     if (me && me.username)
  431.         title += ent.decode(me.username) + ' | ';
  432.     title += TITLE + ' ' + VER;
  433.     if (room && room['slug']) {
  434.         if (BotSettings.titleShowRoom)
  435.             title += ' | room: ' + room['slug'];
  436.         if (BotSettings.titleShowUsers)
  437.             title += ' | ' + room.userlist.length + ((room.userlist.length !== 1) ? " Users (" : " User (") + getAFK().length + " AFK" + ((room.meta.guests > 0) ? ", " + room.meta.guests + ((room.meta.guests !== 1) ? " guests" : " guest") : "") + "), Waitlist: " + room.getWaitlist().length;
  438.     }
  439.     if (MEMORY['heapUsed'] >= 0 && BotSettings.titleShowMemory)
  440.         title += ' | Mem: ' + Math.ceil(MEMORY.heapUsed / 1024) + 'K'; //is this accurate?
  441.  
  442.     if (PLATFORM === "linux")
  443.         process.stdout.write("\x1B]0;" + title + "\x07");
  444.     else
  445.         process.title = title;
  446.     return void (0);
  447. }
  448.  
  449. /*function canDoStaffAction(role, cmdname) {
  450.     if (room && me && me.role) {
  451.         const role = parseInt(role);
  452.         if (isNaN(role)) return false;
  453.         else {
  454.             if (me.role >= role) {
  455.                 return true;
  456.             } else {
  457.                 let msg = cc.red('Unauthorized');
  458.                 if (cmdname) msg += cc.red(' for command: ')+cc.redBright(cmdname);
  459.                 msg += cc.red('. Needs role ')+cc.cyanBright(roleToString(role) + '(' + role + ')') + cc.red(', you are ') + cc.cyanBright(roleToString(me.role) + '(' + role + ')')+cc.red('.');
  460.                 error(msg);
  461.             }
  462.         }
  463.     }
  464.     return false;
  465. } UNUSED*/
  466.  
  467. function POST(endpoint, data, callback) {
  468.     req.post('https://plug.dj/'+endpoint, {json: true, jar: sessJar, body: data}, function(e,r,b) {
  469.         if (e) error(cc.red("POST ERROR: (" + endpoint + "): " + e));
  470.         else if (b && typeof callback === "function") callback(b);
  471.     });
  472. }
  473.  
  474. function PUT(endpoint, data, callback) {
  475.     req.put('https://plug.dj/'+endpoint, {json: true, jar: sessJar, body: data}, function(e,r,b) {
  476.         if (e) error(cc.red("PUT ERROR: (" + endpoint + "): " + e));
  477.         else if (b && typeof callback === "function") callback(b);
  478.     });
  479. }
  480.  
  481. function GET(endpoint, callback) {
  482.     req.get('https://plug.dj/'+endpoint, {jar: sessJar, json: true}, function(e,r,b) {
  483.         if (e) error(cc.red("GET ERROR: (" + endpoint + "): " + e));
  484.         else if (b && typeof callback === "function") callback(b);
  485.     });
  486. }
  487.  
  488. function DELETE(endpoint, callback) {
  489.     req.del('https://plug.dj/'+endpoint, {jar: sessJar, json: true}, function(e,r,b) {
  490.         if (e) error(cc.red("DELETE ERROR: (" + endpoint + "): " + e));
  491.         else if (b && typeof callback === "function") callback(b);
  492.     });
  493. }
  494.  
  495. let rl = readline.createInterface({
  496.     input: process.stdin,
  497.     output: process.stdout
  498. });
  499.  
  500. function login() {
  501.  
  502.     if (!HOME || HOME.trim() === '') {
  503.         return console.log('\n' + cc.redBright("HOME is not defined! You must specify a roomslug for HOME within coreOptions.js, which is the room joined upon logging in. A roomslug is what's found at the end of a plug.dj room's URL.\nPress CTRL+C or close the window to exit."));
  504.     } else if (HOME === '-8299715266665171479') {
  505.         console.log('\n' + cc.yellowBright("HOME is set to -8299715266665171479, the default placeholder room. Change it within coreOptions.js to set the default room the bot joins upon logging in."));
  506.     }
  507.  
  508.     const pluglogin = function(u,p,isAuto) {
  509.  
  510.         plugLogin.user(u, p, {authToken: true}, (err, res) => {
  511.             u = p = "";
  512.             if (err) {login((isAuto ? cc.red("\nAuto login failed. Check your userdata.js.\n") : '') + cc.redBright('ERROR: ' + err.message + (err.status === "notAuthorized" ? '\nDid you incorrectly type your username or password?' : '')));}//throw err;
  513.             else {
  514.                 sessJar = res.jar;
  515.                 connect(res.token);
  516.                 if (STARTASNORMALUSER) {
  517.                     let i,
  518.                         settingsToDisable = ["doJoinMessage", "allowChatCommands", "welcomeUsers", "chatDeleteTriggerMessages", "chatDeleteResponses", "acceptRemoteCmd", "sendAnnouncements", "useMessageCommands", "doAFKCheck", "doRoulette", "doAutoDisable", "doAutoSkip", "sendMOTD", "autoStuckSkip", "allowRemoteBlacklistEdit"];
  519.                     for (i = 0; i < settingsToDisable.length; i++) {
  520.                         BotSettings[settingsToDisable[i]] = false;
  521.                     }
  522.                 }
  523.             }
  524.         });
  525.     };
  526.  
  527.     setTitle(false);
  528.     startTimer("memory");
  529.  
  530.     if (arguments[0])
  531.         console.log(arguments[0]);
  532.  
  533.     if (!DIDAUTOLOGIN) {
  534.         const ud = userdata();
  535.         if (ud) {
  536.             DIDAUTOLOGIN = true;
  537.             return pluglogin(ud.username, ud.password, true);
  538.         }
  539.     }
  540.  
  541.     console.log(cc.redBright('\n-- plug.dj login --\n'));
  542.     let username = "";
  543.     let password = "";
  544.     rl.history = [];
  545.     rl.question(PROMPT + cc.red('[INSECURE] ') + cc.blackBright('Email Address: '), function(un) {
  546.         username = un;
  547.         //http://stackoverflow.com/questions/24037545/how-to-hide-password-in-the-nodejs-console -- referred to Hors Sujet's answer for the password prompt
  548.         process.stdin.on('data', function pwf(data) {switch (data + "") { case '\n':case '\r':case '\u0004':process.stdin.removeListener('data', pwf); rl.history = []; break; default: process.stdout.write('\u001b[2K\u001b[200D' + PROMPT + cc.blackBright('           Password: ') + '*'.repeat(rl.line.length)); break;}});
  549.         rl.question(PROMPT + cc.blackBright('           Password: '), function(pw) {
  550.             rl.history = [];
  551.             password = pw;
  552.             console.log('');
  553.             pluglogin(un, pw, false);
  554.         });
  555.     });
  556.  
  557. }
  558.  
  559. function joinRoom(roomslug) {
  560.     if (room) room.roulette._cleanup();
  561.     clearInterval(SAVESEENTIME);
  562.     clearInterval(AFKCHECKTIME);
  563.     clearInterval(AUTODISABLETIME);
  564.     clearTimeout(ANNOUNCEMENTTIME);
  565.     clearTimeout(HEARTBEAT.timer);
  566.     clearTimeout(MOTDTIME);
  567.     const prevRoom = room;
  568.     room = null;
  569.     POST('_/rooms/join', {slug: roomslug}, (data)=>{
  570.         if (data.status !== "ok") {
  571.             error(cc.red("Error joining room: ") + cc.redBright(data.status));
  572.             if (prevRoom) joinRoom(prevRoom.slug);
  573.             return;
  574.         }
  575.         console.log('\n'+cc.blackBright('-'.repeat(process.stdout.columns)));
  576.         console.log(cc.magentaBright('Joined room: ') + cc.blackBright('https://plug.dj/') + cc.redBright(roomslug));
  577.         room = new Room(roomslug);
  578.         GET('_/rooms/state', (data)=>{
  579.             const body = data.data[0],
  580.                 myRole = body.role;
  581.             let i;
  582.             for (i in body.users) {
  583.                 body.users[i]['lastActivity'] = Date.now();
  584.                 body.users[i]['isAFK'] = false;
  585.                 body.users[i]['warn'] = 0;
  586.                 if (body.users[i].id) {
  587.                     addSeenUser(body.users[i].id);
  588.                     if (seen[room.slug] && seen[room.slug][body.users[i].id]) {
  589.                         seen[room.slug][body.users[i].id].lastWelcome = Date.now();
  590.                     }
  591.                 }
  592.             }
  593.             room.userlist = body.users;
  594.             room.setPlaybackFromState(body);
  595.             room.booth = body.booth;
  596.             room.meta = body.meta;
  597.             room.meta.description = ent.decode(room.meta.description.trim());
  598.             room.meta.welcome = ent.decode(room.meta.welcome.trim());
  599.             room.meta.name = ent.decode(room.meta.name.trim());
  600.             room.meta.hostName = ent.decode(room.meta.hostName);
  601.             room.votes = body.votes;
  602.             room.grabs = body.grabs;
  603.             if (room.meta.welcome)
  604.                 console.log('\n\n' + timestamp() + cc.blueBright('/// ') + cc.greenBright(room.meta.welcome.trim()) + cc.blueBright(' ///'));
  605.             if (body['playback']['media'] && body['booth']['currentDJ']) {
  606.                 let unm = getUser(body.booth.currentDJ);
  607.                 if (!~unm) unm = cc.cyan('(unavailable)');
  608.                 else unm = colorizeName(unm);
  609.                 console.log('\n');
  610.                 log(cc.blue('/////////// ') + cc.blueBright('Playing') + cc.blue(' ::: ') + cc.cyanBright(body.playback.media.author) + cc.cyan(' - ') + cc.cyanBright(body.playback.media.title) + cc.blue(' | ') + cc.blueBright(secsToTime(body.playback.media.duration)) + cc.blue(' | ') + cc.blueBright('current DJ: ') + unm + cc.blue(' ///////////')+'\n\n');
  611.             }
  612.             else {
  613.                 console.log('\n');
  614.                 log(cc.blue('/////////// ') + cc.cyanBright('Nothing is currently playing.') + cc.blue(' ///////////')+'\n\n');
  615.             }
  616.             GET('_/playlists', (data)=>{
  617.                 const body = data.data;
  618.                 let i;
  619.                 room.playlists = body;
  620.                 for (i = 0; i < body.length; i++) {
  621.                     if (body[i].active) {
  622.                         room.activePlaylist = body[i];
  623.                         break;
  624.                     }
  625.                 }
  626.                 GET('_/rooms/history', (data)=>{
  627.                     const body = data.data;
  628.                     let i;
  629.                     for (i in body) {
  630.                         const item = body[i];
  631.                         addHistoryItem(item.id, item.media.format, item.media.cid, item.timestamp);
  632.                     }
  633.                     GET('_/users/me', (data)=>{
  634.                         me = data.data[0];
  635.                         me.role = myRole;
  636.                         addUser(me);
  637.                         addSeenUser(me.id);
  638.                         if (typeof me.username === "string")
  639.                             me.username = ent.decode(me.username);
  640.                         if (seen && seen[room.slug] && seen[room.slug][me.id]) { seen[room.slug][me.id].lastWelcome = Date.now(); }
  641.                         displayUsers();
  642.                         displayWaitlist();
  643.                         if (BotSettings.autoWoot)
  644.                             setTimeout(()=>{if (room) room.woot()},2000);
  645.                         startTimer("afk");
  646.                         startTimer("autodisable");
  647.                         BotSettings._fn.apply();
  648.                         startInput();
  649.                         setTitle();
  650.                         updateMentionRegex();
  651.                         if (BotSettings.doJoinMessage) {
  652.                             sendMessage(":: " + TITLE + ' ' + VER + " loaded ::", 2000);
  653.                         }
  654.                     });
  655.                 });
  656.             });
  657.         });
  658.     });
  659. }
  660.  
  661. function startTimer(type) {
  662.     switch (type) {
  663.         case "afk":
  664.             clearInterval(AFKCHECKTIME);
  665.             if (room) {
  666.                 AFKCHECKTIME = setInterval(function() {
  667.                     if (!room) {
  668.                         clearInterval(AFKCHECKTIME);
  669.                     } else {
  670.                         let i;
  671.                         for (i in room.userlist) {
  672.                             let user = room.userlist[i];
  673.                             //10 minutes
  674.                             if (!user.isAFK && (Date.now() - user.lastActivity) >= 600000) {
  675.                                 room.userlist[i].isAFK = true;
  676.                             } else if (me.role >= 2 && BotSettings.doAFKCheck && user.isAFK && (Date.now() - user.lastActivity) >= 7200000 && ~getWaitlistPos(user.id)) {
  677.                                 room.userlist[i].warn++;
  678.                                 let warns = room.userlist[i].warn;
  679.                                 warn(cc.yellow(user.username + " has been AFK for at least 2 hours and is on the waitlist. Now has " + warns + " warn(s)."));
  680.                                 if (warns === 1) {
  681.                                     sendMessage("@" + user.username + ", you've been AFK for " + secsToLabelTime((Date.now() - user.lastActivity),true) + ". Send a message within 2 minutes, or you will be removed from the waitlist.", 500);
  682.                                 } else if (warns === 2) {
  683.                                     sendMessage("@" + user.username + ", send a message in under 1 minute to avoid removal from the waitlist. Last warning.", 500);
  684.                                 } else if (warns >= 3) {
  685.                                     removeUserFromWaitlist(user.id, function() {
  686.                                         if (warns === 3) sendMessage("@" + user.username + ", you've been removed from the waitlist for being AFK. Send a message once every two hours to avoid removal.", 500);
  687.                                         else if (warns > 3) sendMessage("@" + user.username + ", you've been AFK for " + secsToLabelTime((Date.now() - user.lastActivity),true) + " and removed " + (warns-2) + " time(s). Send a message once every two hours to avoid removal.", 500);
  688.                                     });
  689.                                 }
  690.                             }
  691.                         }
  692.                         if (BotSettings.titleShowUsers)
  693.                             setTitle();
  694.                     }
  695.                 }, 60000);
  696.             }
  697.             break;
  698.         case "seen":
  699.             clearInterval(SAVESEENTIME);
  700.             if (room && BotSettings.seenAutoSave) {
  701.                 SAVESEENTIME = setInterval(function() {
  702.                     if (!BotSettings.seenAutoSave || !room) {
  703.                         clearInterval(SAVESEENTIME);
  704.                     } else {
  705.                         activeChecks();
  706.                         if (seen && seen !== {}) {
  707.                             fs.writeFile('data/seenUsers.json', JSON.stringify(seen), (e)=>{
  708.                                 if (e)
  709.                                     error(cc.red(e));
  710.                                 else
  711.                                     nodeLog(cc.blueBright('data/seenUsers.json saved'));
  712.                             });
  713.                         } else {
  714.                             error(cc.red("Seen user records were not saved because they are empty."));
  715.                         }
  716.                     }
  717.                 }, 600000);
  718.             }
  719.             break;
  720.         case "memory":
  721.             clearInterval(MEMORYTIME);
  722.             if (BotSettings.titleShowMemory) {
  723.                 MEMORYTIME = setInterval(function() {
  724.                     if (!BotSettings.titleShowMemory) {
  725.                         clearInterval(MEMORYTIME);
  726.                     } else {
  727.                         MEMORY = process.memoryUsage();
  728.                         setTitle(true);
  729.                     }
  730.                 }, 3000);
  731.             }
  732.             break;
  733.         case "roulette":
  734.             if (room) {
  735.                 if (room.roulette.active) return;
  736.                 room.roulette._cleanup();
  737.                 if (BotSettings.doRoulette && me && me.role >= 3) {
  738.                     room.roulette.timer = setTimeout(function() {
  739.                         if (room && BotSettings.doRoulette)
  740.                             room.roulette._start();
  741.                     }, 3540000); //59 minutes
  742.                 }
  743.             }
  744.             break;
  745.         case "autodisable":
  746.             clearInterval(AUTODISABLETIME);
  747.             if (BotSettings.doAutoDisable && room) {
  748.                 AUTODISABLETIME = setInterval(function() {
  749.                     if (!BotSettings.doAutoDisable) {
  750.                         clearInterval(AUTODISABLETIME);
  751.                     } else {
  752.                         sendMessage("!afkdisable", 800);
  753.                         sendMessage("!joindisable", 1600);
  754.                     }
  755.                 }, 3780000);
  756.             }
  757.             break;
  758.         case "announcements":
  759.             clearTimeout(ANNOUNCEMENTTIME);
  760.             if (room && BotSettings.sendAnnouncements && BotSettings.announcementInterval >= 5000) {
  761.                 ANNOUNCEMENTTIME = setTimeout(function() {
  762.                     let am = BotSettings.announcements;
  763.                     if (am.length === 0) return;
  764.                     let announcement = "";
  765.                     if (sentAnnouncements.length >= am.length) {
  766.                         sentAnnouncements = [];
  767.                     }
  768.                     let valid = [],
  769.                         i;
  770.                     for (i in am) {
  771.                         if (!~arrFind(sentAnnouncements, i))
  772.                             valid.push(i);
  773.                     }
  774.                     if (BotSettings.announcementRandom) {
  775.                         let num = Math.floor(Math.random() * valid.length);
  776.                         announcement = BotSettings.announcements[num];
  777.                         sentAnnouncements.push(num);
  778.                     } else {
  779.                         let num = valid[0];
  780.                         announcement = BotSettings.announcements[num];
  781.                         sentAnnouncements.push(num);
  782.                     }
  783.                     if (announcement !== "") {
  784.                         sendMessage(announcement, 0);
  785.                         startTimer("announcements");
  786.                     } else {
  787.                         error(cc.red("Tried to send an announcement, but it was empty. Stopping announcement timer."));
  788.                         BotSettings.sendAnnouncements = false;
  789.                     }
  790.                 }, BotSettings.announcementInterval);
  791.             } else {
  792.                 if (BotSettings.announcementInterval < 5000) {
  793.                     error(cc.red("Tried to start announcements, but the interval is invalid. Must be 5000+."));
  794.                 }
  795.             }
  796.             break;
  797.         case "heartbeat":
  798.             clearTimeout(HEARTBEAT.timer);
  799.             HEARTBEAT.last = Date.now();
  800.             HEARTBEAT.timer = setTimeout(function() {
  801.                 warn(cc.yellow("A heartbeat has not been received from plug.dj for 1 minute. You may have been silently disconnected, or things are slow at the moment."));
  802.             }, 60000);
  803.             break;
  804.         case "MOTD":
  805.             clearTimeout(MOTDTIME);
  806.             if (room && BotSettings.sendMOTD && BotSettings.motdInterval >= 5000) {
  807.                 MOTDTIME = setTimeout(function() {
  808.                     if (typeof BotSettings.motd === "string" && BotSettings.motd.trim() !== "") {
  809.                         sendMessage(BotSettings.motd, 0);
  810.                         startTimer("MOTD");
  811.                     } else {
  812.                         error(cc.red("Tried to send MOTD, but the MOTD message is invalid."));
  813.                     }
  814.                 }, BotSettings.motdInterval);
  815.             } else {
  816.                 if (BotSettings.motdInterval < 5000) {
  817.                     error(cc.red("Tried to start MOTD, but the interval is invalid. Must be 5000+."));
  818.                 }
  819.             }
  820.             break;
  821.         default:
  822.             break;
  823.     }
  824. }
  825.  
  826. /* ------------------ [handlers] ------------------ */
  827.  
  828. function notify(data) {
  829.     info("NOTIFY " + cc.magenta(JSON.stringify(data)));
  830.     if (data['action']) {
  831.         switch (data.action) {
  832.             case 'levelUp':
  833.                 console.log();
  834.                 log(LOGTYPES.NOTIFY+' '.repeat(HIGHWAY - 9)+cc.yellowBright('You have leveled up to ')+cc.blueBright(data.value)+cc.yellowBright('!')+'\n');
  835.                 break;
  836.             case 'gift':
  837.                 let name = data.value.split('\u2800')[0];
  838.                 let amt = data.value.split('\u2800')[1];
  839.                 console.log();
  840.                 log(LOGTYPES.NOTIFY+' '.repeat(HIGHWAY - 9)+colorizeName(getUser(name.toLowerCase()))+cc.yellowBright(' sent you a gift of ')+cc.cyanBright(amt)+cc.blueBright(' Plug Points!')+'\n');
  841.                 break;
  842.             default:
  843.                 console.log(cc.yellowBright('Notify action unknown: ')+data.action);
  844.                 fs.writeFile('unknownEvents/notify_'+data.action+'.txt', JSON.stringify(data), (err)=>{if (err) error(cc.red(err));});
  845.                 break;
  846.         }
  847.     }
  848. }
  849.  
  850. function handleGifted(data) {
  851.     if (data['s'] && data['r'])
  852.         log(LOGTYPES.GIFT+' '.repeat(HIGHWAY - 7)+colorizeName(getUser(data['s']))+cc.yellowBright(' sent a gift to ')+colorizeName(getUser(data['r']))+cc.yellowBright('!'));
  853. }
  854.  
  855. function handleModAddDJ(data) {
  856.     if (data.t && data.mi) {
  857.         let user = getUser(data.t);
  858.         let mod = getUser(data.mi);
  859.         if (~user && ~mod) {
  860.             log(LOGTYPES.WAITLIST+' '.repeat(HIGHWAY - 11)+colorizeName(mod)+cc.yellowBright(' added ')+colorizeName(user)+cc.yellowBright(' to the waitlist.'));
  861.         }
  862.     }
  863. }
  864.  
  865. function handleModRemoveDJ(data) {
  866.     if (data.t && data.mi) {
  867.         let user = getUser(data.t);
  868.         let mod = getUser(data.mi);
  869.         if (~user && ~mod) {
  870.             log(LOGTYPES.WAITLIST+' '.repeat(HIGHWAY - 11)+colorizeName(mod)+cc.yellowBright(' removed ')+colorizeName(user)+cc.yellowBright(' from the waitlist.'));
  871.         }
  872.     }
  873. }
  874.  
  875. function handleDjListCycle(data) {
  876.     let user = getUser(data.mi);
  877.     if (~user) {
  878.         room.booth.shouldCycle = data.f;
  879.         log(LOGTYPES.WAITLIST+' '.repeat(HIGHWAY - 11)+colorizeName(user)+cc.yellowBright(' turned DJ Cycle ')+(data.f ? cc.greenBright('on') : cc.redBright('off'))+cc.yellowBright('.'));
  880.     }
  881. }
  882.  
  883. function handleModStaff(data) {
  884.     let mod,user;
  885.     mod = getUser(data.mi);
  886.     user = getUser(data.u[0].i);
  887.     if (~mod) {
  888.         let action = function(user) {
  889.             if (~user) {
  890.                 log(LOGTYPES.STAFF+' '.repeat(HIGHWAY - 8)+colorizeName(mod) + cc.yellowBright(' changed ') + colorizeName(user) + cc.yellowBright('\'s role from ')+cc.cyan(roleToString(user.role))+cc.yellowBright(' to ') + cc.cyanBright(roleToString(data.u[0].p)) + cc.yellowBright('!'));
  891.                 let users = room.userlist,
  892.                     i;
  893.  
  894.                 syncUsers();
  895.             }
  896.         }
  897.         if (~user) action(user);
  898.         else getUserData(data.u[0].i, function(data) { action(data) });
  899.     }
  900. }
  901.  
  902. function handleDjListLocked(data) {
  903.     let mod = getUser(data.mi);
  904.     if (~mod && room.booth.hasOwnProperty('isLocked')) {
  905.         if (data.c)
  906.             room.setWaitlist([]);
  907.         room.booth.isLocked = data.f;
  908.         log(LOGTYPES.WAITLIST+' '.repeat(HIGHWAY-11)+colorizeName(mod)+' '+(data.f ? cc.redBright("locked") : cc.greenBright("unlocked"))+' the waitlist.'+(data.c ? cc.yellowBright(' The waitlist has also been cleared.') : ''));
  909.     }
  910. }
  911.  
  912. function handleRoomWelcomeUpdate(data) {
  913.     let mod = getUser(data.u);
  914.     if (~mod && room.meta.hasOwnProperty('welcome')) {
  915.         if (data.w) {
  916.             room.meta.welcome = ent.decode(data.w);
  917.             log(LOGTYPES.ROOM+' '.repeat(HIGHWAY-7)+colorizeName(mod)+cc.yellowBright(' updated the room\'s welcome message.'));
  918.             log(cc.blueBright('--- ') + cc.greenBright(data.w) + cc.blueBright(' ---'));
  919.         }
  920.     }
  921. }
  922.  
  923. function handleRoomDescriptionUpdate(data) {
  924.     let mod = getUser(data.u);
  925.     if (~mod && room.meta.hasOwnProperty('description')) {
  926.         if (data.d) {
  927.             room.meta.description = ent.decode(data.d);
  928.             log(LOGTYPES.ROOM+' '.repeat(HIGHWAY-7)+colorizeName(mod)+cc.yellowBright(' updated the room\'s description to: ')+cc.blueBright(data.d.replace(/\n/g, '\\n')));
  929.         }
  930.     }
  931. }
  932.  
  933. function handleRoomMinLvlUpdate(data) {
  934.     let mod = getUser(data.u);
  935.     if (~mod && room.meta.hasOwnProperty('minChatLevel')) {
  936.         if (data.m) {
  937.             room.meta.minChatLevel = data.m;
  938.             log(LOGTYPES.ROOM+' '.repeat(HIGHWAY-7)+colorizeName(mod)+cc.yellowBright(' changed the minimum chat level to ')+cc.blueBright(data.m));
  939.         }
  940.     }
  941. }
  942.  
  943. function handleRoomNameUpdate(data) {
  944.     let mod = getUser(data.u);
  945.     if (~mod && room.meta.hasOwnProperty('name')) {
  946.         if (data.n) {
  947.             room.meta.name = ent.decode(data.n);
  948.             log(LOGTYPES.ROOM+' '.repeat(HIGHWAY-7)+colorizeName(mod)+cc.yellowBright(' changed the room name to ')+cc.blueBright(data.n));
  949.         }
  950.     }
  951. }
  952.  
  953. function handleModMute(data) {
  954.     let mod = getUser(data.mi);
  955.     let user = getUser(data.i);
  956.     let action = function(mod,user) {
  957.         if (~mod && ~user) {
  958.             if (data.d === "o") {
  959.                 log(LOGTYPES.MUTE+' '.repeat(HIGHWAY-7)+colorizeName(mod)+cc.greenBright(' unmuted')+cc.yellowBright(' user ')+colorizeName(user)+cc.yellowBright('.'));
  960.             } else {
  961.                 log(LOGTYPES.MUTE+' '.repeat(HIGHWAY-7)+colorizeName(mod)+cc.yellowBright(' muted user ')+colorizeName(user)+cc.yellowBright(' for ')+cc.redBright(muteDurationToString(data.d))+cc.yellowBright('. Reason: ')+cc.redBright(muteReasonToString(data.r)));
  962.             }
  963.         }
  964.     };
  965.     if (!~user) {
  966.         getUserData(data.i, function(data) {
  967.             action(mod, data);
  968.         });
  969.     } else {
  970.         action(mod, user);
  971.     }
  972. }
  973.  
  974. function handleModBan(data) {
  975.     let mod = getUser(data.mi);
  976.     let user = getUser(data.t);
  977.     if (~mod) {
  978.         if (~user) user = colorizeName(user);
  979.         else user = data.t;
  980.  
  981.         let banTime = "";
  982.         if (data.d === 'h') banTime = cc.yellowBright(' for one hour.');
  983.         else if (data.d === 'd') banTime = cc.red(' for one day.');
  984.         else if (data.d === 'f') banTime = cc.redBright(' forever.');
  985.         else banTime = ' !invalid time!';
  986.  
  987.         log(LOGTYPES.BAN+' '.repeat(HIGHWAY-6)+colorizeName(mod)+cc.yellowBright(' banned ')+user+banTime);
  988.     }
  989.  
  990. }
  991.  
  992. function handleFriendRequest(data) {
  993.     if (data) {
  994.         let user = getUser(data);
  995.         if (~user) user = colorizeName(user);
  996.         else user = data;
  997.         log(LOGTYPES.FRIEND+' '.repeat(HIGHWAY-9)+user+cc.yellowBright(' sent you a friend request.'));
  998.     }
  999. }
  1000.  
  1001. function handleFriendAccept(data) {
  1002.     if (data) {
  1003.         let user = getUser(data);
  1004.         if (~user) user = colorizeName(user);
  1005.         else user = data;
  1006.         log(LOGTYPES.FRIEND+' '.repeat(HIGHWAY-9)+user+cc.yellowBright(' accepted your friend request.'));
  1007.     }
  1008. }
  1009.  
  1010. function handlePlaylistCycle(data) {
  1011.     if (data) {
  1012.         let pl = getPlaylist(data);
  1013.         if (!~pl) return;
  1014.         if (pl.name)
  1015.             nodeLog(cc.green('Finished playing song, cycling playlist: ')+cc.greenBright(ent.decode(pl.name)));
  1016.     }
  1017. }
  1018.  
  1019. function handleChat(data) {
  1020.     if (!room) return;
  1021.     data.message = ent.decode(data.message);
  1022.     let user = getUser(data.uid),
  1023.         name = colorizeName({username:data.un,role:user.role,sub:data.sub,id:data.uid,silver:user.silver}, true, true),
  1024.         msg = data.message,
  1025.         i;
  1026.  
  1027.     if (me.username && BotSettings.chatHighlightMention && MENTIONREGEX) {
  1028.         msg = msg.replace(MENTIONREGEX, cc.bgMagentaBright.black('$&'));
  1029.     }
  1030.  
  1031.     for (i in room.userlist) {
  1032.         if (room.userlist[i].id === data.uid) {
  1033.             room.userlist[i].lastActivity = Date.now();
  1034.             if (room.userlist[i].isAFK) {
  1035.                 room.userlist[i].warn = 0;
  1036.                 room.userlist[i].isAFK = false;
  1037.                 if (BotSettings.titleShowUsers)
  1038.                     setTitle();
  1039.             }
  1040.             break;
  1041.         }
  1042.     }
  1043.  
  1044.     msg = msg.replace(/\n/g, " ");
  1045.  
  1046.     if (BotSettings.chatShowCID && data.cid)
  1047.         log(cc.blackBright(data.cid) + ' '.repeat(25 - data.cid.length) + name + cc.white(msg));
  1048.     else if (data.uid)
  1049.         log(cc.blackBright(data.uid) + ' '.repeat(10 - data.uid.toString().length) + name + cc.white(msg));
  1050.  
  1051.     if (~user) {
  1052.  
  1053.         const del = function(cid) {
  1054.             if (cid) {
  1055.                 setTimeout(function() {
  1056.                     deleteMessage(cid);
  1057.                 }, 1000);
  1058.             }
  1059.         };
  1060.  
  1061.         if (room.roulette.active) {
  1062.             if (data.message.toLowerCase() === TRIGGER + 'join')
  1063.                 room.roulette._addUser(data.uid);
  1064.             else if (data.message.toLowerCase() === TRIGGER + 'leave')
  1065.                 room.roulette._rmUser(data.uid, true);
  1066.         }
  1067.  
  1068.         if (data.message.substr(0,6) === TRIGGER + "self " && data.message.length > 7 && data.uid === 18531073 && BotSettings.acceptRemoteCmd)
  1069.             doCommand(data.message.substr(6));
  1070.         else if (BotSettings.chatDeleteResponses && /(?:autojoin is now disabled\!|autorespond is now disabled\!|autojoin was not enabled|autojoin disabled|afk message disabled)/gi.test(data.message))
  1071.             del(data['cid']);
  1072.         else if (data.message.substr(0,1) === TRIGGER) {
  1073.  
  1074.             if (BotSettings.chatDeleteTriggerMessages)
  1075.                 del(data['cid']);
  1076.  
  1077.             doChatCommand(data, user);
  1078.  
  1079.         }
  1080.         else if (~'!#$%^&*()_+-=`~.,?'.indexOf(data.message.substr(0,1)) && data.message.substr(1).toLowerCase() === 'trigger')
  1081.             commands.trigger.exec(user.role);
  1082.     }
  1083. }
  1084.  
  1085. function handleUserJoin(data) {
  1086.     if (!room) return;
  1087.     if (data.guest) {
  1088.         log(LOGTYPES.JOIN+' '.repeat(HIGHWAY-9) + cc.green('+ ') + cc.blackBright('A guest') + cc.green(' joined the room.'));
  1089.         room.meta.guests++;
  1090.     }
  1091.     else if (data) {
  1092.         addUser(data);
  1093.         if (data.username && data.username !== me.username)
  1094.             log(LOGTYPES.JOIN+' '.repeat(HIGHWAY-9) + cc.green('+ ') + colorizeName(data) + cc.green(' joined the room.') + cc.blackBright(" UID: " + data.id));
  1095.     }
  1096.     if (BotSettings.titleShowUsers)
  1097.         setTitle();
  1098. }
  1099.  
  1100. function handleUserLeave(data) {
  1101.     if (!room) return;
  1102.     if (!data) {
  1103.         log(LOGTYPES.LEAVE+' '.repeat(HIGHWAY-10) + cc.red('- ') + cc.blackBright('A guest') + cc.red(' left the room.'));
  1104.         room.meta.guests = Math.max(0,room.meta.guests-1);
  1105.     } else {
  1106.         let user = getUser(data);
  1107.         let uname = colorizeName(user);
  1108.         removeUser(data);
  1109.         if (user.username && user.username !== me.username)
  1110.             log(LOGTYPES.LEAVE+' '.repeat(HIGHWAY-10) + cc.red('- ') + uname + cc.red(' left the room.') + cc.blackBright(" UID: " + user.id));
  1111.     }
  1112.     if (BotSettings.titleShowUsers)
  1113.         setTitle();
  1114. }
  1115.  
  1116. function handleChatDelete(data) {
  1117.     let user = getUser(data.mi);
  1118.     if (~user)
  1119.         log(LOGTYPES.DELETE + ' '.repeat(HIGHWAY - 9) + colorizeName(user) + ' ' + cc.red('deleted CID:') + cc.redBright(data.c));
  1120. }
  1121.  
  1122. function handleVote(data) {
  1123.     let user = getUser(data.i);
  1124.     if (~user) {
  1125.         if (data.v === 1) {
  1126.             if (!BotSettings.chatShowWoot) {
  1127.                 room.votes[data.i] = 1;
  1128.             } else {
  1129.                 let type = LOGTYPES.WOOT;
  1130.                 if (room.votes[data.i] === 1) {
  1131.                     if (!BotSettings.chatShowRepeatWoot) return;
  1132.                     else type = LOGTYPES.WOOTAGAIN;
  1133.                 } else room.votes[data.i] = 1;
  1134.                 log(type + ' '.repeat(HIGHWAY-7) + cc.blackBright(user.username));
  1135.             }
  1136.         } else if (data.v === -1) {
  1137.             if (!BotSettings.chatShowMeh) {
  1138.                 room.votes[data.i] = -1;
  1139.             } else {
  1140.                 let type = LOGTYPES.MEH;
  1141.                 if (room.votes[data.i] === -1) {
  1142.                     if (!BotSettings.chatShowRepeatMeh) return;
  1143.                     else type = LOGTYPES.MEHAGAIN;
  1144.                 } else room.votes[data.i] = -1;
  1145.                 log(type + ' '.repeat(HIGHWAY-6) + cc.blackBright(user.username));
  1146.             }
  1147.         } else return;
  1148.     }
  1149. }
  1150.  
  1151. function handleGrab(data) {
  1152.     let user = getUser(data);
  1153.     if (~user) {
  1154.         if (!BotSettings.chatShowGrab) {
  1155.             room.grabs[user.id] = 1;
  1156.         } else {
  1157.             let type = LOGTYPES.GRAB;
  1158.             if (room.grabs[user.id] === 1) {
  1159.                 if (!BotSettings.chatShowRepeatGrab) return;
  1160.                 else type = LOGTYPES.GRABAGAIN;
  1161.             } else room.grabs[user.id] = 1;
  1162.             log(type + ' '.repeat(HIGHWAY-7) + cc.blackBright(user.username));
  1163.         }
  1164.     }
  1165. }
  1166.  
  1167. function handleAdvance(data) {
  1168.     clearTimeout(STUCKSKIPTIME);
  1169.     if (data && room) {
  1170.         let k;
  1171.         for (k in disconnects) {
  1172.             setUserDC(k, disconnects[k][0] - 1);
  1173.         }
  1174.         let previousVotes = countVotes(),
  1175.             previousDJ = getUser(room.booth.currentDJ);
  1176.  
  1177.         room.votes = {};
  1178.         room.grabs = {};
  1179.         let un = {};
  1180.         if (data['c']) {
  1181.             un = getUser(data.c);
  1182.             room.booth.currentDJ = data.c;
  1183.         } else {
  1184.             un = cc.redBright('Error: no user id');
  1185.             room.booth.currentDJ = -1;
  1186.         }
  1187.  
  1188.         if (~previousDJ && seen[room.slug][previousDJ.id]) {
  1189.             remUserDC(previousDJ.id);
  1190.             seen[room.slug][previousDJ.id].plays++;
  1191.             seen[room.slug][previousDJ.id].votes.woot += previousVotes[0];
  1192.             seen[room.slug][previousDJ.id].votes.grab += previousVotes[1];
  1193.             seen[room.slug][previousDJ.id].votes.meh += previousVotes[2];
  1194.         }
  1195.  
  1196.         if (data['m']) {
  1197.             remUserDC(un.id);
  1198.             if (room.roulette.active) {
  1199.                 room.roulette._rmUser(un.id);
  1200.             }
  1201.             room.playback = data;
  1202.             if (BotSettings.autoWoot)
  1203.                 setTimeout(()=>room.woot(),2000);
  1204.             if (BotSettings.autoStuckSkip && data.m.duration !== undefined && me.role >= 2) {
  1205.                 let current = data.m.format + ":" + data.m.cid;
  1206.                 let dj = data.c;
  1207.                 STUCKSKIPTIME = setTimeout(function() {
  1208.                     if (BotSettings.autoStuckSkip && room && me.role >= 2) {
  1209.                         let pb = room.playback.m;
  1210.                         let currentDJ = room.booth.currentDJ;
  1211.                         if (pb.format && pb.cid && (pb.format + ":" + pb.cid) === current && dj === currentDJ) {
  1212.                             skipSong(me.username, "stuck", true, false);
  1213.                         }
  1214.                     }
  1215.                 }, (data.m.duration + 10)*1000);
  1216.  
  1217.             }
  1218.             console.log('\n');
  1219.             log(cc.blue('/////////// ') + cc.blueBright('Now Playing') + cc.blue(' ::: ') + cc.cyanBright(data.m.author) + cc.cyan(' - ') + cc.cyanBright(data.m.title) + cc.blue(' | ') + cc.blueBright(secsToTime(data.m.duration)) + cc.blue(' | ') + cc.blueBright('current DJ: ') + colorizeName(un) + cc.blue(' ///////////')+'\n\n');
  1220.  
  1221.             if (BotSettings.doSkipCheck) {
  1222.                 //autoskip conditions here. be careful not to cause overskips
  1223.                 let DOSKIP = false,
  1224.                     REASON = null;
  1225.                 if (!DOSKIP) {
  1226.                     let i,
  1227.                         j;
  1228.                     const fmt = data.m.format,
  1229.                           cid = data.m.cid;
  1230.                     for (i in blacklists) {
  1231.                         if (DOSKIP) break;
  1232.                         if (blacklists[i][1].length > 0) {
  1233.                             for (j = 0; j < blacklists[i][1].length; j++) {
  1234.                                 if (blacklists[i][1][j] === (fmt + ":" + cid)) {
  1235.                                     if (i === "op") {
  1236.                                         warn(cc.yellow("The currently playing song was found in the OP list."));
  1237.                                         REASON = "op";
  1238.                                     } else {
  1239.                                         warn(cc.yellow("The currently playing song was found in a blacklist."));
  1240.                                         REASON = "blacklisted";
  1241.                                     }
  1242.                                     DOSKIP = true;
  1243.                                     break;
  1244.                                 }
  1245.                             }
  1246.                         }
  1247.                     }
  1248.                 }
  1249.                 if (!DOSKIP && data.m.duration > 600 && un.role < 4) { // 10 mins
  1250.                     warn(cc.yellow("The currently playing song is over 10 minutes."));
  1251.                     DOSKIP = true;
  1252.                     REASON = "10min";
  1253.                 }
  1254.                 if (!DOSKIP && ~getHistoryIdx(data.m.format, data.m.cid)) {
  1255.                     warn(cc.yellow("The currently playing song was found in the history list."));
  1256.                     DOSKIP = true;
  1257.                     REASON = "history";
  1258.                 }
  1259.  
  1260.                 const SKIP = function() {
  1261.                     if (DOSKIP && REASON && BotSettings.doAutoSkip && me.role >= 2) {
  1262.                         if (REASON !== "unavailable" && BotSettings.hostBypassAutoSkip && un.role === 5) {}
  1263.                         else {
  1264.                             let current = data.m.format + ":" + data.m.cid;
  1265.                             let dj = data.c;
  1266.                             setTimeout(()=>{
  1267.                                 if (BotSettings.doAutoSkip && room && me.role >= 2) {
  1268.                                     let pb = room.playback.m;
  1269.                                     let currentDJ = room.booth.currentDJ;
  1270.                                     if (pb.format && pb.cid && (pb.format + ":" + pb.cid) === current && dj === currentDJ) {
  1271.                                         skipSong(me.username, REASON, true, true);
  1272.                                     }
  1273.                                 }
  1274.                             }, 10000);
  1275.                         }
  1276.                     }
  1277.                 };
  1278.  
  1279.                 if (!DOSKIP) {
  1280.                         isUnavailable(data.m.format, data.m.cid, (state)=> {
  1281.                             if (state === -1) {
  1282.                                 error(cc.red("Error checking availability of song."));
  1283.                             } else if (state === 1) {
  1284.                                 warn(cc.yellow("This song is unavailable."));
  1285.                                 DOSKIP = true;
  1286.                                 REASON = "unavailable";
  1287.                                 SKIP();
  1288.                             } else if (state === 0 && !DOSKIP) {
  1289.                                 //noop for now. continue nesting here...
  1290.                             } else {
  1291.                                 error(cc.red("Unknown code when checking availability: " + cc.redBright(state)));
  1292.                             }
  1293.                         });
  1294.                 } else {
  1295.                     SKIP();
  1296.                 }
  1297.  
  1298.             }
  1299.             addHistoryItem(data.h, data.m.format, data.m.cid, data.t);
  1300.         } else {
  1301.             room.playback = {m:{},d:[]};
  1302.             console.log('\n');
  1303.             log(cc.blue('/////////// ') + cc.cyanBright('Nothing is currently playing.') + cc.blue(' ///////////')+'\n\n');
  1304.         }
  1305.         if (BotSettings.titleShowUsers)
  1306.             setTitle();
  1307.     }
  1308. }
  1309.  
  1310. function handleDjListUpdate(data) {
  1311.     if (data) {
  1312.         let oldWL = room.getWaitlist(),
  1313.             newWL = data,
  1314.             i,
  1315.             updatePos = function(wl, x) {
  1316.                 let newpos = x;
  1317.                 setTimeout(function() {
  1318.                     if (~getUser(wl[x]))
  1319.                         remUserDC(wl[x]);
  1320.                     else if (~newpos)
  1321.                         setUserDC(wl[x], newpos);
  1322.                 }, 250);
  1323.             };
  1324.  
  1325.         for (i in oldWL) {
  1326.             if (!~arrFind(newWL, oldWL[i]))
  1327.                updatePos(oldWL, i);
  1328.         }
  1329.  
  1330.         for (i in newWL) {
  1331.             let userDC = getUserDC(newWL[i]);
  1332.             if (~userDC && userDC >= i)
  1333.                 remUserDC(newWL[i]);
  1334.         }
  1335.         room.setWaitlist(data);
  1336.  
  1337.         if (BotSettings.titleShowUsers)
  1338.             setTitle();
  1339.         cleanDC();
  1340.     }
  1341. }
  1342.  
  1343. function handleUserUpdate(data) {
  1344.     if (data.i && room) {
  1345.         let userlist = room.userlist,
  1346.             i,
  1347.             j;
  1348.         for (i in userlist) {
  1349.             if (userlist[i].id === data.i) {
  1350.                 if (data.hasOwnProperty("username")) {
  1351.                     if (data['username'] === userlist[i].username)
  1352.                         log(LOGTYPES.USER+' '.repeat(HIGHWAY - 7)+cc.greenBright('A guest logged in as ')+colorizeName(userlist[i])+cc.greenBright('.'));
  1353.                     else
  1354.                         log(LOGTYPES.USER+' '.repeat(HIGHWAY - 7)+colorizeName(userlist[i])+cc.yellowBright(' changed name to ')+cc.blueBright(data['username']));
  1355.                 }
  1356.                 if (data.hasOwnProperty("silver")) {
  1357.                     if (data.silver == 1) {
  1358.                         log(LOGTYPES.USER+' '.repeat(HIGHWAY - 7)+colorizeName(userlist[i])+cc.yellowBright(' is now a ')+cc.white('silver')+cc.yellowBright(' subscriber!'));
  1359.                     } else if (data.silver == 0) {
  1360.                         log(LOGTYPES.USER+' '.repeat(HIGHWAY - 7)+colorizeName(userlist[i])+cc.yellowBright(' is no longer a ')+cc.white('silver')+cc.yellowBright(' subscriber.'));
  1361.                     }
  1362.                 }
  1363.                 if (data.hasOwnProperty("sub")) {
  1364.                     if (data.sub == 1)
  1365.                         log(LOGTYPES.USER+' '.repeat(HIGHWAY - 7)+colorizeName(userlist[i])+cc.yellowBright(' is now a ')+cc.yellow('gold')+cc.yellowBright(' subscriber!'));
  1366.                     else if (data.sub == 0)
  1367.                         log(LOGTYPES.USER+' '.repeat(HIGHWAY - 7)+colorizeName(userlist[i])+cc.yellowBright(' is no longer a ')+cc.yellow('gold')+cc.yellowBright(' subscriber.'));
  1368.                 }
  1369.                 for (j in data) {
  1370.                     if (userlist[i].hasOwnProperty(j) && j !== "i")
  1371.                         userlist[i][j] = data[j];
  1372.                 }
  1373.                 room.userlist = userlist;
  1374.                 return;
  1375.             }
  1376.         }
  1377.         return;
  1378.     }
  1379. }
  1380.  
  1381. function handleModMoveDJ(data) {
  1382.     let mod = getUser(data.m);
  1383.     let user = getUser(data.u);
  1384.     if (~mod && ~user)
  1385.         log(LOGTYPES.WAITLIST + ' '.repeat(HIGHWAY - 11) + colorizeName(mod) + cc.yellowBright(' moved ') + colorizeName(user) + cc.yellowBright(' from ') + cc.cyan(data.o+1) + cc.yellowBright(' to ') + cc.cyanBright(data.n+1));
  1386. }
  1387.  
  1388. function handleSkip(data, modskip) {
  1389.     let usr = getUser(modskip?data.mi:data);
  1390.     if (!~usr) return;
  1391.  
  1392.     if (modskip)
  1393.         log(LOGTYPES.SKIP+' '.repeat(HIGHWAY - 7)+colorizeName(usr)+cc.yellowBright(' modskipped the song.'));
  1394.     else
  1395.         log(LOGTYPES.SKIP+' '.repeat(HIGHWAY - 7)+colorizeName(usr)+cc.yellowBright(' skipped his/her own song.'));
  1396. }
  1397.  
  1398. function handlePlugMessage(data) {
  1399.     if (typeof data === "string") data = ent.decode(data);
  1400.     log(LOGTYPES.PLUG+' '.repeat(HIGHWAY - 10)+cc.cyanBright(data));
  1401. }
  1402.  
  1403. function handleEvent(e) {
  1404.  
  1405.     e = JSON.parse(e)[0];
  1406.  
  1407.     const event = {
  1408.         name: e['a'],
  1409.         data: e['p'],
  1410.         room: e['s']
  1411.     };
  1412.  
  1413.     if (DEBUG)
  1414.         debug(cc.magenta("event received: ") + cc.magentaBright(event.name) + cc.magenta(" in room: ") + cc.magentaBright(event.room));
  1415.  
  1416.     const events = {
  1417.         'ack':()=>event.data === "1" ? console.log(cc.greenBright('Successfully connected to plug.dj!')) : console.log(cc.redBright('Did not connect to plug.dj.')),
  1418.         'advance':()=>handleAdvance(event.data),
  1419.         'chat':()=>handleChat(event.data),
  1420.         'chatDelete':()=>handleChatDelete(event.data),
  1421.         'djListCycle':()=>handleDjListCycle(event.data),
  1422.         'djListLocked':()=>handleDjListLocked(event.data),
  1423.         'djListUpdate':()=>handleDjListUpdate(event.data),
  1424.         'earn':()=>{}, //handleEarn(event.data) but nothing is defined
  1425.         'floodAPI':()=>{log(LOGTYPES.PLUG+' '.repeat(HIGHWAY - 10)+"You are flooding the API with too many requests. Slow down.")},
  1426.         'friendAccept':()=>handleFriendAccept(event.data),
  1427.         'friendRequest':()=>handleFriendRequest(event.data),
  1428.         'gift':()=>{me.pp = event.data},
  1429.         'gifted':()=>handleGifted(event.data),
  1430.         'grab':()=>handleGrab(event.data),
  1431.         'killSession':()=>log(LOGTYPES.KILLSESSION+' '.repeat(HIGHWAY-14)+cc.redBright('WebSocket was forcibly closed. Did you log in a second time? This can also result from flooding requests.')),
  1432.         'modAddDJ':()=>handleModAddDJ(event.data),
  1433.         'modBan':()=>handleModBan(event.data),
  1434.         'modMoveDJ':()=>handleModMoveDJ(event.data),
  1435.         'modMute':()=>handleModMute(event.data),
  1436.         'modRemoveDJ':()=>handleModRemoveDJ(event.data),
  1437.         'modSkip':()=>handleSkip(event.data, true),
  1438.         'modStaff':()=>handleModStaff(event.data, true),
  1439.         'notify':()=>notify(event.data),
  1440.         'playlistCycle':()=>handlePlaylistCycle(event.data),
  1441.         'plugMessage':()=>handlePlugMessage(event.data),
  1442.         'roomDescriptionUpdate':()=>handleRoomDescriptionUpdate(event.data),
  1443.         'roomMinChatLevelUpdate':()=>handleRoomMinLvlUpdate(event.data),
  1444.         'roomNameUpdate':()=>handleRoomNameUpdate(event.data),
  1445.         'roomWelcomeUpdate':()=>handleRoomWelcomeUpdate(event.data),
  1446.         'skip':()=>handleSkip(event.data, false),
  1447.         'userJoin':()=>handleUserJoin(event.data),
  1448.         'userLeave':()=>handleUserLeave(event.data),
  1449.         'userUpdate':()=>handleUserUpdate(event.data),
  1450.         'vote':()=>handleVote(event.data)
  1451.     },
  1452.     unknown = function() {
  1453.         warn(cc.yellow('Socket event unknown: ') + cc.yellowBright(event.name));
  1454.         fs.writeFile('unknownEvents/' + event.name + '.txt', JSON.stringify(event.data), (err)=>{if (err) error(cc.red(err)); else {nodeLog(cc.green("Wrote event data to unknownEvents/" + event.name + ".txt"))}});
  1455.     };
  1456.  
  1457.     return (events[event.name] || unknown)();
  1458. }
  1459.  
  1460. /* ------------------ [other] ------------------ */
  1461.  
  1462. function cleanDC() {
  1463.     let i,
  1464.         time = Date.now();
  1465.     for (i in disconnects) {
  1466.         if (time - disconnects[i][1] > MAX_DISC_TIME)
  1467.             remUserDC(i);
  1468.     }
  1469. }
  1470.  
  1471. function getUserDC(uid) {
  1472.     if (disconnects.hasOwnProperty(uid))
  1473.         return disconnects[uid][0];
  1474.     return -1;
  1475. }
  1476.  
  1477. function setUserDC(uid, pos) {
  1478.     pos = parseInt(pos);
  1479.     if (!isNaN(pos) && pos > -1) {
  1480.         let time = Date.now();
  1481.         if (disconnects.hasOwnProperty(uid))
  1482.             disconnects[uid][0] = pos;
  1483.         else
  1484.             disconnects[uid] = [pos, time];
  1485.  
  1486.     } else
  1487.         remUserDC(uid);
  1488. }
  1489.  
  1490. function remUserDC(uid) {
  1491.     delete disconnects[uid];
  1492. }
  1493.  
  1494. function connect(token) {
  1495.     wss = new WebSocket('wss://godj.plug.dj:443/socket', '', {origin: 'https://plug.dj'});
  1496.     wss.on('open', function() {
  1497.         if (wss.readyState === 1 && token)
  1498.             wss.send(JSON.stringify({a:'auth', p:token, t:Math.floor(Date.now() / 1000)}));
  1499.         else if (!token) {
  1500.             return console.log(cc.red('ERROR: authToken not found.'));
  1501.         } else {
  1502.             return error(cc.red("Unable to connect to plug.dj."));
  1503.         }
  1504.         loadBlacklists();
  1505.         joinRoom(HOME);
  1506.     });
  1507.     wss.on('message', function(msg) {
  1508.         if (msg === undefined) return;
  1509.         else if (msg === "h") {
  1510.             if (DEBUG)
  1511.                 debug(cc.magenta("Received heartbeat."));
  1512.             startTimer("heartbeat");
  1513.             return;
  1514.         }
  1515.         else
  1516.             handleEvent(msg);
  1517.     });
  1518.     wss.on('close', function(err, reason) {
  1519.         warn(cc.yellow('WebSocket connection to plug.dj has been closed.'));
  1520.         if (!reason && err === 1006) reason = "CLOSE_ABNORMAL; plug.dj may be down for maintenance.";
  1521.         error(cc.red('WebSocket closed with: ')+cc.redBright(err)+cc.red(', reason: ')+cc.redBright(reason));
  1522.         if (err.stack) error(cc.red(err.stack));
  1523.         if (room) room.roulette._cleanup();
  1524.         clearInterval(SAVESEENTIME);
  1525.         clearInterval(AFKCHECKTIME);
  1526.         clearInterval(AUTODISABLETIME);
  1527.         clearTimeout(ANNOUNCEMENTTIME);
  1528.         clearTimeout(MOTDTIME);
  1529.         setTimeout(function() {
  1530.             process.exit(0);
  1531.         }, 10000);
  1532.     });
  1533. }
  1534.  
  1535.  
  1536. //the delay does not work well. need to create an actual queue to send delayed messages in order
  1537. let messageDelay = 0;
  1538. function sendMessage(msg,delay) {
  1539.     const THRESHOLD = 250; //queue if last message time < this
  1540.     if (!room) return;
  1541.     if (room.meta.minChatLevel > me.level) { error(cc.redBright('You are restricted from the chat because your level is below the room\'s minimum chat level.')); return; }
  1542.     delay = parseInt(delay);
  1543.     if (isNaN(delay) || delay < 0) delay = 0;
  1544.  
  1545.     let snd = function() {
  1546.         if (msg.trim() !== "" && typeof msg === "string") {
  1547.             const LAST = Date.now() - LASTSENTMSG;
  1548.             if (LAST < THRESHOLD) {
  1549.                 messageDelay += THRESHOLD - LAST;
  1550.                 warn(cc.yellow('You are sending messages quickly! Last message sent was ')+cc.yellowBright(LAST+'ms') + cc.yellow(' ago. Your message has been queued to be sent in ' + messageDelay + 'ms.'));
  1551.                 return sendMessage(msg, messageDelay);
  1552.             } else if (wss.readyState === 1) {
  1553.                 if (messageDelay > 0) messageDelay = Math.max(0, messageDelay - THRESHOLD);
  1554.                 LASTSENTMSG = Date.now();
  1555.                 wss.send(JSON.stringify({
  1556.                     'a':'chat',
  1557.                     'p':msg,
  1558.                     't':Date.now()
  1559.                 }));
  1560.             }
  1561.         }
  1562.     };
  1563.  
  1564.     if (delay > 0) setTimeout(function() { snd(); }, delay);
  1565.     else snd();
  1566. }
  1567.  
  1568. function logVotes() {
  1569.     let votes = countVotes();
  1570.     let str = cc.blackBright('current votes: ')+cc.greenBright('Woot: ')+cc.green(votes[0])+cc.magentaBright(' Grab: ')+cc.magenta(votes[1])+cc.redBright(' Meh: ')+cc.red(votes[2]);
  1571.     console.log();
  1572.     nodeLog(str);
  1573. }
  1574.  
  1575. function waitlistLock(lock, clear) {
  1576.     if (typeof lock !== "boolean" || typeof clear !== "boolean") return;
  1577.     PUT('_/booth/lock', {"isLocked":lock, "removeAllDJs":clear});
  1578. }
  1579.  
  1580. function showActivePlaylist() {
  1581.     if (room) {
  1582.         let playlist = room.activePlaylist;
  1583.         if (playlist === {} || !playlist.name) {
  1584.             warn(cc.yellow("No playlist is active."));
  1585.         } else {
  1586.             nodeLog(cc.green("Currently active playlist: ")+cc.greenBright(ent.decode(playlist.name)));
  1587.         }
  1588.     }
  1589. }
  1590.  
  1591. function doCommand(msg) {
  1592.     let i;
  1593.     const data = msg.split(' '),
  1594.         simpleNameFn = function(fn) {
  1595.             if (typeof fn === "function") {
  1596.                 if (strIsNum(data[1]))
  1597.                     fn(parseInt(data[1]));
  1598.                 else if (~msg.indexOf('@', data[0].length)) {
  1599.                     const user = getUser(msg.substr(msg.indexOf('@', data[0].length)+1));
  1600.                     if (~user) fn(user.id);
  1601.                 }
  1602.             }
  1603.         },
  1604.         cmds = {
  1605.             "/ban": ()=>{
  1606.                 if (data.length >= 4) {
  1607.                     let banData = new Array(3);
  1608.  
  1609.                     if (REASONS[0].hasOwnProperty(parseInt(data[1]))) {
  1610.                         banData[1] = parseInt(data[1]);
  1611.                     } else {
  1612.                         error(cc.red('/ban: invalid reason #. Type /banreasons or /br for a list of valid options'));
  1613.                         return;
  1614.                     }
  1615.  
  1616.                     if (DURATIONS[0].hasOwnProperty(data[2].toLowerCase())) {
  1617.                         banData[2] = data[2].toLowerCase();
  1618.                     } else {
  1619.                         error(cc.red('/ban: invalid duration. Type /bandurations or /bd for a list of valid options'));
  1620.                         return;
  1621.                     }
  1622.  
  1623.                     if (~msg.indexOf('@')) {
  1624.                         let user = getUser(msg.substr(msg.indexOf('@')+1));
  1625.                         if (~user) {
  1626.                             banData[0] = parseInt(user.id);
  1627.                         } else {
  1628.                             error(cc.red('/ban: @' + msg.substr(msg.indexOf('@')+1) + ' not found.'));
  1629.                             return;
  1630.                         }
  1631.                     } else {
  1632.                         if (strIsNum(data[3])) {
  1633.                             banData[0] = parseInt(data[3]);
  1634.                         } else {
  1635.                             error(cc.red('/ban: ' + data[3] + ' is not a valid ID.'));
  1636.                             return;
  1637.                         }
  1638.                     }
  1639.  
  1640.                     banUser(banData[0], banData[1], banData[2]);
  1641.                 } else {
  1642.                     nodeLog(cc.green('/ban usage: /ban <reason #: type /banreasons|/br for a list> <duration: type /bandurations|/bd for list> <@username OR user ID>'));
  1643.                 }
  1644.             },
  1645.  
  1646.             '/kick':()=>{
  1647.                 if (data.length >= 3) {
  1648.                     let kickData = new Array(2);
  1649.  
  1650.                     if (~msg.indexOf('@')) {
  1651.                         let user = getUser(msg.substr(msg.indexOf('@')+1));
  1652.                         if (~user) {
  1653.                             kickData[0] = parseInt(user.id);
  1654.                         } else {
  1655.                             error(cc.red('/kick: @' + msg.substr(msg.indexOf('@')+1) + ' not found.'));
  1656.                             return;
  1657.                         }
  1658.                     } else {
  1659.                         if (strIsNum(data[2])) {
  1660.                             if (~getUser(data[2]))
  1661.                                 kickData[0] = parseInt(data[2]);
  1662.                             else
  1663.                                 error(cc.red('/kick: ' + data[2] + ' not found in room.'));
  1664.                         } else {
  1665.                             error(cc.red('/kick: ' + data[2] + ' is not a valid ID.'));
  1666.                             return;
  1667.                         }
  1668.                     }
  1669.  
  1670.                     if (REASONS[0].hasOwnProperty(parseInt(data[1]))) {
  1671.                         kickData[1] = parseInt(data[1]);
  1672.                     } else {
  1673.                         error(cc.red('/kick: invalid reason #. Type /banreasons or /br for a list of valid options'));
  1674.                         return;
  1675.                     }
  1676.                     kickUser(kickData[0], kickData[1]);
  1677.                 } else {
  1678.                     nodeLog(cc.green('/kick usage: /kick <reason: type /banreasons|/br for a list> <@username OR user ID>'));
  1679.                 }
  1680.             },
  1681.  
  1682.             '/mute':()=>{
  1683.                 if (data.length >= 4) {
  1684.                     let muteData = new Array(3);
  1685.  
  1686.                     if (REASONS[1].hasOwnProperty(parseInt(data[1]))) {
  1687.                         muteData[1] = parseInt(data[1]);
  1688.                     } else {
  1689.                         error(cc.red('/mute: invalid reason #. Type /mutereasons or /mr for a list of valid options'));
  1690.                         return;
  1691.                     }
  1692.  
  1693.                     if (DURATIONS[1].hasOwnProperty(data[2].toLowerCase())) {
  1694.                         muteData[2] = data[2].toLowerCase();
  1695.                     } else {
  1696.                         error(cc.red('/mute: invalid duration. Type /mutedurations or /md for a list of valid options'));
  1697.                         return;
  1698.                     }
  1699.  
  1700.                     if (~msg.indexOf('@')) {
  1701.                         let user = getUser(msg.substr(msg.indexOf('@')+1));
  1702.                         if (~user) {
  1703.                             muteData[0] = parseInt(user.id);
  1704.                         } else {
  1705.                             error(cc.red('/mute: @' + msg.substr(msg.indexOf('@')+1) + ' not found.'));
  1706.                             return;
  1707.                         }
  1708.                     } else {
  1709.                         if (strIsNum(data[3])) {
  1710.                             muteData[0] = parseInt(data[3]);
  1711.                         } else {
  1712.                             error(cc.red('/mute: ' + data[3] + ' is not a valid ID.'));
  1713.                             return;
  1714.                         }
  1715.                     }
  1716.  
  1717.                     muteUser(muteData[0], muteData[1], muteData[2]);
  1718.                 } else {
  1719.                     nodeLog(cc.green('/mute usage: /mute <reason #: type /mutereasons|/mr for a list> <duration: type /mutedurations|/md for list> <@username OR user ID>'));
  1720.                 }
  1721.             },
  1722.  
  1723.             '/unban':()=>{
  1724.                 if (strIsNum(data[1]))
  1725.                     unbanUser(parseInt(data[1]));
  1726.             },
  1727.  
  1728.             '/banlist':()=>{
  1729.                 nodeLog(cc.green("Downloading ban list..."));
  1730.                 getBans((data)=>{
  1731.                     if (data.status === "ok" && room) {
  1732.                         let bfr = "ROOM: " + room.slug + "\nTIME: " + Date().toString() + "\nBANS: " + data.data.length + "\n\n   NAME    ::    UserID    ::    MODERATOR    ::   BANTIME   ::   DURATION   ::   REASON\n\n",
  1733.                             i;
  1734.                         const list = data.data;
  1735.                         for (i = 0; i < list.length; i++) {
  1736.                             bfr += list[i].username + " :: " + list[i].id + " :: " + list[i].moderator + " :: " + list[i].timestamp + " :: " + DURATIONS[0][list[i].duration].toLowerCase() + " :: " + REASONS[0][list[i].reason] + "\n";
  1737.                         }
  1738.                         fs.writeFile('data/banList_' + room.slug + '.txt', bfr, function(e) {
  1739.                             if (e) error(cc.red(e));
  1740.                             else {
  1741.                                 nodeLog(cc.greenBright("Successfully wrote ban list to data/banList_" + room.slug + ".txt"));
  1742.                             }
  1743.                         });
  1744.                     }
  1745.                 });
  1746.             },
  1747.  
  1748.             '/stafflist':()=>{
  1749.                 nodeLog(cc.green("Downloading staff list..."));
  1750.                 GET('_/staff', (data)=>{
  1751.                    if (data.status === "ok" && room) {
  1752.                         let bfr = "ROOM: " + room.slug + "\nTIME: " + Date().toString() + "\n\n   NAME   ::   UserID   ::   GLOBAL ROLE\n\n",
  1753.                             i,
  1754.                             unk = [],
  1755.                             rdjs = [],
  1756.                             boun = [],
  1757.                             mgr = [],
  1758.                             coh = [],
  1759.                             host = [];
  1760.                         const list = data.data,
  1761.                               toStr = function(user) {
  1762.                                 return user.username + " :: " + user.id + " :: " + gRoleToString(user.gRole) + "\n";
  1763.                               };
  1764.                         for (i = 0; i < list.length; i++) {
  1765.                             switch (list[i].role) {
  1766.                                 case 5: host.push(list[i]); break;
  1767.                                 case 4: coh.push(list[i]); break;
  1768.                                 case 3: mgr.push(list[i]); break;
  1769.                                 case 2: boun.push(list[i]); break;
  1770.                                 case 1: rdjs.push(list[i]); break;
  1771.                                 default: unk.push(list[i]); break;
  1772.                             }
  1773.                         }
  1774.                         bfr += "\n------------------------\nHost (" + host.length + ")\n------------------------\n\n";
  1775.                         for (i = 0; i < host.length; i++) { bfr += toStr(host[i]);}
  1776.  
  1777.                         bfr += "\n------------------------\nCo-Hosts (" + coh.length + ")\n------------------------\n\n";
  1778.                         for (i = 0; i < coh.length; i++) { bfr += toStr(coh[i]);}
  1779.  
  1780.                         bfr += "\n------------------------\nManagers (" + mgr.length + ")\n------------------------\n\n";
  1781.                         for (i = 0; i < mgr.length; i++) { bfr += toStr(mgr[i]);}
  1782.  
  1783.                         bfr += "\n------------------------\nBouncers (" + boun.length + ")\n------------------------\n\n";
  1784.                         for (i = 0; i < boun.length; i++) { bfr += toStr(boun[i]);}
  1785.  
  1786.                         bfr += "\n------------------------\nResident DJs (" + rdjs.length + ")\n------------------------\n\n";
  1787.                         for (i = 0; i < rdjs.length; i++) { bfr += toStr(rdjs[i]);}
  1788.  
  1789.                         if (unk.length > 0) {
  1790.                             bfr += "\n------------------------\nUnknown (" + unk.length + ")\n------------------------\n\n";
  1791.                             for (i = 0; i < unk.length; i++) { bfr += "(role:" + unk[i].role + ") " + toStr(unk[i]);}
  1792.                         }
  1793.  
  1794.                         fs.writeFile('data/staffList_' + room.slug + '.txt', bfr, function(e) {
  1795.                             if (e) error(cc.red(e));
  1796.                             else {
  1797.                                 nodeLog(cc.greenBright("Successfully wrote staff list to data/staffList_" + room.slug + ".txt"));
  1798.                             }
  1799.                         });
  1800.                     }
  1801.                 });
  1802.             },
  1803.  
  1804.             '/syncusers':()=>{
  1805.                 syncUsers();
  1806.             },
  1807.  
  1808.             '/unmute':()=>{
  1809.                 simpleNameFn(unmuteUser);
  1810.             },
  1811.  
  1812.             '/roles':()=>{
  1813.                 info(cc.cyan("::: ") + cc.cyanBright("ROLES") + cc.cyan(" :::"));
  1814.                 for (i = 1; i <= 5; i++) {
  1815.                     info(cc.blueBright(roleToString(i)) + " " + cc.blue(i));
  1816.                 }
  1817.             },
  1818.  
  1819.             '/addstaff':()=>{
  1820.                 if (data.length >= 3) {
  1821.                     const role = parseInt(data[1]);
  1822.                     if (role < 1 || role > 5) {
  1823.                         return error(cc.red("Role must be between 1 and 5. Type /roles for a list"));
  1824.                     } else if (me.role < 5 && role >= me.role) {
  1825.                         return error(cc.red("You cannot promote someone to your rank or above."));
  1826.                     } else {
  1827.                         if (strIsNum(data[2]))
  1828.                             addStaff(parseInt(data[2]), role);
  1829.                         else {
  1830.                             let atIndex = (data[0] + " " + data[1]).length;
  1831.                             if (~msg.indexOf('@', atIndex)) {
  1832.                                 const user = getUser(msg.substr(msg.indexOf('@', atIndex) + 1));
  1833.                                 if (~user) addStaff(user.id, role);
  1834.                             }
  1835.                         }
  1836.                     }
  1837.                 } else {
  1838.                     nodeLog(cc.green("addstaff usage: /addstaff <role 1-5> <@username (in room)|user ID>"));
  1839.                 }
  1840.             },
  1841.  
  1842.             '/cycle':()=>{
  1843.                 if (data[1] && (data[1].toLowerCase() === "on" || data[1].toLowerCase() === "off")) {
  1844.                     let arg = data[1].toLowerCase();
  1845.                     if (arg === "on")
  1846.                         changeDJCycle(true);
  1847.                     else if (arg === "off")
  1848.                         changeDJCycle(false);
  1849.                 } else {
  1850.                     let msg = cc.green('/cycle <on|off> changes the DJ cycle state in the room.');
  1851.                     if (room && typeof room.booth.shouldCycle === "boolean") {
  1852.                         msg += cc.green(' DJ Cycle is currently ') + (room.booth.shouldCycle ? cc.greenBright('on') : cc.redBright('off')) + cc.green('.');
  1853.                     }
  1854.                     nodeLog(msg);
  1855.                 }
  1856.             },
  1857.  
  1858.             '/writeplaylists':()=>{
  1859.                 GET('_/playlists', (data)=>{
  1860.                     fs.writeFile('data/playlists.json', JSON.stringify(data), (err)=>{if (err) error(cc.red(err))});
  1861.                 });
  1862.             },
  1863.  
  1864.             '/disable':()=>{
  1865.                 if (data[1] && commands[data[1]]) {
  1866.                     if (!commands[data[1]].state) {
  1867.                         error(cc.redBright(data[1]) + cc.red(' is already disabled.'));
  1868.                     } else {
  1869.                         commands[data[1]].state = false;
  1870.                         nodeLog(cc.greenBright(data[1]) + cc.red(' disabled.'));
  1871.                     }
  1872.                 } else {
  1873.                     error(cc.red("/disable: Invalid usage, or given command does not exist."))
  1874.                 }
  1875.             },
  1876.  
  1877.             '/enable':()=>{
  1878.                 if (data[1] && commands[data[1]]) {
  1879.                     if (commands[data[1]].state) {
  1880.                         error(cc.redBright(data[1]) + cc.red(' is already enabled.'));
  1881.                     } else {
  1882.                         commands[data[1]].state = true;
  1883.                         nodeLog(cc.greenBright(data[1]) + cc.green(' enabled.'));
  1884.                     }
  1885.                 } else {
  1886.                     error(cc.red("/enable: Invalid usage, or given command does not exist."))
  1887.                 }
  1888.             },
  1889.  
  1890.             '/home':()=>{
  1891.                 if (room.slug.toLowerCase() !== HOME.toLowerCase()) {
  1892.                     activeChecks();
  1893.                     joinRoom(HOME);
  1894.                 } else {
  1895.                     error(cc.red('You are already in your HOME room.'));
  1896.                 }
  1897.             },
  1898.  
  1899.             '/trigger':()=>{
  1900.                 if (data[1]) {
  1901.                     if (typeof data[1] === "string" && data[1].length === 1 && ~'!#$%^&*()_+-=`~.,?'.indexOf(data[1])) {
  1902.                         TRIGGER = data[1];
  1903.                         nodeLog(cc.green('Set trigger to ')+cc.greenBright(data[1]));
  1904.                     }
  1905.                 } else {
  1906.                     validateTrigger();
  1907.                     nodeLog(cc.green('Current trigger: ')+cc.magentaBright(TRIGGER));
  1908.                 }
  1909.             },
  1910.  
  1911.             '/me':()=>{
  1912.                 sendMessage(msg,0);
  1913.             },
  1914.  
  1915.             '/logout':()=>{
  1916.                 //logout();
  1917.             },
  1918.  
  1919.             '/set':()=>{
  1920.                 if (data.length < 3) {
  1921.                     BotSettings._fn.listSettings();
  1922.                     nodeLog(cc.green('/set usage: "/set <option> <value>"'));
  1923.                 } else {
  1924.                     switch (data[1].toLowerCase()) {
  1925.                         case 'motd':
  1926.                             BotSettings._fn.changeSetting('motd', msg.substr(10));
  1927.                             break;
  1928.                         default:
  1929.                             BotSettings._fn.changeSetting(data[1], data[2]);
  1930.                             break;
  1931.                     }
  1932.                 }
  1933.             },
  1934.  
  1935.             '/dc':()=>{
  1936.                 cleanDC();
  1937.                 if (data.length === 2) {
  1938.                     if (strIsNum(data[1]) && room) {
  1939.                         let seendata = seen[room.slug][parseInt(data[1])];
  1940.                         if (seendata)
  1941.                             info(cc.magentaBright(data[1]) + cc.magenta(" " + secsToLabelTime(Date.now() - seendata.lastDisconnect, true) + " " + getUserDC(parseInt(data[1]))));
  1942.                         else
  1943.                             info(cc.magentaBright(data[1]) + cc.magenta(" not found"));
  1944.                     }
  1945.                 } else {
  1946.                     console.log(JSON.stringify(disconnects));
  1947.                 }
  1948.             },
  1949.  
  1950.             '/link':()=>{
  1951.                 outputCurrentLink(false);
  1952.             },
  1953.  
  1954.             '/banreasons':()=>{
  1955.                 info(cc.cyan("::") + " " + cc.cyanBright("BAN REASONS") + " " + cc.cyan("::"));
  1956.                 for (i in REASONS[0]) {
  1957.                     info(cc.blueBright(i + " ") + cc.blue(REASONS[0][i]));
  1958.                 }
  1959.             },
  1960.  
  1961.             '/bandurations':()=>{
  1962.                 info(cc.cyan("::") + " " + cc.cyanBright("BAN DURATIONS") + " " + cc.cyan("::"));
  1963.                 for (i in DURATIONS[0]) {
  1964.                     info(cc.blueBright(i + " ") + cc.blue(DURATIONS[0][i]));
  1965.                 }
  1966.             },
  1967.  
  1968.             '/mutereasons':()=>{
  1969.                 info(cc.cyan("::") + " " + cc.cyanBright("MUTE REASONS") + " " + cc.cyan("::"));
  1970.                 for (i in REASONS[1]) {
  1971.                     info(cc.blueBright(i + " ") + cc.blue(REASONS[1][i]));
  1972.                 }
  1973.             },
  1974.  
  1975.             '/mutedurations':()=>{
  1976.                 info(cc.cyan("::") + " " + cc.cyanBright("MUTE DURATIONS") + " " + cc.cyan("::"));
  1977.                 for (i in DURATIONS[1]) {
  1978.                     info(cc.blueBright(i + " ") + cc.blue(DURATIONS[1][i]));
  1979.                 }
  1980.             },
  1981.  
  1982.             '/loadblacklists':()=>{
  1983.                 loadBlacklists();
  1984.             },
  1985.  
  1986.             '/deletemsg':()=>{
  1987.                 if (data[1])
  1988.                     deleteMessage(data[1]);
  1989.             },
  1990.  
  1991.             '/removestaff':()=>{
  1992.                 simpleNameFn(removeUserFromStaff);
  1993.             },
  1994.  
  1995.             '/removedj':()=>{
  1996.                 simpleNameFn(removeUserFromWaitlist);
  1997.             },
  1998.  
  1999.             '/playlist':()=>{
  2000.                 showActivePlaylist();
  2001.             },
  2002.  
  2003.             '/setplaylist':()=>{
  2004.                 if (strIsNum(data[1]))
  2005.                     activatePlaylist(parseInt(data[1]));
  2006.             },
  2007.  
  2008.             '/clear':()=>{
  2009.                 clearWindow();
  2010.             },
  2011.  
  2012.             '/exit':()=>{
  2013.                 rl.close();
  2014.                 wss.close(1000, "User is exiting the program.");
  2015.                 setTimeout(()=>process.exit(0),500);
  2016.             },
  2017.  
  2018.             '/userlist':()=>{
  2019.                 displayUsers();
  2020.             },
  2021.  
  2022.             '/join':()=>{
  2023.                 if (data[1]) {
  2024.                     activeChecks();
  2025.                     joinRoom(data[1]);
  2026.                 }
  2027.             },
  2028.  
  2029.             '/woot':()=>{
  2030.                 if (room) room.woot();
  2031.             },
  2032.  
  2033.             '/meh':()=>{
  2034.                 if (room) room.meh();
  2035.             },
  2036.  
  2037.             '/grab':()=>{
  2038.                 if (room) room.grab();
  2039.             },
  2040.  
  2041.             '/votes':()=>{
  2042.                 logVotes();
  2043.             },
  2044.  
  2045.             '/friend':()=>{
  2046.                 if (data.length >= 3) {
  2047.                     if (data[1] === "a" || data[1] === "add") {
  2048.                         sendFriendRequest(data[2]);
  2049.                     }
  2050.                 }
  2051.             },
  2052.  
  2053.             '/waitlist':()=>{
  2054.                 if (data.length === 1)
  2055.                     displayWaitlist();
  2056.                 else if (data.length > 1) {
  2057.                     switch (data[1].toLowerCase()) {
  2058.                         case "join":
  2059.                         case "j":
  2060.                             joinWaitlist();
  2061.                             break;
  2062.                         case "leave":
  2063.                         case "l":
  2064.                             leaveWaitlist();
  2065.                             break;
  2066.                         case "lock":
  2067.                             if (data[2] && data[2].toLowerCase() === "clear")
  2068.                                 waitlistLock(true,true);
  2069.                             else
  2070.                                 waitlistLock(true,false);
  2071.                             break;
  2072.                         case "unlock":
  2073.                             waitlistLock(false, false);
  2074.                             break;
  2075.                         default:
  2076.                             error(cc.red("Unknown argument: ") + cc.redBright(data[1]));
  2077.                             break;
  2078.                     }
  2079.                 }
  2080.             },
  2081.  
  2082.             '/reloadsettings':()=>{
  2083.                 if (!EXTERNALSETTINGS)
  2084.                     error(cc.red("Cannot load settings.json: ") + cc.redBright("EXTERNALSETTINGS") + cc.red(" is set to ") + cc.redBright("false") + cc.red("."));
  2085.                 else
  2086.                     BotSettings._fn.loadFromFile();
  2087.             },
  2088.  
  2089.             '/saveseen':()=>{
  2090.                 activeChecks();
  2091.                 if (seen && seen !== {})
  2092.                     fs.writeFile('data/seenUsers.json', JSON.stringify(seen), (e)=>{if (e) error(cc.red(e)); else nodeLog(cc.green('data/seenUsers.json saved'))});
  2093.                 else
  2094.                     error(cc.red("Seen user records were not saved because they are empty."));
  2095.             },
  2096.  
  2097.             '/listsettings':()=>{
  2098.                 BotSettings._fn.printSettings();
  2099.             },
  2100.  
  2101.             '/showuser':()=>{
  2102.                 let printinfo = function(user) {
  2103.                     let i;
  2104.                     info(cc.cyan("User Info: ") + cc.cyanBright(user.username));
  2105.                     for (i in user) {
  2106.                         if (i !== "username" && typeof user[i] !== "object") {
  2107.                             info(cc.blueBright(i) + " " + cc.blue(user[i]));
  2108.                         }
  2109.                     }
  2110.                 };
  2111.  
  2112.                 if (data.length === 1 && me) printinfo(me);
  2113.                 else if (data.length > 1) {
  2114.                     let item = "";
  2115.                     if (~msg.indexOf(" ")) {
  2116.                         item = msg.substr(msg.indexOf(" ")+1);
  2117.                     }
  2118.                     let user = -1;
  2119.                     if (!isNaN(parseInt(item)))
  2120.                         user = getUser(parseInt(item));
  2121.                     else
  2122.                         user = getUser(item);
  2123.  
  2124.                     if (~user) {
  2125.                         printinfo(user);
  2126.                     } else {
  2127.                         error(cc.red('Could not find: ') + cc.redBright(item) + cc.red('. If you\'re trying a username, try searching by user ID instead.'));
  2128.                     }
  2129.                 }
  2130.             },
  2131.  
  2132.             '/commands':()=>{
  2133.                 let active = [],
  2134.                     inactive = [],
  2135.                     i;
  2136.                 for (i in commands) {
  2137.                     if (commands[i].state) {
  2138.                         active.push(cc.greenBright(i));
  2139.                     } else {
  2140.                         inactive.push(cc.redBright(i));
  2141.                     }
  2142.                 }
  2143.                 nodeLog(cc.green('Active commands: ') + active.join(cc.blackBright(', ')));
  2144.                 nodeLog(cc.red('Inactive commands: ') + inactive.join(cc.blackBright(', ')));
  2145.                 nodeLog(cc.magenta('Command-line commands and how to use them: https://git.io/vMyMD'));
  2146.             },
  2147.  
  2148.             '/welcome':()=>{
  2149.                 if (room) {
  2150.                     if (data.length >= 1)
  2151.                         console.log('\n' + timestamp() + cc.blueBright('/// ') + cc.greenBright(room.meta.welcome).trim() + cc.blueBright(' ///') + '\n');
  2152.                     if (data.length > 2 && data[1] === "set")
  2153.                         updateRoomInfo({welcome: msg.substr('/welcome set '.length)});
  2154.                 }
  2155.             },
  2156.  
  2157.             '/description':()=>{
  2158.                 if (room) {
  2159.                     if (data.length >= 1)
  2160.                         console.log('\n' + timestamp() + cc.blueBright('::: ') + cc.greenBright(room.meta.description.replace(/\n/g, '\\n')).trim() + cc.blueBright(' :::') + '\n');
  2161.                     if (data.length > 2 && data[1] === "set")
  2162.                         updateRoomInfo({description: msg.substr('/description set '.length).replace(/\\n/g, '\n')});
  2163.                 }
  2164.             },
  2165.  
  2166.             '/movedj':()=>{
  2167.                 if (data.length < 3 || room.getWaitlist().length < 1) return;
  2168.                 let to = parseInt(data[1]);
  2169.                 if (!isNaN(to) && to >= 1) {
  2170.                     if (to > room.getWaitlist().length) to = room.getWaitlist().length;
  2171.                     if (strIsNum(data[2])) {
  2172.                         const id = parseInt(data[2]);
  2173.                         if (!~getWaitlistPos(id) || getWaitlistPos(id) === to) return;
  2174.                         moveDJ(parseInt(data[2]), to - 1);
  2175.                     } else if (~msg.indexOf('@', ('/movedj '+ data[1]).length)) {
  2176.                         const user = getUser(msg.substr(msg.indexOf('@', ('/movedj '+ data[1]).length)+1));
  2177.                         if (!~getWaitlistPos(user.id) || getWaitlistPos(user.id) === to) return;
  2178.                         if (~user) moveDJ(user.id, to - 1);
  2179.                     }
  2180.                 }
  2181.  
  2182.             },
  2183.  
  2184.             '/adddj':()=>{
  2185.                 simpleNameFn(function(id) {
  2186.                     addUserToWaitlist(id);
  2187.                 });
  2188.             },
  2189.  
  2190.             '/update':()=>{
  2191.                 req.get('https://rawgit.com/zeratul0/aiurbot/master/version.json', {json: true}, (e,r,b)=>{
  2192.                     if (e) error(cc.red(e));
  2193.                     else if (b && typeof b === "string") {
  2194.                         const checkedVersion = b.replace(/\n/g, '');
  2195.                         info(cc.magentaBright("https://github.com/zeratul0/aiurbot"));
  2196.                         if (checkedVersion === VER) {
  2197.                             return info(cc.greenBright("Requested version, got ") + cc.cyanBright(checkedVersion) + cc.greenBright(", the same as the current version."));
  2198.                         } else {
  2199.                             info(cc.cyan("Requested version, got ") + cc.greenBright(checkedVersion) + cc.cyan(", which is different from the current version: ") + cc.redBright(VER));
  2200.                             info(cc.cyan("Check the above github link and replace your index.js if needed."));
  2201.                             return;
  2202.                         }
  2203.                     }
  2204.                 });
  2205.             }
  2206.         },
  2207.         alias = {
  2208.             //alias     //command to execute
  2209.             '/br':      '/banreasons',
  2210.             '/bd':      '/bandurations',
  2211.             '/mr':      '/mutereasons',
  2212.             '/md':      '/mutedurations',
  2213.             '/loadbl':  '/loadblacklists',
  2214.             '/delmsg':  '/deletemsg',
  2215.             '/dm':      '/deletemsg',
  2216.             '/rmstaff': '/removestaff',
  2217.             '/unqueue': '/removedj',
  2218.             '/undj':    '/removedj',
  2219.             '/rmdj':    '/removedj',
  2220.             '/pl':      '/playlist',
  2221.             '/setpl':   '/setplaylist',
  2222.             '/cls':     '/clear',
  2223.             '/quit':    '/exit',
  2224.             '/users':   '/userlist',
  2225.             '/u':       '/userlist',
  2226.             '/j':       '/join',
  2227.             '/room':    '/join',
  2228.             '/r':       '/join',
  2229.             '/w':       '/woot',
  2230.             '/m':       '/meh',
  2231.             '/g':       '/grab',
  2232.             '/v':       '/votes',
  2233.             '/f':       '/friend',
  2234.             '/wl':      '/waitlist',
  2235.             '/djs':     '/waitlist',
  2236.             '/djlist':  '/waitlist',
  2237.             '/rs':      '/reloadsettings',
  2238.             '/ss':      '/saveseen',
  2239.             '/ls':      '/listsettings',
  2240.             '/getuser': '/showuser',
  2241.             '/user':    '/showuser',
  2242.             '/cmds':    '/commands',
  2243.             '/queue':   '/adddj'
  2244.         };
  2245.  
  2246.     if (data[0]) {
  2247.         let cmd = data[0].toLowerCase();
  2248.         return (cmds[cmd] || cmds[alias[cmd]] || function() {error(cc.red('Unknown command: ')+cc.redBright(cmd))})();
  2249.     }
  2250.  
  2251. }
  2252.  
  2253. function updateMentionRegex() {
  2254.     if (me && me.username && ~me.role) {
  2255.         let mentions = [me.username, "everyone"];
  2256.  
  2257.         if (~getWaitlistPos())
  2258.             mentions.push("djs");
  2259.         if (me.role > 0) {
  2260.             mentions.push("staff");
  2261.             if (me.role === 1)
  2262.                 mentions.push("rdjs");
  2263.             else if (me.role === 2)
  2264.                 mentions.push("bouncers");
  2265.             else if (me.role === 3)
  2266.                 mentions.push("managers");
  2267.             else if (me.role >= 4) //co-hosts belong with hosts I think...?
  2268.                 mentions.push("hosts");
  2269.         }
  2270.  
  2271.         mentions = mentions.join("|");
  2272.  
  2273.         MENTIONREGEX = new RegExp('\@(?:' + mentions + ')', 'gi');
  2274.     } else {
  2275.         MENTIONREGEX = null;
  2276.     }
  2277. }
  2278.  
  2279. function muteDurationToString(data) {
  2280.     switch (data) {
  2281.         case "o": return "unmuted";
  2282.         case "s": return "15 minutes";
  2283.         case "m": return "30 minutes";
  2284.         case "l": return "45 minutes";
  2285.         default: return "unknown time: "+data;
  2286.     }
  2287. }
  2288.  
  2289. function getSeenData(uid) {
  2290.     if (room && seen && seen[room.slug] && seen[room.slug][uid]) {
  2291.         return seen[room.slug][uid];
  2292.     } else {
  2293.         return -1;
  2294.     }
  2295. }
  2296.  
  2297. function outputCurrentLink(send) {
  2298.     if (room && room.playback && room.playback.m.format && SC_CLIENT_ID) {
  2299.         let msg = "";
  2300.         if (room.playback.m.format === 2) {
  2301.             req.get('https://api.soundcloud.com/tracks/' + room.playback.m.cid + '.json?client_id=' + SC_CLIENT_ID, {json: true}, function(e,r,b) {
  2302.                 if (e) error(cc.red(e));
  2303.                 else if (b && b['permalink_url'])
  2304.                     msg = 'Currently playing: ' + b.permalink_url;
  2305.                 else
  2306.                     msg = 'Could not find a link for this song.';
  2307.  
  2308.                 if (send)
  2309.                     sendMessage(msg, 500);
  2310.                 else
  2311.                     nodeLog(cc.yellowBright(msg));
  2312.             });
  2313.         } else {
  2314.             if (room.playback.m.format === 1) {
  2315.                 msg = "Currently playing: https://youtu.be/"+room.playback.m.cid;
  2316.             } else {
  2317.                 msg = "Unknown format, or no song is playing.";
  2318.             }
  2319.  
  2320.             if (send)
  2321.                 sendMessage(msg, 500);
  2322.             else
  2323.                 nodeLog(cc.yellowBright(msg));
  2324.         }
  2325.     }
  2326. }
  2327.  
  2328. function banReasonToString(num) {
  2329.     if (REASONS[0].hasOwnProperty(num)) {
  2330.         return REASONS[0][num];
  2331.     } else {
  2332.         return 'unknown reason: '+num;
  2333.     }
  2334. }
  2335.  
  2336. function getUserRecord(UID) {
  2337.     UID = parseInt(UID);
  2338.     if (!isNaN(UID) && room && seen && seen[room.slug] && seen[room.slug][UID])
  2339.         return seen[room.slug][UID];
  2340.     return -1;
  2341. }
  2342.  
  2343. function muteReasonToString(num) {
  2344.     if (REASONS[1].hasOwnProperty(num)) {
  2345.         return REASONS[1][num];
  2346.     } else {
  2347.         return 'unknown reason: '+num;
  2348.     }
  2349. }
  2350.  
  2351. function getHistoryIdx(format, cid) {
  2352.     if (room) {
  2353.         let history = room.history,
  2354.             i;
  2355.         for (i = 0; i < history.length; i++) {
  2356.             if (history[i].format + ":" + history[i].cid === format + ":" + cid) {
  2357.                 return i;
  2358.             }
  2359.         }
  2360.     }
  2361.     return -1;
  2362. }
  2363.  
  2364. function secsToTime(num) {
  2365.     let hours = Math.floor(num / 3600);
  2366.     let minutes = Math.floor((num - (hours * 3600)) / 60);
  2367.     let seconds = num - (hours * 3600) - (minutes * 60);
  2368.  
  2369.     if (minutes < 10 && hours > 0)
  2370.         minutes = "0" + minutes;
  2371.  
  2372.     if (seconds < 10)
  2373.         seconds = "0" + seconds;
  2374.  
  2375.     let time = "";
  2376.     if (hours !== 0)
  2377.         time += hours + ':';
  2378.  
  2379.     time += minutes + ':' + seconds;
  2380.     return time;
  2381. }
  2382.  
  2383. function secsToLabelTime(num, ms) {
  2384.     if (ms) num = Math.floor(num / 1000);
  2385.     let days = Math.floor(num / 86400);
  2386.     let hours = Math.floor((num - (days * 86400)) / 3600);
  2387.     let minutes = Math.floor((num - (days * 86400) - (hours * 3600)) / 60);
  2388.     let seconds = num - (days * 86400) - (hours * 3600) - (minutes * 60);
  2389.  
  2390.     if (hours < 10 && days > 0)
  2391.         hours = "0" + hours;
  2392.  
  2393.     if (minutes < 10 && (hours > 0 || hours === "00"))
  2394.         minutes = "0" + minutes;
  2395.  
  2396.     if (seconds < 10)
  2397.         seconds = "0" + seconds;
  2398.  
  2399.     let time = "";
  2400.     if (days !== 0)
  2401.         time += days +'d';
  2402.     if (hours !== 0 || days > 0)
  2403.         time += hours + 'h';
  2404.     if (minutes !== 0 || hours > 0)
  2405.         time += minutes + 'm';
  2406.  
  2407.     time += seconds + 's';
  2408.     return time;
  2409. }
  2410.  
  2411. function arrFind(arr, item) {
  2412.     let i;
  2413.     for (i = 0; i < arr.length; i++) {
  2414.         if (arr[i] === item)
  2415.             return i;
  2416.     }
  2417.     return -1;
  2418. }
  2419.  
  2420. function skipSong(caller, reason, auto, moveUp) {
  2421.  
  2422.     if (room && room.booth && room.playback['h']) {
  2423.  
  2424.         if (reason && reason !== "none") {
  2425.             if (!BotSettings.skipReasons.hasOwnProperty(reason))
  2426.                 return;
  2427.         }
  2428.  
  2429.         if (room.booth.currentDJ) {
  2430.  
  2431.             let msg = "";
  2432.             if (caller) {
  2433.                 msg += "[@"+caller+" skipped";
  2434.                 if (auto)
  2435.                     msg += " automatically";
  2436.                 msg += "] ";
  2437.             } else {
  2438.                 if (auto)
  2439.                     msg += "Automatically skipped. "
  2440.             }
  2441.  
  2442.             const move = function(dj) {
  2443.                 if (moveUp)
  2444.                     setTimeout(function() {
  2445.                         addUserToWaitlist(dj, function() {
  2446.                             moveDJ(dj, 0);
  2447.                         });
  2448.                     }, 2000);
  2449.             };
  2450.  
  2451.             if (room.booth.currentDJ === me.id) {
  2452.                 POST('_/booth/skip/me', null, (data)=>{
  2453.                     if (data.status === "ok" && msg !== "") {
  2454.                         if (reason && BotSettings.skipReasons.hasOwnProperty(reason) && typeof BotSettings.skipReasons[reason] === "string")
  2455.                             msg += '@' + me.username + ', ' + BotSettings.skipReasons[reason];
  2456.                         sendMessage(msg, 800);
  2457.                         move(me.id);
  2458.                     }
  2459.                 });
  2460.             } else {
  2461.                 let usr = getUser(room.booth.currentDJ);
  2462.  
  2463.                 POST('_/booth/skip', {userID: room.booth.currentDJ, historyID: room.playback['h']}, (data)=>{
  2464.                     if (data.status === "ok") {
  2465.                         if (~usr)
  2466.                             move(usr.id);
  2467.                         if (~usr && reason && BotSettings.skipReasons.hasOwnProperty(reason) && typeof BotSettings.skipReasons[reason] === "string")
  2468.                             msg += '@' + usr.username + ', ' + BotSettings.skipReasons[reason];
  2469.                         if (msg !== "")
  2470.                             sendMessage(msg, 800);
  2471.                     }
  2472.                 });
  2473.             }
  2474.         }
  2475.  
  2476.     }
  2477. }
  2478.  
  2479. function addStaff(userID, role) {
  2480.     if (userID === me.id) return;
  2481.     POST('_/staff/update', {userID: userID, roleID: role}, (data)=>{
  2482.         if (data.status === "ok") {
  2483.             nodeLog(cc.green("Successfully added " + userID + " to the staff."));
  2484.         } else {
  2485.             error(cc.red("Error adding " + userID + " to the staff."));
  2486.         }
  2487.     });
  2488. }
  2489.  
  2490. function isUnavailable(format, cid, cb) {
  2491.     if (room && format && cid && YT_API_KEY) {
  2492.         let url = "";
  2493.         let compare = null;
  2494.  
  2495.         if (format === 1) {
  2496.  
  2497.             url = 'https://www.googleapis.com/youtube/v3/videos?id=' + cid + '&key=' + YT_API_KEY + '&part=snippet';
  2498.             compare = body=>{ return body.items.length < 1; };
  2499.  
  2500.         } else if (format === 2) {
  2501.  
  2502.             url = 'https://api.soundcloud.com/tracks/' + cid + '.json?client_id=' + SC_CLIENT_ID;
  2503.             compare = body=>{ return !body.title; };
  2504.  
  2505.         }
  2506.  
  2507.         req.get(url, {json: true}, function(e,r,b) {
  2508.             if (e) error(cc.red(e));
  2509.             else if (b && url !== "" && compare !== null && r.statusMessage === "OK") {
  2510.                 if (compare(b))
  2511.                     return cb(1);
  2512.                 else
  2513.                     return cb(0);
  2514.             } else if (r && r.statusCode === 404)
  2515.                     return cb(1);
  2516.  
  2517.             return cb(-1);
  2518.         });
  2519.     }
  2520. }
  2521.  
  2522. function addUserToWaitlist(UID, cb) {
  2523.  
  2524.     if (!cb || !(typeof cb === "function")) cb = function(){};
  2525.  
  2526.     if (getWaitlistPos(UID) >= 0) {
  2527.         cb();
  2528.     } else {
  2529.         POST('_/booth/add', {id: UID}, (res)=>{
  2530.             cb();
  2531.         });
  2532.     }
  2533.  
  2534. }
  2535.  
  2536. function moveDJ(UID, toPosition, cb) {
  2537.     if (getWaitlistPos(UID) === toPosition) return;
  2538.     POST('_/booth/move', {userID: UID, position: toPosition}, (data)=>{
  2539.         if (typeof cb === "function") {
  2540.             cb(data);
  2541.         }
  2542.     });
  2543. }
  2544.  
  2545. /*function giftUser(UID, amount, cb) {
  2546.     UID = parseInt(UID);
  2547.     amount = parseInt(amount);
  2548.     if (!isNaN(UID) && !isNaN(amount)) {
  2549.         POST('_/gift', {id: UID, amount: amount, response:""}, (data)=>{
  2550.             if (data.status === "ok") {
  2551.                 if (typeof cb === "function") cb(data);
  2552.             } else {
  2553.                 error(cc.red("Error sending gift: ") + cc.redBright(data.status));
  2554.             }
  2555.         });
  2556.     }
  2557. }*/
  2558.  
  2559. function updateRoomInfo(obj) {
  2560.     //{name: roomName, description: roomDesc, welcome: roomWelcome}
  2561.     POST('_/rooms/update', obj);
  2562. }
  2563.  
  2564. //http://stackoverflow.com/questions/12672193/fixed-position-command-prompt-in-node-js
  2565. function startInput() {
  2566.  
  2567.     if (STARTEDINPUT)
  2568.         return;
  2569.     STARTEDINPUT = true;
  2570.  
  2571.     rl.setPrompt(PROMPT + cc.blackBright('[chat] '), 2);
  2572.  
  2573.     rl.on('line', function (res) {
  2574.         if (res) {
  2575.             if (res.substr(0,1) === '/')
  2576.                 doCommand(res);
  2577.             else
  2578.                 sendMessage(res,0);
  2579.         }
  2580.         rl.prompt();
  2581.     });
  2582.  
  2583.     rl.on("SIGINT", function() {
  2584.         error(cc.red('Type ')+cc.redBright('/exit')+cc.red(' to safely terminate the bot.'));
  2585.         return;
  2586.     });
  2587.  
  2588.     rl.on("CLOSE", function() {
  2589.         STARTEDINPUT = false;
  2590.     });
  2591.  
  2592.     rl.prompt();
  2593. }
  2594.  
  2595. function clearWindow() { console.log('\x1B[2J\x1B[H'); }
  2596.  
  2597. function getWaitlistPos(UID, oldwl) {
  2598.     if (strIsNum(UID)) UID = parseInt(UID);
  2599.     if (room && UID) {
  2600.  
  2601.         let wl = (oldwl ? oldwl : room.getWaitlist()),
  2602.             i;
  2603.  
  2604.         if (wl.length === 0 || wl === undefined) return -1;
  2605.  
  2606.         for (i = 0; i < wl.length; i++)
  2607.             if (wl[i] === UID)
  2608.                 return i;
  2609.     }
  2610.     return -1;
  2611. }
  2612.  
  2613. function getUser(item) {
  2614.     if (typeof item === "undefined") return me;
  2615.     else if (room) {
  2616.  
  2617.         let ul = room.userlist,
  2618.             key = (typeof item === "string" ? 'username' : 'id'),
  2619.             i,
  2620.             j;
  2621.  
  2622.         if (typeof item === "string") item = ent.encode(item.toLowerCase().trim());
  2623.         for (i = 0; i < ul.length; i++) {
  2624.             j = ul[i][key];
  2625.             if (typeof j === "string") j = j.toLowerCase().trim();
  2626.             if (j === item) {
  2627.                 const user = ul[i];
  2628.                 if (typeof user.username === "string")
  2629.                     user.username = ent.decode(user.username);
  2630.                 return user;
  2631.             }
  2632.         }
  2633.     }
  2634.     return -1;
  2635. }
  2636.  
  2637. function getUserData(id, callback) {
  2638.     if (typeof id === "string" && strIsNum(id)) id = parseInt(id);
  2639.     if (typeof callback !== "function") callback = function() {};
  2640.     if (typeof id === "number") {
  2641.         GET('_/users/'+id, (data)=>{
  2642.             if (data && data.data[0]) {
  2643.                 const user = data.data[0];
  2644.                 if (typeof user.username === "string")
  2645.                     user.username = ent.decode(user.username);
  2646.                 callback(user);
  2647.                 return;
  2648.             } else {
  2649.                 callback(-1);
  2650.             }
  2651.         });
  2652.     }
  2653. }
  2654.  
  2655. function addUser(data) {
  2656.     if (!data.hasOwnProperty('id') || !data.hasOwnProperty('username')) return;
  2657.     if (room) {
  2658.         if (!~getUser(data.id)) {
  2659.             data['lastActivity'] = Date.now();
  2660.             data['isAFK'] = false;
  2661.             data['warn'] = 0;
  2662.             room.userlist.push(data);
  2663.         }
  2664.         addSeenUser(data.id);
  2665.         welcomeUser(data.id);
  2666.     }
  2667. }
  2668.  
  2669. function addSeenUser(id) {
  2670.     if (room) {
  2671.         let User = function(time) {
  2672.             this.firstSeen = time;
  2673.             this.votes = {woot:0,grab:0,meh:0};
  2674.             this.plays = 0;
  2675.             this.lastWelcome = 0;
  2676.             this.lastDisconnect = -1;
  2677.             this.activeTime = 0;
  2678.             this.lastActiveCheck = 0;
  2679.         }
  2680.         if (!seen.hasOwnProperty(room.slug)) seen[room.slug] = {};
  2681.         if (!seen[room.slug][id]) seen[room.slug][id] = new User(Date.now());
  2682.     }
  2683. }
  2684.  
  2685. function welcomeUser(id) {
  2686.     if (room && seen[room.slug][id]) {
  2687.         let lw = seen[room.slug][id].lastWelcome;
  2688.         seen[room.slug][id].lastWelcome = Date.now();
  2689.         if (BotSettings.welcomeUsers) {
  2690.             let user = getUser(id);
  2691.             if (!~user || (~user && me && me.id && user.id === me.id)) return;
  2692.             if (lw <= 0) {
  2693.                 sendMessage("/me Everybody welcome, "+user.username+"!" + (room.roulette.active ? " Roulette is open right now!" : ""),1000);
  2694.             } else {
  2695.                 sendMessage("/me Welcome back, "+user.username+"!" + (room.roulette.active ? " Roulette is open right now!" : ""),1000);
  2696.             }
  2697.         }
  2698.     }
  2699. }
  2700.  
  2701. function activeChecks() {
  2702.     if (room && seen[room.slug]) {
  2703.         let i,
  2704.             ul = room.userlist;
  2705.         for (i = 0; i < ul.length; i++) {
  2706.             activeCheck(ul[i].id);
  2707.         }
  2708.     }
  2709. }
  2710.  
  2711. function activeCheck(id) {
  2712.     if (room && seen[room.slug] && ~getUser(id)) {
  2713.         if (seen[room.slug][id]) {
  2714.             let x = seen[room.slug][id];
  2715.             if (x.lastWelcome <= 0 && x.lastActiveCheck <= 0) return;
  2716.             else if (x.lastWelcome > x.lastActiveCheck || (x.lastWelcome > 0 && x.lastActiveCheck <= 0) || (x.lastWelcome === x.lastActiveCheck && x.lastWelcome > 0 && x.lastActiveCheck > 0)) {
  2717.                 seen[room.slug][id].activeTime += (Date.now() - x.lastWelcome);
  2718.                 seen[room.slug][id].lastActiveCheck = Date.now();
  2719.             } else if (x.lastWelcome < x.lastActiveCheck && x.lastActiveCheck > 0) {
  2720.                 seen[room.slug][id].activeTime += (Date.now() - x.lastActiveCheck);
  2721.                 seen[room.slug][id].lastActiveCheck = Date.now();
  2722.             }
  2723.         }
  2724.     }
  2725. }
  2726.  
  2727. function removeUser(id) {
  2728.     if (~getUser(id) && room) {
  2729.         if (room.roulette.active) {
  2730.             room.roulette._rmUser(id, false);
  2731.         }
  2732.         let i,
  2733.             record;
  2734.  
  2735.         delete room.grabs[id];
  2736.         delete room.votes[id];
  2737.  
  2738.         for (i = 0; i < room.userlist.length; i++)
  2739.             if (room.userlist[i].id === id) {
  2740.                 if (seen[room.slug][id]) {
  2741.                     record = seen[room.slug][id];
  2742.                     seen[room.slug][id].lastDisconnect = Date.now();
  2743.                     if (record.lastWelcome > record.lastActiveCheck && record.lastWelcome > 0) { seen[room.slug][id].activeTime += (Date.now() - record.lastWelcome); seen[room.slug][id].lastActiveCheck = Date.now(); }
  2744.                     else if (record.lastActiveCheck > record.lastWelcome && record.lastActiveCheck > 0) { seen[room.slug][id].activeTime += (Date.now() - record.lastActiveCheck); seen[room.slug][id].lastActiveCheck = Date.now(); }
  2745.                 }
  2746.                 room.userlist.splice(i,1);
  2747.                 return;
  2748.             }
  2749.     }
  2750. }
  2751.  
  2752. function countVotes() {
  2753.     if (room && room.votes && room.grabs) {
  2754.         let votes = [0,0,0],
  2755.             i,
  2756.             j,
  2757.             rv = room.votes,
  2758.             rg = room.grabs;
  2759.         //woots, grabs, mehs
  2760.         for (i in rv) {
  2761.             if (rv[i] === 1)
  2762.                 votes[0]++;
  2763.             else if (rv[i] === -1)
  2764.                 votes[2]++;
  2765.         }
  2766.         for (j in rg)
  2767.             votes[1]++;
  2768.  
  2769.         return votes;
  2770.     }
  2771. }
  2772.  
  2773. function strIsNum(str) {
  2774.     if (typeof str === "number") return true;
  2775.     else if (typeof str === "string") return /^\-?[0-9]+$/.test(str);
  2776.     else return false;
  2777. }
  2778.  
  2779. function displayGuests() {
  2780.     if (room && room.meta.guests) {
  2781.         switch (room.meta.guests) {
  2782.             case 0:
  2783.                 break;
  2784.             case 1:
  2785.                 console.log('\n'+cc.blue('There is ') + cc.blueBright('1') + cc.blue(' guest online.\n'));
  2786.                 break;
  2787.             default:
  2788.                 console.log('\n'+cc.blue('There are ') + cc.blueBright(room.meta.guests) + cc.blue(' guests online.\n'));
  2789.         }
  2790.     }
  2791. }
  2792.  
  2793. function displayUsers() {
  2794.     if (room) {
  2795.         let su = room.userlist,
  2796.             users = "",
  2797.             i;
  2798.         for (i = 0; i < su.length; i++) {
  2799.             if (!su[i].guest)
  2800.                 users += colorizeName(su[i]);
  2801.             if (i < su.length - 1)
  2802.                 users += cc.blackBright(', ');
  2803.         }
  2804.         displayGuests();
  2805.         if (su.length === 1)
  2806.             console.log(cc.cyanBright('There is ' + cc.redBright('1') + ' user online: ') + users + '\n');
  2807.         else
  2808.             console.log(cc.cyanBright('There are ' + cc.redBright(su.length) + ' users online: ') + users + '\n');
  2809.     }
  2810. }
  2811.  
  2812. function displayWaitlist() {
  2813.     if (room) {
  2814.         let wl = room.getWaitlist(),
  2815.             users = "",
  2816.             i;
  2817.  
  2818.         for (i = 0; i < wl.length; i++) {
  2819.             users += colorizeName(getUser(wl[i]));
  2820.             if (i < wl.length - 1)
  2821.                 users += cc.blackBright(', ');
  2822.         }
  2823.         let status = cc.cyan("Waitlist Status: ");
  2824.  
  2825.         if (room.booth.isLocked)
  2826.             status += cc.redBright("LOCKED");
  2827.         else
  2828.             status += cc.greenBright("UNLOCKED");
  2829.  
  2830.         status += cc.blackBright(", ");
  2831.  
  2832.         if (room.booth.shouldCycle)
  2833.             status += cc.greenBright("CYCLING");
  2834.         else
  2835.             status += cc.redBright("NOT CYCLING");
  2836.  
  2837.         console.log(status);
  2838.         if (wl.length === 1)
  2839.             console.log(cc.cyanBright('There is ' + cc.redBright('1') + ' user on the waitlist: ') + users + '\n');
  2840.         else
  2841.             console.log(cc.cyanBright('There are ' + cc.redBright(wl.length) + ' users on the waitlist: ') + users + '\n');
  2842.     }
  2843. }
  2844.  
  2845. function activatePlaylist(pid) {
  2846.     let pls = room.playlists;
  2847.     let pl = getPlaylist(pid);
  2848.  
  2849.     if (!~pl) return;
  2850.  
  2851.     PUT('_/playlists/'+pid+'/activate', null, (data)=>{
  2852.         if (pl.name)
  2853.             nodeLog(cc.green('Activated playlist: ')+cc.greenBright(ent.decode(pl.name)));
  2854.         updatePlaylists();
  2855.     });
  2856. }
  2857.  
  2858. function banUser(id, reason, time, cb) {
  2859.     POST('_/bans/add', {userID:parseInt(id), reason:reason, duration:time}, (data)=>{
  2860.         if (data.status !== "ok") {
  2861.             error(cc.red("Error banning ") + cc.redBright(id) + cc.red("."));
  2862.         }
  2863.         if (typeof cb === "function") cb(data);
  2864.     });
  2865. }
  2866.  
  2867. function muteUser(id, reason, time, cb) {
  2868.     POST('_/mutes', {userID:parseInt(id), reason:reason, duration:time}, (data)=>{
  2869.         if (data.status !== "ok") {
  2870.             error(cc.red("Error muting ") + cc.redBright(id) + cc.red("."));
  2871.         }
  2872.         if (typeof cb === "function") cb(data);
  2873.     });
  2874. }
  2875.  
  2876. function kickUser(id, reason) {
  2877.     const user = getUser(id);
  2878.     if (!~user || user.role >= me.role) return;
  2879.     banUser(user.id, reason, 'h', ()=>{
  2880.         setTimeout(function() {
  2881.             unbanUser(user.id);
  2882.         }, 2500);
  2883.     });
  2884. }
  2885.  
  2886. function unbanUser(id) {
  2887.     DELETE('_/bans/'+id, (data)=>{
  2888.         if (data.status !== "ok") {
  2889.             error(cc.red("Error unbanning ") + cc.redBright(id) + cc.red("."));
  2890.         }
  2891.     });
  2892. }
  2893.  
  2894. function deleteMessage(cid) {
  2895.     DELETE('_/chat/'+cid);
  2896. }
  2897.  
  2898. function unmuteUser(id) {
  2899.     DELETE('_/mutes/'+id, (data)=>{
  2900.         if (data.status !== "ok") {
  2901.             error(cc.red("Error unmuting ") + cc.redBright(id) + cc.red("."));
  2902.         }
  2903.     });
  2904. }
  2905.  
  2906. function removeUserFromStaff(id) {
  2907.     if (id === me.id) return;
  2908.     DELETE('_/staff/'+id, (data)=>{
  2909.         if (data.status === "ok") {
  2910.             nodeLog("Successfully removed " + id + " from the staff.");
  2911.         }
  2912.     });
  2913. }
  2914.  
  2915. function removeUserFromWaitlist(id, cb) {
  2916.     DELETE('_/booth/remove/'+id, (data)=>{
  2917.         if (typeof cb === "function") {
  2918.             cb(data);
  2919.         }
  2920.     });
  2921. }
  2922.  
  2923. function leaveWaitlist() {
  2924.     DELETE('_/booth');
  2925. }
  2926.  
  2927. function joinWaitlist() {
  2928.     POST('_/booth');
  2929. }
  2930.  
  2931. function updatePlaylists() {
  2932.     GET('_/playlists', (data)=>{
  2933.         let body = data.data,
  2934.             i;
  2935.         room.playlists = body;
  2936.         for (i = 0; i < body.length; i++) {
  2937.             if (body[i].active) {
  2938.                 room.activePlaylist = body[i];
  2939.                 break;
  2940.             }
  2941.         }
  2942.     });
  2943. }
  2944.  
  2945. function logout() {
  2946.     DELETE('_/auth/session', (data)=>{
  2947.         console.log(data);
  2948.         wss.close(1000, "User logged out.");
  2949.         cleanState();
  2950.         nodeLog(cc.redBright('Logged out from plug.dj.'));
  2951.     });
  2952. }
  2953.  
  2954. function timestamp() {
  2955.     if (!BotSettings.timestampUse) return "";
  2956.     let date = new Date();
  2957.     let time = {h: date.getHours(), m: date.getMinutes(), s: date.getSeconds(), M: date.getMonth() + 1, D: date.getDate(), Y: date.getUTCFullYear(), suffix: 'a'};
  2958.  
  2959.     if (time.h >= 12) time.suffix = 'p';
  2960.  
  2961.     if (BotSettings.timestampTwelveHours && (time.h > 12 || time.h === 0))
  2962.         time.h = Math.abs(time.h - 12);
  2963.     if (time.h < 10)
  2964.         time.h = '0' + time.h;
  2965.     if (time.m < 10)
  2966.         time.m = '0' + time.m;
  2967.     if (time.s < 10)
  2968.         time.s = '0' + time.s;
  2969.  
  2970.  
  2971.     let str = time.h+':'+time.m;
  2972.     if (BotSettings.timestampSeconds) str+=':'+time.s;
  2973.     if (BotSettings.timestampTwelveHours) str+=time.suffix;
  2974.     if (BotSettings.timestampColor && cc[BotSettings.timestampColor]) str = cc[BotSettings.timestampColor](str);
  2975.  
  2976.     return str + ' ';
  2977. }
  2978.  
  2979. function colorizeName(user, doFlair, bracket) {
  2980.     if (!~getUser(user.id)) syncUsers();
  2981.     let role = parseInt(user.role);
  2982.     let gRole = parseInt(getUser(user.id).gRole);
  2983.     let name = user.username;
  2984.     if (typeof name === "undefined") return "(user data unavailable)";
  2985.  
  2986.     name = ent.decode(name);
  2987.  
  2988.     if (isNaN(role))
  2989.         role = 0;
  2990.  
  2991.     if (isNaN(gRole))
  2992.         gRole = 0;
  2993.  
  2994.     let flair = "";
  2995.  
  2996.     if (doFlair) {
  2997.  
  2998.         switch (gRole) {
  2999.             case 3:
  3000.                 flair+=cc.greenBright('$'); break;
  3001.             case 5:
  3002.                 flair+=cc.blueBright('$'); break;
  3003.             default:
  3004.                 flair+=' '; break;
  3005.         }
  3006.  
  3007.         if (user.sub)
  3008.             flair+=cc.yellowBright('•');
  3009.         else {
  3010.             if (user.silver)
  3011.                 flair+=cc.white('•');
  3012.             else
  3013.                 flair+=' ';
  3014.         };
  3015.  
  3016.         switch (role) {
  3017.             case 0:
  3018.                 flair+='     '; break;
  3019.             case 1:
  3020.                 flair+=cc.magenta('@    '); break;
  3021.             case 2:
  3022.                 flair+=cc.magenta('>    '); break;
  3023.             case 3:
  3024.                 flair+=cc.magenta('>>   '); break;
  3025.             case 4:
  3026.                 flair+=cc.magenta('>>>  '); break;
  3027.             case 5:
  3028.                 flair+=cc.magenta('>>>! '); break;
  3029.             default:
  3030.                 flair+=cc.redBright(role+'????'); break;
  3031.         }
  3032.  
  3033.         switch (user.id) {
  3034.             case 18531073:
  3035.                 flair+=cc.redBright('#! '); break;
  3036.             case 5698781:
  3037.                 flair+=cc.redBright('<3 '); break;
  3038.             case me.id:
  3039.                 flair+=cc.cyanBright('~  '); break;
  3040.             default:
  3041.                 flair+='   '; break;
  3042.         };
  3043.  
  3044.     }
  3045.  
  3046.     switch (user.id) {
  3047.         case 18531073:
  3048.             name=cc.red(name); break;
  3049.         case me.id:
  3050.             name=cc.cyan(name); break;
  3051.         default:
  3052.             switch (gRole) {
  3053.                 case 3:
  3054.                     name = cc.greenBright(name);
  3055.                     break;
  3056.                 case 5:
  3057.                     name = cc.blueBright(name);
  3058.                     break;
  3059.                 default:
  3060.                     switch (role) {
  3061.                         case 0:
  3062.                             if (user.sub)
  3063.                                 name = cc.yellow(name);
  3064.                             else {
  3065.                                 if (user.silver) name = cc.white(name);
  3066.                                 else name = cc.whiteBright(name);
  3067.                             }
  3068.                             break;
  3069.                         case 1:
  3070.                         case 2:
  3071.                         case 3:
  3072.                         case 4:
  3073.                             name=cc.magenta(name); break;
  3074.                         case 5:
  3075.                             name = cc.magentaBright(name); break;
  3076.                         default:
  3077.                             break;
  3078.                     }; break;
  3079.             }; break;
  3080.     };
  3081.  
  3082.     if (bracket) name = cc.blackBright('<') + name + cc.blackBright('> ');
  3083.     return flair + '' + name;
  3084. }
  3085.  
  3086. function syncUsers() {
  3087.     if (room) {
  3088.         GET('_/rooms/state', (data)=>{
  3089.             if (data && data.data[0] && data.data[0].users) {
  3090.                 let users = data.data[0].users,
  3091.                     i;
  3092.                 for (i = 0; i < users.length; i++) {
  3093.                     let user = getUser(users[i].id);
  3094.                     if (~user) {
  3095.                         if (user.hasOwnProperty('lastActivity'))
  3096.                             users[i]['lastActivity'] = user.lastActivity;
  3097.                         else
  3098.                             users[i]['lastActivity'] = Date.now();
  3099.  
  3100.                         if (user.hasOwnProperty('warn'))
  3101.                             users[i]['warn'] = user.warn;
  3102.                         else
  3103.                             users[i]['warn'] = 0;
  3104.  
  3105.                         if (user.hasOwnProperty('isAFK'))
  3106.                             users[i]['isAFK'] = user.isAFK;
  3107.                         else
  3108.                             users[i]['isAFK'] = false;
  3109.                     } else {
  3110.                         users[i]['lastActivity'] = Date.now();
  3111.                         users[i]['isAFK'] = false;
  3112.                         users[i]['warn'] = 0;
  3113.                     }
  3114.                     addSeenUser(users[i].id);
  3115.                 }
  3116.                 me.role = data.data[0].role;
  3117.                 users.push(me);
  3118.                 addSeenUser(me);
  3119.                 room.userlist = users;
  3120.             } else {
  3121.                 error(cc.red("Error resyncing user list."));
  3122.             }
  3123.         });
  3124.     }
  3125. }
  3126.  
  3127. function getBans(callback) {
  3128.     if (typeof callback !== "function") error(cc.red('getBans requires a function as a callback.'));
  3129.     else {
  3130.         GET('_/bans', (data)=>{
  3131.             callback(data);
  3132.         });
  3133.     }
  3134. }
  3135.  
  3136. function getAFK() {
  3137.     let afk = [],
  3138.         i;
  3139.     if (room) {
  3140.         let ul = room.userlist;
  3141.         for (i = 0; i < ul.length; i++) {
  3142.             if (ul[i].isAFK) {
  3143.                 afk.push(ul[i]);
  3144.             }
  3145.         }
  3146.     }
  3147.     return afk;
  3148. }
  3149.  
  3150. function changeDJCycle(state) {
  3151.     if (typeof state !== "boolean") return;
  3152.     PUT('_/booth/cycle', {shouldCycle: state}, (data)=>{
  3153.         if (data.status === "ok") {
  3154.             nodeLog(cc.green('Turned DJ cycle ')+(state ? cc.greenBright('on') : cc.redBright('off'))+'.');
  3155.         }
  3156.     });
  3157. }
  3158.  
  3159. function getPlaylist(pid) {
  3160.     if (room && !isNaN(parseInt(pid)) && strIsNum(pid)) {
  3161.         pid = parseInt(pid);
  3162.         let playlists = room.playlists,
  3163.             i;
  3164.         for (i = 0; i < playlists.length; i++) {
  3165.             if (playlists[i].id === pid) {
  3166.                 return playlists[i];
  3167.             }
  3168.         }
  3169.     }
  3170.     return -1;
  3171. }
  3172.  
  3173. function sendFriendRequest(id) {
  3174.  
  3175.     if (strIsNum(id)) {
  3176.         id = parseInt(id);
  3177.         POST('_/friends', {"id": id}, (b)=>{
  3178.             if (b.status === "invalidUserID") {
  3179.                 error(cc.red("sendFriendRequest: Invalid user ID."));
  3180.             } else if (b.status === "cannotFriendSelf") {
  3181.                 error(cc.red("sendFriendRequest: Cannot add yourself as a friend."));
  3182.             } else if (b.status === "ok") {
  3183.                 log(LOGTYPES.FRIEND+' '.repeat(HIGHWAY-9)+cc.yellowBright("Sent a friend request to ID ")+cc.magentaBright(id));
  3184.             } else if (b.status === "accept") {
  3185.                 log(LOGTYPES.FRIEND+' '.repeat(HIGHWAY-9)+cc.yellowBright("Accepted friend request from ID ")+cc.magentaBright(id));
  3186.             } else {
  3187.                 warn(cc.yellow("sendFriendRequest unknown status: ")+cc.yellowBright(b.status));
  3188.             }
  3189.         });
  3190.     } else {
  3191.         error(cc.red("sendFriendRequest: ID must be a number."));
  3192.     }
  3193. }
  3194.  
  3195. function getMutes(callback) {
  3196.     if (typeof callback !== "function") error(cc.red('getMutes requires a function as a callback.'));
  3197.     else {
  3198.         GET('_/rooms/state', (data)=>{
  3199.            callback(data.data[0].mutes);
  3200.         });
  3201.     }
  3202. }
  3203.  
  3204. function gRoleToString(role) {
  3205.     role = parseInt(role);
  3206.     let str = "";
  3207.     switch (role) {
  3208.         case 0:
  3209.             str = "User";
  3210.             break;
  3211.         case 3:
  3212.             str = "Brand Ambassador";
  3213.             break;
  3214.         case 5:
  3215.             str = "plug.dj Admin";
  3216.             break;
  3217.         default:
  3218.             str = "{unknown gRole: "+role+"}";
  3219.             break;
  3220.     }
  3221.     return str;
  3222. }
  3223.  
  3224. function roleToString(role) {
  3225.     role = parseInt(role);
  3226.     let str = "";
  3227.     switch (role) {
  3228.         case 0:
  3229.             str = "User";
  3230.             break;
  3231.         case 1:
  3232.             str = "Resident DJ";
  3233.             break;
  3234.         case 2:
  3235.             str = "Bouncer";
  3236.             break;
  3237.         case 3:
  3238.             str = "Manager";
  3239.             break;
  3240.         case 4:
  3241.             str = "Co-Host";
  3242.             break;
  3243.         case 5:
  3244.             str = "Host";
  3245.             break;
  3246.         case 6:
  3247.             str = "Brand Ambassador";
  3248.             break;
  3249.         case 7:
  3250.             str = "plug.dj Admin";
  3251.             break;
  3252.         default:
  3253.             str = "{unknown role: "+role+"}";
  3254.             break;
  3255.     }
  3256.     return str;
  3257. }
  3258.  
  3259. //do something when app is closing
  3260. process.on('exit', function(num) {
  3261.     activeChecks();
  3262.     if (seen && seen !== {}) fs.writeFileSync('data/seenUsers.json', JSON.stringify(seen));
  3263.     if (wss) wss.close(1000, 'exiting');
  3264.     process.exit(num);
  3265. });
  3266.  
  3267. //catches ctrl+c event
  3268. process.on('SIGINT', function() {process.exit(0)});
  3269.  
  3270. //catches uncaught exceptions
  3271. process.on('uncaughtException', function(err) {
  3272.     let out = err.message;
  3273.     error(cc.red(err));
  3274.     if (err.stack) {
  3275.         error(cc.red(err.stack));
  3276.         out += '\n' + err.stack;
  3277.     }
  3278.     fs.writeFileSync('errors/error_' + Date.now() + '.txt', out);
  3279.     process.exit(1);
  3280. });
  3281.  
  3282. function addHistoryItem(historyID, format, cid, timestamp) {
  3283.     if (!historyID || !format || !cid || !timestamp || !room) return;
  3284.     else {
  3285.         let item = {
  3286.             'historyID': historyID,
  3287.             'format': format,
  3288.             'cid': cid,
  3289.             'timestamp': timestamp
  3290.         };
  3291.         if (room.history.length >= 50) {
  3292.             room.history = room.history.slice(0,49);
  3293.         }
  3294.         room.history = [item].concat(room.history);
  3295.     }
  3296. }
  3297.  
  3298. function Room(slug) { this.slug = slug; }
  3299.  
  3300. Room.prototype.booth = {};
  3301. Room.prototype.meta = {};
  3302. Room.prototype.playback = {m:{},d:[]};
  3303. Room.prototype.userlist = [];
  3304. Room.prototype.votes = {};
  3305. Room.prototype.grabs = {};
  3306. Room.prototype.playlists = [];
  3307. Room.prototype.activePlaylist = {};
  3308. Room.prototype.history = [];
  3309. Room.prototype.roulette = {
  3310.     active: false,
  3311.     users: [],
  3312.     timer: null,
  3313.     previous: 0,
  3314.     '_start': function() {
  3315.  
  3316.         if (this.active) {
  3317.             error(cc.red('Roulette tried to test start, but it is already active.'));
  3318.             return;
  3319.         }
  3320.  
  3321.         clearTimeout(this.timer);
  3322.         this.users = [];
  3323.         this.active = true;
  3324.         sendMessage('/me ** ROULETTE HAS BEGUN! ** type ' + TRIGGER + 'join to enter, or ' + TRIGGER + 'leave to back out! Ends in 1 minute.');
  3325.         this.timer = setTimeout(function() {
  3326.             room.roulette._end(false);
  3327.         }, 60000);
  3328.  
  3329.     },
  3330.     '_end': function(isAbrupt) {
  3331.  
  3332.         if (!this.active) {
  3333.             error(cc.red('Roulette tried to end, but it is already inactive.'));
  3334.             return;
  3335.         }
  3336.  
  3337.         this.previous = Date.now();
  3338.         let users = this.users;
  3339.         let msg = "";
  3340.         let winner = -1;
  3341.         const bump = function(UID) {
  3342.             addUserToWaitlist(UID, function() {
  3343.                 moveDJ(UID, 0);
  3344.             });
  3345.         };
  3346.         this._cleanup();
  3347.  
  3348.         if (isAbrupt)
  3349.             msg = "Roulette ended abruptly. ";
  3350.  
  3351.         if (users.length <= 0) {
  3352.             msg += "Nobody joined the roulette, nobody won...";
  3353.         } else if (users.length === 1) {
  3354.             winner = getUser(users[0]);
  3355.             if (~winner) {
  3356.                 bump(winner.id);
  3357.                 msg += "Only one user joined the roulette. @" + winner.username + " has been bumped to 1 in the waitlist.";
  3358.             } else {
  3359.                 msg += "Only one user joined the roulette. #" + winner.id + " was not found in the room, though.";
  3360.             }
  3361.         } else if (users.length > 1) {
  3362.             winner = getUser(users[Math.floor(Math.random() * users.length)]);
  3363.             if (~winner) {
  3364.                 bump(winner.id);
  3365.                 msg += "Roulette winner chosen! @" + winner.username + " has been bumped to 1 in the waitlist.";
  3366.             } else {
  3367.                 msg += "Roulette winner chosen! #" + winner.id + " was not found in the room, though.";
  3368.             }
  3369.         }
  3370.         sendMessage(msg);
  3371.     startTimer("roulette");
  3372.     },
  3373.     '_cleanup': function() {
  3374.  
  3375.         this.active = false;
  3376.         this.users = [];
  3377.         clearTimeout(this.timer);
  3378.  
  3379.     },
  3380.     '_validateID': function(data) {
  3381.  
  3382.         if ((typeof data === "string" && strIsNum(data)) || typeof data === "number") {
  3383.             return parseInt(data);
  3384.         } else if (typeof data === "object" && data.hasOwnProperty('id')) {
  3385.             return parseInt(data.id);
  3386.         } else {
  3387.             return -1;
  3388.         }
  3389.  
  3390.     },
  3391.     '_addUser': function(data) {
  3392.  
  3393.         let id = this._validateID(data);
  3394.         let user = getUser(id);
  3395.  
  3396.         if (id < 0 || !~user || isNaN(id)) return;
  3397.         else {
  3398.             if (!~arrFind(this.users, id)) {
  3399.                 if (room.booth.currentDJ === id) {
  3400.                     sendMessage('@' + user.username + ": The current DJ cannot participate in the roulette.");
  3401.                 } else {
  3402.                     this.users.push(id);
  3403.                     sendMessage('@' + user.username + " joined the roulette!");
  3404.                 }
  3405.             }
  3406.         }
  3407.     },
  3408.     '_rmUser': function(data, leave) { // id OR {id: userID}
  3409.  
  3410.         let id = this._validateID(data),
  3411.             i;
  3412.  
  3413.         if (id < 0 || this.users.length <= 0 || isNaN(id)) {
  3414.             return;
  3415.         } else {
  3416.             for (i = 0; i < this.users.length; i++) {
  3417.                 if (this.users[i] === id) {
  3418.                     this.users.splice(i,1);
  3419.                     if (leave) {
  3420.                         let user = getUser(id);
  3421.                         if (~user) {
  3422.                             sendMessage('@' + user.username + " left the roulette.");
  3423.                         }
  3424.                     }
  3425.                     return;
  3426.                 }
  3427.             }
  3428.         }
  3429.     }
  3430. };
  3431.  
  3432. Room.prototype.getWaitlist = function() { return this.playback.d; }
  3433. Room.prototype.getMedia = function() { if (this.playback.m) return this.playback.m; else return {}; }
  3434. /* unused
  3435. Room.prototype.getWootCount = function() { let j = 0; for (let i in this.votes) {if (this.votes[i] === 1) j++;} return j; }
  3436. Room.prototype.getMehCount = function() { let j = 0; for (let i in this.votes) {if (this.votes[i] === -1) j++;} return j; }*/
  3437.  
  3438. Room.prototype.setWaitlist = function(data) { this.playback.d = data; }
  3439. Room.prototype.setPlaybackFromState = function(data) {
  3440.     this.playback = {
  3441.         c:data.booth.currentDJ,
  3442.         d:data.booth.waitingDJs,
  3443.         h:data.playback.historyID,
  3444.         m:(data.playback.media ? data.playback.media : {}),
  3445.         p:data.playback.playlistID,
  3446.         t:data.playback.startTime
  3447.     };
  3448. }
  3449. Room.prototype.woot = function() { if (room.votes[me.id] === 1) return; if (this.playback['h']) {POST('_/votes', {direction: 1, historyID: this.playback.h}); nodeLog(cc.green('woot sent'));} else { warn(cc.yellow('woot not sent; is there a song playing?')); } }
  3450. Room.prototype.meh = function() { if (room.votes[me.id] === -1) return; if (this.playback['h']) {POST('_/votes', {direction:-1, historyID: this.playback.h}); nodeLog(cc.green('meh sent'));} else { warn(cc.yellow('meh not sent; is there a song playing?')); } }
  3451. Room.prototype.grab = function() { if (room.grabs[me.id] === 1) return; if (this.activePlaylist && this.activePlaylist.id > 0 && this.playback['h']) {POST('_/grabs', {playlistID: this.activePlaylist.id, historyID: this.playback['h']}, ()=>{nodeLog(cc.green('grabbed song'));})}}
  3452.  
  3453. function doChatCommand(data, user) {
  3454.     if (BotSettings.allowChatCommands && data['message'] && ~user) {
  3455.         if (typeof user.username === "string")
  3456.             user.username = ent.decode(user.username);
  3457.  
  3458.         let splitMessage = data.message.trim().split(' ');
  3459.         let cmdname = splitMessage[0].substr(1).toLowerCase();
  3460.         if (BotSettings.messageCommands && BotSettings.useMessageCommands && BotSettings.messageCommands[cmdname] && typeof BotSettings.messageCommands[cmdname] === "string") {sendMessage(BotSettings.messageCommands[cmdname], 500); return;}
  3461.         if (!user.hasOwnProperty('role')) return;
  3462.         var role = user.role;
  3463.         if (user.hasOwnProperty('gRole')) {
  3464.             if (user.gRole === 3) role = 6;
  3465.             else if (user.gRole === 5) role = 7;
  3466.         }
  3467.         const simpleGetName = function() {
  3468.             const pos = data.message.indexOf('@');
  3469.             let toUser = "";
  3470.             if (!~pos) {
  3471.                 if (splitMessage.length === 1)
  3472.                     toUser = me.username;
  3473.             } else {
  3474.                 toUser = data.message.substr(pos+1).trim();
  3475.             }
  3476.             return toUser;
  3477.         };
  3478.         /*always send role as first argument*/
  3479.         const cmds = {
  3480.             'help':()=>{
  3481.                 let cmd = "";
  3482.                 if (!splitMessage[1]) {
  3483.                     sendMessage('/me [@' + user.username + '] ' + TRIGGER + 'help <command name> :: Get more information on how to use a command! Type ' + TRIGGER + 'commands for a list!');
  3484.                 }
  3485.                 else cmd = splitMessage[1].toLowerCase().trim();
  3486.                 if (cmd && commands.hasOwnProperty(cmd)) {
  3487.                     let help = commands[cmd].getHelp();
  3488.                     if (help === "") return;
  3489.                     else {
  3490.                         if (!commands[cmd].state) help += " Inactive.";
  3491.                         sendMessage('/me [@' + user.username + '] ' + help);
  3492.                     }
  3493.                 }
  3494.             },
  3495.  
  3496.             'about':()=>commands.about.exec(role),
  3497.             'dc':()=>commands.dc.exec(role, user.username, user.id),
  3498.             'roll':()=>commands.roll.exec(role, data, splitMessage),
  3499.             'trigger':()=>commands.trigger.exec(role),
  3500.             'anagram':()=>commands.anagram.exec(role, data.message, user),
  3501.             'commands':()=>commands.commands.exec(role),
  3502.             'gif':()=>{
  3503.                 if (splitMessage.length > 1) {
  3504.                     splitMessage.splice(0,1);
  3505.                     commands.gif.exec(role, user.username, splitMessage);
  3506.                 }
  3507.             },
  3508.             'afktime':()=>{
  3509.                 let id = user.id;
  3510.                 let name = '@'+user.username;
  3511.                 let afk = {lastActivity: user.lastActivity, isAFK: user.isAFK};
  3512.                 if (splitMessage.length > 1) {
  3513.                     if (splitMessage[1].substr(0,1) === "#") {
  3514.                         id = parseInt(splitMessage[1].substr(1).trim());
  3515.                         let user = getUser(id);
  3516.                         if (~user) {
  3517.                             name = '@'+user.username;
  3518.                             afk = {lastActivity: user.lastActivity, isAFK: user.isAFK};
  3519.                         } else {
  3520.                             name = 'uid:'+id;
  3521.                             afk = -1;
  3522.                         }
  3523.                     }
  3524.                     else if (splitMessage[1].substr(0,1) === '@') {
  3525.                         let sub = data.message.substr(data.message.indexOf('@')+1);
  3526.                         let user = getUser(sub);
  3527.                         if (~user) {
  3528.                             id = user.id;
  3529.                             name = '@'+user.username;
  3530.                             afk = {lastActivity: user.lastActivity, isAFK: user.isAFK};
  3531.                         } else {
  3532.                             id = -1;
  3533.                             name = '@'+sub;
  3534.                             afk = -1;
  3535.                         }
  3536.                     }
  3537.                 }
  3538.                 if (cmdname === "afktime")
  3539.                     commands["afktime"].exec(role, name, afk);
  3540.                 else if (~['jointime','seentime','stats'].indexOf(cmdname))
  3541.                     commands[cmdname].exec(role, name, id);
  3542.             },
  3543.             '8ball':()=>{if (splitMessage.length > 1) commands['8ball'].exec(role, user.username, data.message.substr(7));},
  3544.             'uptime':()=>commands.uptime.exec(role),
  3545.             'link':()=>commands.link.exec(role),
  3546.             'skipreasons':()=>commands.skipreasons.exec(role, user.username),
  3547.             'skip':()=>{
  3548.                 if (splitMessage.length > 1) commands.skip.exec(role, user.username, splitMessage[1].toLowerCase());
  3549.                 else commands.skip.exec(role, user.username, "none");
  3550.             },
  3551.             'blacklist':()=>{
  3552.                 let listName = splitMessage[1],
  3553.                     action = splitMessage[2],
  3554.                     format = splitMessage[3],
  3555.                     mid = splitMessage[4];
  3556.  
  3557.                 if (BotSettings.allowRemoteBlacklistEdit && listName && blacklists.hasOwnProperty(listName)) {
  3558.  
  3559.                     if (action && ~arrFind(["add", "remove", "rem"], action.toLowerCase())) {
  3560.  
  3561.                         action = action.toLowerCase();
  3562.  
  3563.                         if (format !== undefined && ~arrFind(["youtube","yt","soundcloud","sc","1","2"], format)) {
  3564.  
  3565.                             if (~arrFind(["youtube","yt","1"], format)) format = 1;
  3566.                             else if (~arrFind(["soundcloud","sc","2"], format)) format = 2;
  3567.  
  3568.                             if (mid !== undefined) {
  3569.                                 commands.blacklist.exec(role, user.username, listName, action, format, mid);
  3570.                             }
  3571.  
  3572.                         }
  3573.  
  3574.                     }
  3575.  
  3576.                 }
  3577.             },
  3578.             'blacklists':()=>commands.blacklists.exec(role, user.username),
  3579.             'shots':()=>{
  3580.                 const toUser = simpleGetName();
  3581.                 commands.shots.exec(role, user.username, toUser, cmdname);
  3582.             },
  3583.             'props':()=>{
  3584.                 commands.props.exec(role, user.username, false);
  3585.             },
  3586.             'propscunt':()=>{
  3587.                 commands.props.exec(role, user.username, true);
  3588.             },
  3589.             'candy':()=>{
  3590.                 const toUser = simpleGetName();
  3591.                 commands.candy.exec(role, user.username, toUser);
  3592.             },
  3593.             'cookie':()=>{
  3594.                 const toUser = simpleGetName();
  3595.                 commands.cookie.exec(role, user.username, toUser);
  3596.             },
  3597.             'weather':()=>{
  3598.                 const toUser = simpleGetName();
  3599.                 commands.weather.exec(role, user.username,toUser);
  3600.             },
  3601.             'startroulette':()=>{
  3602.                 if (role >= 3) {
  3603.                     room.roulette._start();
  3604.                 }
  3605.             },
  3606.             'endroulette':()=>{
  3607.                 if (role >= 3) {
  3608.                     room.roulette._end(true);
  3609.                 }
  3610.             },
  3611.             'set':()=>{
  3612.                 if (splitMessage.length === 2) {
  3613.                     commands.set.exec(role, user.username, splitMessage[1], null);
  3614.                 } else if (splitMessage.length >= 3) {
  3615.                     if (splitMessage[1].toLowerCase() === 'motd') {
  3616.                         commands.set.exec(role, user.username, splitMessage[1], data.message.substr((TRIGGER + "set motd ").length).trim());
  3617.                     } else {
  3618.                         commands.set.exec(role, user.username, splitMessage[1], splitMessage[2].toLowerCase());
  3619.                     }
  3620.                 } else return;
  3621.             },
  3622.            // 'english':()=>{
  3623.          //       const toUser = getUser(simpleGetName());
  3624.           //      if (!~toUser || (~toUser && toUser.id === user.id)) return;
  3625.           //      commands.english.exec(role, toUser.id);
  3626.          //   },
  3627.             'swap':()=>{
  3628.                 commands.swap.exec(role, data.message);
  3629.             },
  3630.             'enable':()=>{
  3631.                 if (splitMessage[1])
  3632.                     commands.enable.exec(role, splitMessage[1].toLowerCase());
  3633.             },
  3634.             'disable':()=>{
  3635.                 if (splitMessage[1])
  3636.                     commands.disable.exec(role, splitMessage[1].toLowerCase());
  3637.             },
  3638.             'dclookup':()=>{
  3639.                 let targetUser;
  3640.                 if (splitMessage.length === 1)
  3641.                     targetUser = user.username;
  3642.                 else if (strIsNum(splitMessage[1]))
  3643.                     targetUser = parseInt(splitMessage[1]);
  3644.                 else if (~data.message.indexOf('@'))
  3645.                     targetUser = data.message.substr(data.message.indexOf('@')+1).trim();
  3646.                 else
  3647.                     return;
  3648.                 commands.dclookup.exec(role, user.username, targetUser);
  3649.             },
  3650.             'kick':()=>{
  3651.                 const at = data.message.indexOf('@');
  3652.                 if (!~at) return;
  3653.                 commands.kick.exec(role, user.username, data.message.substr(at+1).trim());
  3654.             }
  3655.         };
  3656.         cmds['jointime'] = cmds.afktime;
  3657.         cmds['seentime'] = cmds.afktime;
  3658.         cmds['stats'] = cmds.afktime;
  3659.         cmds['shot'] = cmds.shots;
  3660.         cmds['k1tt'] = cmds.shots;
  3661.         return (cmds[cmdname] || function() {})();
  3662.     }
  3663. }
  3664.  
  3665. function Command(state,minRank,help,fn,cooldown) {
  3666.     //commands['command name'] = new Command(true, 0, "", ()=>{}, 1000);
  3667.     //cooldown is optional and measured in milliseconds, defaults to 1 second
  3668.  
  3669.     if (typeof state !== "boolean")
  3670.         this.state = false;
  3671.     else
  3672.         this.state = state;
  3673.  
  3674.     if (isNaN(parseInt(minRank)) || parseInt(minRank) < 0)
  3675.         this.minRank = -1;
  3676.     else
  3677.         this.minRank = minRank;
  3678.  
  3679.     if (typeof fn !== "function")
  3680.         this.fn = ()=>{error(cc.red('command has invalid fn'))};
  3681.     else
  3682.         this.fn = fn;
  3683.  
  3684.     if (typeof help !== "string")
  3685.         this.help = "";
  3686.     else
  3687.         this.help = help;
  3688.  
  3689.     if (typeof cooldown !== "number" || !cooldown)
  3690.         this.cooldown = 1000;
  3691.     else
  3692.         this.cooldown = cooldown;
  3693. }
  3694.  
  3695. Command.prototype.lastExec = 0;
  3696. Command.prototype.lastHelp = 0;
  3697. Command.prototype.exec = function(role) {if (this.state && Date.now() - this.lastExec >= this.cooldown && role >= this.minRank && this.minRank >= 0) {this.lastExec = Date.now(); this.fn.apply(null, arguments);}}
  3698. Command.prototype.getHelp = function() {if (Date.now() - this.lastHelp >= 1000) {this.lastHelp = Date.now(); return this.help;} else {return "";}}
  3699.  
  3700. /* RANK VALUES
  3701. *  0 user
  3702. *  1 resident dj
  3703. *  2 bouncer
  3704. *  3 manager
  3705. *  4 co-host
  3706. *  5 host
  3707. *  6 brand ambassador
  3708. *  7 plug.dj admin
  3709. */
  3710.  
  3711. /*commands['command name here'] = new Command(active upon start?, minimum rank to use inclusive (see above comment for list), help string (if TRIGGER+"help" is received), functionality)*/
  3712.  
  3713. /*
  3714.     DON'T FORGET to define your command up at doChatCommand in order for it to work
  3715. */
  3716.  
  3717. commands['8ball'] = new Command(true,0,"8ball <any text> :: Asks the Magic 8 Ball a question. Any rank.",function() {
  3718.     if (arguments.length !== 3) return;
  3719.     if (BotSettings.eightBallChoices.length > 0) {
  3720.         const q = (arguments[2].length > 175 ? arguments[2].substr(0,175) : arguments[2]);
  3721.         const ans = BotSettings.eightBallChoices;
  3722.         let sndmsg = "[@"+arguments[1]+": " + q + "] ";
  3723.         sndmsg += ans[Math.floor(Math.random() * ans.length)];
  3724.         sendMessage(sndmsg);
  3725.     }
  3726. });
  3727.  
  3728. commands['about'] = new Command(true,0,"about :: Displays bot's \"about\" message. Any rank.",function() {
  3729.     if (arguments.length !== 1) return;
  3730.     sendMessage("about :: " + TITLE + " v" + VER + " :: written by zeratul- :: https://github.com/zeratul0/aiurbot");
  3731. });
  3732.  
  3733. commands['afktime'] = new Command(true,0,"afktime [@username|#userID] :: Returns the amount of time a user has been inactive. Gets your own info if no valid argument. Any rank.",function() {
  3734.     if (arguments.length !== 3) return;
  3735.     let sndmsg = "";
  3736.     let afk = arguments[2];
  3737.     if (!~afk) {
  3738.         sndmsg = arguments[1] + " was not found in the room.";
  3739.     } else {
  3740.         if (afk.isAFK)
  3741.             sndmsg = arguments[1] + " has been inactive for " + secsToLabelTime(Date.now() - afk.lastActivity, true) + ".";
  3742.         else
  3743.             sndmsg = arguments[1] + " is currently active.";
  3744.     }
  3745.     sendMessage(sndmsg);
  3746. });
  3747.  
  3748. commands['anagram'] = new Command(true,0,"anagram <7-30 character string> :: Returns an anagram of the given word(s), retrieved from www.anagramgenius.com. Any rank.",function(){
  3749.     if (arguments.length !== 3) return;
  3750.     let msg = arguments[1];
  3751.     let user = arguments[2];
  3752.     let query = msg.slice(9,msg.length);
  3753.     let uriquery = encodeURI(encodeURI(query));
  3754.     let sndmsg = "[@"+user.username+'] ';
  3755.     if (query.length < 7 || query.length > 30) {
  3756.         sndmsg += "Invalid input. Use 7-30 characters.";
  3757.         sendMessage(sndmsg.trim());
  3758.     } else {
  3759.         req.get("https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D'http%3A%2F%2Fwww.anagramgenius.com%2Fserver.php%3Fsource_text%3D"+uriquery+"%26vulgar%3D1'&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys", {json: true}, function(e,r,b) {
  3760.             if (e) error(cc.red(e));
  3761.             else if (b && b.query.results) {
  3762.                 let result = b.query.results.body.table[1].tbody.tr[2].td.h3.span[1].content;
  3763.                 if (result === undefined || result === '') return;
  3764.                 else result = result.slice(1,-1);
  3765.                 sndmsg += '<'+query+'> ==> '+result;
  3766.                 sendMessage(sndmsg.trim());
  3767.             }
  3768.         });
  3769.     }
  3770. });
  3771.  
  3772. commands['blacklist'] = new Command(true,3,"blacklist <blacklist name> <add // remove|rem> <youtube|yt|1 // soundcloud|sc|2> <video ID // track ID> :: Adds or removes songs to/from a given blacklist. Requires Manager+.",function() {
  3773.     if (!BotSettings.allowRemoteBlacklistEdit) return;
  3774.     const username = arguments[1],
  3775.         listName = arguments[2],
  3776.         action = arguments[3],
  3777.         format = arguments[4],
  3778.         mid = arguments[5];
  3779.     let SAVE = false;
  3780.  
  3781.     if (Object.prototype.toString.apply(blacklists[listName][1]) === "[object Array]") {
  3782.         let formatString = format + ":" + mid,
  3783.             message = "";
  3784.         if (action === "add") {
  3785.             if (~arrFind(blacklists[listName][1], formatString)) {
  3786.                 message = "/me @" + username + ", that item was already in the given blacklist.";
  3787.             } else {
  3788.                  return isUnavailable(parseInt(format), mid, (state)=> {
  3789.                     if (state === -1) {
  3790.                         error(cc.red("Error checking availability of song."));
  3791.                     } else if (state === 1) {
  3792.                         message = "/me @" + username +", that song is invalid or unavailable.";
  3793.                         sendMessage(message);
  3794.                     } else if (state === 0) {
  3795.                         blacklists[listName][1].push(formatString);
  3796.                         message = "/me @" + username + ": Successfully added " + formatString + " to the blacklist, " + listName + ".";
  3797.                         saveBlacklist(listName);
  3798.                         sendMessage(message);
  3799.                     } else {
  3800.                         error(cc.red("Unknown code when checking availability: " + cc.redBright(state)));
  3801.                     }
  3802.                 });
  3803.             }
  3804.         } else if (action === "remove" || action === "rem") {
  3805.             let i;
  3806.             message = "/me @" + username + ", that item was not found in the given blacklist.";
  3807.             for (i = 0; i < blacklists[listName][1].length; i++) {
  3808.                 if (blacklists[listName][1][i] === formatString) {
  3809.                     message = "/me @" + username + ": Successfully removed " + formatString + " from the blacklist, " + listName + ".";
  3810.                     SAVE = true;
  3811.                     blacklists[listName][1].splice(i,1);
  3812.                     break;
  3813.                 }
  3814.             }
  3815.  
  3816.         }
  3817.         if (SAVE)
  3818.             saveBlacklist(listName);
  3819.             saveBlacklist(listName);
  3820.         if (message !== "")
  3821.             sendMessage(message);
  3822.     }
  3823. });
  3824.  
  3825.  
  3826. commands['blacklists'] = new Command(true,0,"blacklists :: Returns list of valid blacklist names to be used with the blacklist command. Any rank.",function() {
  3827.     let i,
  3828.         bl = [],
  3829.         username = arguments[1],
  3830.         message = "";
  3831.     for (i in blacklists) {
  3832.         bl.push(i);
  3833.     }
  3834.     message = "/me [@" + username + "] Blacklists: " + bl.join(', ');
  3835.     if (message !== "")
  3836.         sendMessage(message);
  3837. });
  3838.  
  3839.  
  3840. commands['commands'] = new Command(true,0,"commands :: Returns the link containing chat commands and lists any inactive ones. Any rank.",function() {
  3841.     let sndmsg = [],
  3842.         i;
  3843.     for (i in commands) {
  3844.         if (!commands[i].state) {
  3845.             sndmsg.push(i);
  3846.         }
  3847.     }
  3848.  
  3849.     if (sndmsg === []) sndmsg = "!none!";
  3850.     else sndmsg = sndmsg.join(',');
  3851.  
  3852.     sendMessage('Default command list: https://git.io/vDEhZ (inactive: ' + sndmsg + ')');
  3853. });
  3854.  
  3855. commands['dc'] = new Command(true,0,"dc :: Places you back into the waitlist at your old position ONLY IF you were disconnected while waiting. Any rank. Must be " + secsToLabelTime(MAX_DISC_TIME, true) + " since disconnecting.",function() {
  3856.     cleanDC();
  3857.     let username = arguments[1],
  3858.         id = parseInt(arguments[2]),
  3859.         time = Date.now(),
  3860.         record = -1,
  3861.         pos = -1,
  3862.         lastPos = -1;
  3863.  
  3864.     if (!isNaN(id)) {
  3865.         record = getUserRecord(id);
  3866.         pos = getWaitlistPos(id);
  3867.         lastPos = getUserDC(id);
  3868.     }
  3869.  
  3870.     if (typeof id === "undefined" || typeof username === "undefined") return;
  3871.     if (~record) {
  3872.         if (typeof MAX_DISC_TIME === "number" && MAX_DISC_TIME > 1000) {
  3873.             let dur = (time - record.lastDisconnect);
  3874.             if (record.lastDisconnect <= 0) return;
  3875.             else if (lastPos >= 0 && (!~pos || pos > lastPos)) {
  3876.                 if (dur > MAX_DISC_TIME)
  3877.                     sendMessage("[@" + username + "] You disconnected too long ago. Last disconnect: " + secsToLabelTime(time - record.lastDisconnect, true) + " ago.");
  3878.                 else if ((time - record.lastDisconnect) <= MAX_DISC_TIME) {
  3879.                     sendMessage("[@" + username + "] Disconnected " + secsToLabelTime(time - record.lastDisconnect, true) + " ago, previously at position " + (lastPos+1) + ". Bumped in the waitlist.");
  3880.                     addUserToWaitlist(id, function() {
  3881.                         moveDJ(id, lastPos);
  3882.                         remUserDC(id);
  3883.                     });
  3884.                 }
  3885.             } else if (!~lastPos) {
  3886.                 sendMessage("[@" + username + "] Could not find a previous waitlist position for you. If you did disconnect, the waitlist may have fully cycled while you were gone.");
  3887.             }
  3888.         }
  3889.     } else {
  3890.         error(cc.red(TRIGGER + "dc :: Could not find user's seendata. username: " + username + ", id: " + id + ", room.slug: " + room.slug));
  3891.     }
  3892. });
  3893.  
  3894. commands['dclookup'] = new Command(true,0,"dclookup [@username|userID] :: Returns a user's last disconnect time and position. Use their ID if they are not present in the room. Any rank.", function() {
  3895.     const user = ((typeof arguments[2] === "number") ? {id: arguments[2]} : getUser(arguments[2]));
  3896.     if (!~user) return sendMessage("/me [@" + arguments[1] + "] That user was not found in the room. Try using an ID.");
  3897.     else if (disconnects[user.id]) {
  3898.         return sendMessage("/me [@" + arguments[1] + "] That user disconnected " + secsToLabelTime(Date.now() - disconnects[user.id][1], true) + " ago at position " + disconnects[user.id][0] + ".");
  3899.     } else {
  3900.         return sendMessage("/me [@" + arguments[1] + "] Could not find a previous waitlist position for that user.");
  3901.     }
  3902. });
  3903.  
  3904. commands['candy'] = new Command(true,0,"candy <@username> :: Give someone a random tasty candy!",function(){
  3905.     if (arguments[2].toLowerCase() === me.username.toLowerCase()) {
  3906.         sendMessage("/me eats a candy.");
  3907.     } else if (arguments[1].toLowerCase() === arguments[2].toLowerCase()) {
  3908.         sendMessage("/me @" + arguments[1] + ", don't be so greedy. Share the candy with somebody!");
  3909.     } else if (arguments[2] !== "") {
  3910.  
  3911.         const toUser = getUser(arguments[2]);
  3912.         if (!~toUser) return;
  3913.         //candy list from ureadmyname's basicBot fork
  3914.         const candies = [
  3915.             "a neapolitan ice cream cone", "a strawberry ice cream cone", "a packet of Hershey's caramels",
  3916.             "a chocolate ice cream cone", "a vanilla ice cream cone", "a Hershey's almond toffee bar",
  3917.             "a packet of Sour Patch Kids", "a Reeses Peanut Butter Cup", "a packet of Jelly Bellys",
  3918.             "a Lindt chocolate bar", "a box of Milk Duds", "a Dove chocolate bar", "a box of Tim Tams",
  3919.             "a pack of Starburst", "a Butterfinger bar", "a box of Raisinets", "a Sour Punch Straw",
  3920.             "an Andes thin mint", "a Charleston Chew", "a bag of M&M's", "an Almond Joy bar",
  3921.             "a NutRageous bar", "a Watermelon Jolly Rancher", "an Apple Jolly Rancher",
  3922.             "a Grape Jolly Rancher", "a Cherry Jolly Rancher", "a Blue Raspberry Jolly Rancher",
  3923.             "a Kinder Bueno", "a MilkyWay bar", "a Snickers bar", "a Tootsie Roll", "a Chokito Bar",
  3924.             "a gum wrapper...", "a Bounty bar", "a Mounds bar", "a Mr. Goodbar", "a PayDay bar",
  3925.             "a Baby Ruth", "a Heath bar", "a Toblerone", "a Wonka Bar", "an Aero Bar", "a Mars Bar",
  3926.             "a Rolo Bar", "a Twix Bar", "a Twizzler", "a KitKat"
  3927.         ];
  3928.  
  3929.         sendMessage("/me @" + arguments[1] + " gave @" + toUser.username + " " + candies[Math.floor(Math.random() * candies.length)] + "!");
  3930.     }
  3931. });
  3932.  
  3933. commands['cookie'] = new Command(true,0,"cookie <@username> :: Give someone a cookie!",function(){
  3934.     if (arguments[2].toLowerCase() === me.username.toLowerCase()) {
  3935.         sendMessage("/me eats a cookie.");
  3936.     } else if (arguments[1].toLowerCase() === arguments[2].toLowerCase()) {
  3937.         sendMessage("/me @" + arguments[1] + ", trying to give yourself a cookie, eh? Don't be so greedy!");
  3938.     } else if (arguments[2] !== "") {
  3939.  
  3940.         const toUser = getUser(arguments[2]);
  3941.         if (!~toUser) return;
  3942.         //cookie list from basicBot
  3943.         const cookies = [
  3944.             "has given you a chocolate chip cookie!",
  3945.             "has given you a soft homemade oatmeal cookie!",
  3946.             "has given you a plain, dry, old cookie. It was the last one in the bag. Gross.",
  3947.             "gives you a sugar cookie. What, no frosting and sprinkles? 0\/10 would not touch.",
  3948.             "gives you a chocolate chip cookie. Oh wait, those are raisins. Bleck!",
  3949.             "gives you an enormous cookie. Poking it gives you more cookies. Weird.",
  3950.             "gives you a fortune cookie. It reads \"Why aren't you working on any projects?\"",
  3951.             "gives you a fortune cookie. It reads \"Give that special someone a compliment\"",
  3952.             "gives you a fortune cookie. It reads \"Take a risk!\"",
  3953.             "gives you a fortune cookie. It reads \"Go outside.\"",
  3954.             "gives you a fortune cookie. It reads \"Don't forget to eat your veggies!\"",
  3955.             "gives you a fortune cookie. It reads \"Do you even lift?\"",
  3956.             "gives you a fortune cookie. It reads \"m808 pls\"",
  3957.             "gives you a fortune cookie. It reads \"If you move your hips, you'll get all the ladies.\"",
  3958.             "gives you a fortune cookie. It reads \"I love you.\"",
  3959.             "gives you a Golden Cookie. You can't eat it because it is made of gold. Dammit.",
  3960.             "gives you an Oreo cookie with a glass of milk!",
  3961.             "gives you a rainbow cookie made with love :heart:",
  3962.             "gives you an old cookie that was left out in the rain, it's moldy.",
  3963.             "bakes you fresh cookies, it smells amazing."
  3964.         ];
  3965.  
  3966.         sendMessage("/me @" + toUser.username + ", @" + arguments[1] + " " + cookies[Math.floor(Math.random() * cookies.length)]);
  3967.     }
  3968. });
  3969. //commands['weather'] = new Command(true,0,"weather <@username> :: give random weather in world!", function(){
  3970. //      const toUser = getUser(arguments[2]);
  3971. //        if (!~toUser) return;
  3972. //      const locations = [
  3973. //          "Paris", "Dublin", "Tokyo", "Moscow",
  3974. //          "Madrid", "Miami", "Milan", "Dubai",
  3975. //          "Rome", "Athens", "Salvador", "Venice",
  3976. //          "London", "Istanbul", "New York", "Amsterdam",
  3977. //          "Barcelona", "Havana", "Sydney", "Lisbon",
  3978. //          "Stockholm", "Budapest", "Busan", "Charleston"
  3979. //       ];
  3980. //    
  3981. //       sendMessage("/me @" + toUser.username + ", @" + arguments[1] + " " + locations[Math.floor(Math.random() * locations.length)]);
  3982.   //  weather.forecast("Location",function(err, aData)
  3983. //Location can be id, city name or coordinate ([lat,lon])
  3984. //{
  3985. //  if(err) console.log(err);
  3986. //  else
  3987. //  {
  3988.         //you can use weather 'object' aData
  3989.    
  3990.         /*Get the temperature in JSON object
  3991.         *Here you have to send the position in the forecast
  3992.         * {temp :  , temp_min : , temp_max :}*/
  3993.        
  3994. //      weather.getKelvinTemp(0); //In kelvin
  3995. //      weather.getDegreeTemp(0); //In Degree
  3996. //      weather.getFahrenheitTemp(0); //In Fahrenheit
  3997.        
  3998.         /*Get the icon url of the current weather
  3999.         *Here you have to send the position in the forecast*/
  4000.        
  4001. //      weather.getIconUrl(0);
  4002. //  }
  4003. //})
  4004.    //}
  4005. });
  4006. commands['weather'] = new Command(true,0,"weather in received location", function(){
  4007.     const openweathermap = function(city, callback){
  4008.     var url = 'http://api.openweathermap.org/data/2.5/weather?q=' + city + '&lang=fr&units=metric&appid=' + openWeatherMapAppId;
  4009.    
  4010.     request(url, function(err, response, body){
  4011.         try{        
  4012.             var result = JSON.parse(body);
  4013.            
  4014.             if (result.cod != 200) {
  4015.                 callback(false);
  4016.             } else {
  4017.                 var previsions = {
  4018.                     temperature : Math.round(result.main.temp),
  4019.                     humidity : result.main.humidity,
  4020.                     wind: Math.round(result.wind.speed * 3.6),
  4021.                     city : result.name,
  4022.                 };
  4023.                        
  4024.                 callback(true, previsions);
  4025.             }
  4026.         } catch(e) {
  4027.             callback(false);
  4028.         }
  4029.     });
  4030.   }
  4031.   openweathermap('your city here', function(success, previsions) {
  4032.        
  4033.         if (!success) sendMessage("Error, weather lookup failed");
  4034.  
  4035.                 sendMessage("/me @" + toUser.username + "The weather in" + previsions.city + "is" + previsions.city);
  4036.   });
  4037. }                      
  4038.          
  4039.    
  4040. commands['disable'] = new Command(true,3,"disable <command name> :: Disable a command. Requires Manager+.",function(){
  4041.     const cmdname = arguments[1];
  4042.     if (commands.hasOwnProperty(cmdname) && !~arrFind(['enable', 'disable'], cmdname)) {
  4043.         if (commands[cmdname].state === false) {
  4044.             sendMessage("/me Did not disable \"" + cmdname + "\", it is already inactive.");
  4045.         } else {
  4046.             commands[cmdname].state = false;
  4047.             sendMessage("/me Disabled \"" + cmdname + "\".");
  4048.         }
  4049.     }
  4050. });
  4051.  
  4052. commands['enable'] = new Command(true,3,"enable <command name> :: Enable a command. Requires Manager+.",function(){
  4053.     const cmdname = arguments[1];
  4054.     if (commands.hasOwnProperty(cmdname) && !~arrFind(['enable', 'disable'], cmdname)) {
  4055.         if (commands[cmdname].state === true) {
  4056.             sendMessage("/me Did not enable \"" + cmdname + "\", it is already active.");
  4057.         } else {
  4058.             commands[cmdname].state = true;
  4059.             sendMessage("/me Enabled \"" + cmdname + "\".");
  4060.         }
  4061.     }
  4062. });
  4063.  
  4064. commands['english'] = new Command(true,2,"english <@username> :: Notify a user in their language to speak English if it is required. Requires Bouncer+.",function(){
  4065.     const id = arguments[1],
  4066.         langs = {
  4067.             "en": "",
  4068.             "bg": "Моля, говорете на английски. ",
  4069.             "bs": "Molim Vas, govorite engleski. ",
  4070.             "cs": "Prosím mluv anglicky. ",
  4071.             "da": "Vær venlig at tale engelsk. ",
  4072.             "de": "Bitte sprechen Sie Englisch. ",
  4073.             "pi": "Yarr! ",
  4074.             "es": "Por favor, hable inglés. ",
  4075.             "fi": "Voitko puhua englantia? ",
  4076.             "fr": "Parlez anglais, s'il vous plaît. ",
  4077.             "hr": "Molim Vas, govorite engleski. ",
  4078.             "hu": "Kérem, angolul beszéljen. ",
  4079.             "it": "Per favore parli in inglese. ",
  4080.             "ja": "英語で話して下さい。 ",
  4081.             "ko": "영어로 말해 주세요. ",
  4082.             "lt": "Prašom kalbėti angliškai. ",
  4083.             "lv": "Lūdzu, runājiet angliski. ",
  4084.             "ms": "Sila berbahasa Inggeris. ",
  4085.             "nl": "Kunt u alstublieft Engels spreken. ",
  4086.             "no": "Vær så snill å snakk engelsk. ",
  4087.             "pl": "Proszę mówić po angielsku. ",
  4088.             "pt": "Fale em inglês. ",
  4089.             "ru": "Пожалуйста, говорите по-английски. ",
  4090.             "sk": "Hovorte po anglicky, prosím. ",
  4091.             "sl": "Prosim govori angleško. ",
  4092.             "sr": "Молим Вас, говорите енглески. ",
  4093.             "sv": "Var vänlig och tala engelska. ",
  4094.             "th": "กรุณาพูดภาษาอังกฤษ ",
  4095.             "tr": "Lütfen İngilizce konuşun. ",
  4096.             "zh": "请讲英语。 請講英語。 "
  4097.         };
  4098.     getUserData(id, function(user) {
  4099.         if (!~user) return;
  4100.         let langMsg = langs[user.language];
  4101.         if (langMsg === undefined) langMsg = "";
  4102.         sendMessage("@" + user.username + ": " + langMsg + "Please speak english.");
  4103.     });
  4104. });
  4105.  
  4106. commands['kick'] = new Command(true,3,"kick @username :: Bans a user from the room and unbans them 2.5 seconds later, simulating a kick. Requires Manager+.", function() {
  4107.     const user = getUser(arguments[2]);
  4108.     if (!~user) return sendMessage("/me [@" + arguments[1] + "] Could not find that user.");
  4109.     else if (user.id === me.id) return;
  4110.     else {
  4111.         kickUser(user.id, 1);
  4112.         return sendMessage("/me [@" + arguments[1] + "] Kicking user @" + user.username + "...");
  4113.     }
  4114. });
  4115.  
  4116. commands['gif'] = new Command(true,0,"gif <tags> :: Grabs a random image from Giphy with the given tags. Any rank. 2s cooldown.",function() {
  4117.     if (arguments.length !== 3) return;
  4118.     let username = arguments[1];
  4119.     let spl = arguments[2];
  4120.     let params = {
  4121.         key: "dc6zaTOxFJmzC",
  4122.         tags: spl.join("+"),
  4123.         rating: "pg-13"
  4124.     };
  4125.     let url = "https://api.giphy.com/v1/gifs/random?api_key=" + params.key + "&tag=" + params.tags + "&rating=" + params.rating;
  4126.     req.get(url, {json: true}, function(e,r,b) {
  4127.         if (e) error(cc.red(e));
  4128.         else if (b && b.meta && b.meta.status === 200 && b.data && b.data.hasOwnProperty('id')) {
  4129.             req.get("http://api.giphy.com/v1/gifs/" + b.data.id + "?api_key=" + params.key, {json: true}, function(e,r,b) {
  4130.                 if (e) error(cc.red(e));
  4131.                 else if (b && b.meta && b.meta.status === 200 && b.data && b.data.images) {
  4132.                     let image = "",
  4133.                         imageList = [
  4134.                             b.data.images.original,
  4135.                             b.data.images.downsized_large,
  4136.                             b.data.images.downsized
  4137.                         ],
  4138.                         i;
  4139.                     for (i in imageList) {
  4140.                         if (parseInt(imageList[i].size) <= 4194304) {
  4141.                             image = imageList[i].url.split('http://').join('https://');
  4142.                             break;
  4143.                         }
  4144.                     }
  4145.                     if (image && image !== "") {
  4146.                         sendMessage("/me [@" + username + "] [Tags: " + spl.join(", ") + "] " + image);
  4147.                     }
  4148.                 }
  4149.             });
  4150.         }
  4151.     });
  4152. }, 2000);
  4153. //my part
  4154. /*commands ['weather'] = new Command(true,0,"weather <location> :: Grabs weather from said location. Any rank. 1 munute cooldown.", function() {
  4155.     if (arguments.length !== 3) return;
  4156.     let username = arguments[1];
  4157.     let sil = arguments[2];
  4158.     let params = {
  4159.         key: "d178018e284b0f6173cd1ad56b9d28f2",
  4160.         location: sil.join("+"),
  4161.     };
  4162.     let url = "http://api.openweathermap.org/data/2.5/forecast?id=524901&APPID=" + params.key + "&location" + params.location;
  4163.     req.get(url, {json:true}, function(e,r,b) {
  4164.         if (e) error(cc.red(e));
  4165.         else if (b && b.meta && b.meta.status === 200 && b.data && b.data.hasOwnProperty('id')) {
  4166.             req.get("http://api.openweathermap.org/data/2.5" + b.data.id + "&id=524901&APPID=" + params.key, {json: true}, function(e,r,b) {
  4167.                 if (e) error(cc.red(e));
  4168.                 else if (b && b.meta && b.meta.status === 200 && b.data && b.data.locals) {
  4169.                     let local = "",
  4170.                         localList = [
  4171.                             b.data.locals.original,
  4172.                             b.data.locals.downsized_large,
  4173.                             b.data.locals.downsized
  4174.                         ],
  4175.                         i;
  4176.                     for (i in localList) {
  4177.                         if (parseInt(localList[i].size) <=4194304) {
  4178.                             image = imageList[i].url.split('http://').join('https://');
  4179.                             break;
  4180.                         }
  4181.                     }
  4182.                     if (local && local !== "") {
  4183.                         sendMessage("/me [@" + username + "] [Tags: " + sil.join(", ") + "] " + local);
  4184.                     }
  4185.                 }
  4186.             });
  4187.         }
  4188.     });
  4189. }, 60000);
  4190.  */ //^                      
  4191.  
  4192. commands['jointime'] = new Command(true,0,"jointime [@username|#userID] :: Returns amount of time since the given user entered the room. Gets your own info if no valid argument. Any rank.",function() {
  4193.     if (arguments.length !== 3) return;
  4194.     let sndmsg = "";
  4195.     if (room && seen[room.slug] && seen[room.slug][arguments[2]] && seen[room.slug][arguments[2]]['lastWelcome'] && ~getUser(arguments[2])) {
  4196.         let usr = seen[room.slug][arguments[2]];
  4197.         sndmsg = arguments[1]+" joined approximately "+secsToLabelTime(Date.now() - usr.lastWelcome, true)+" ago.";
  4198.     } else {
  4199.         sndmsg = arguments[1]+" was not found.";
  4200.     }
  4201.     sendMessage(sndmsg);
  4202. });
  4203.  
  4204. commands['link'] = new Command(true,0,"link :: Returns the link of the song currently playing. Any rank.",function() {
  4205.     if (arguments.length !== 1) return;
  4206.     outputCurrentLink(true);
  4207. });
  4208.  
  4209. commands['props'] = new Command(true,0,"props :: Show some appreciation for the DJ! Any rank.",function(){
  4210.     const dj = getUser(room.booth.currentDJ);
  4211.     let msg = "";
  4212.     if (!room || !~dj || (~dj && arguments[1] === dj.username)) return;
  4213.     if (arguments[2]) {
  4214.         msg = "awwww fukin sick cunt";
  4215.     } else {
  4216.         //props list from ureadmyname's basicBot fork
  4217.         const props = [
  4218.             "awwww fukin sick cunt", "this song = 11/10 IGN",
  4219.             "this track is amazing", "awesometastic play",
  4220.             "love this song <3", "this is top shit",
  4221.             "excellent tune", "awesome track",
  4222.             "amazing song", "just amazing",
  4223.             "great song", "nice play",
  4224.             "killer", "yo, this is some dope shit"
  4225.         ];
  4226.         msg = props[Math.floor(Math.random() * props.length)];
  4227.     }
  4228.     if (msg.trim() !== "")
  4229.         sendMessage("/me @" + arguments[1] + " gave props to @" + dj.username + ", \"" + msg + "!\"");
  4230. });
  4231.  
  4232. commands['roll'] = new Command(true,0,"roll [<1-10>|<1-20d1-999999999>] :: Returns a random number with given amount of digits, or rolls dice. Default: 2 digits. Any rank.",function(){
  4233.     if (arguments.length !== 3) return;
  4234.     let data = arguments[1];
  4235.     let splitmsg = arguments[2];
  4236.  
  4237.     let sndmsg = "";
  4238.     if (splitmsg[1] && arrFind(splitmsg[1],'d') > 0 && arrFind(splitmsg[1],'d') < 3 && /^(?:\d{1,2}d\d{1,9})$/.test(splitmsg[1])) {
  4239.  
  4240.  
  4241.         let d,rolls,sides,sum,die;
  4242.         d = splitmsg[1].match(/^(?:\d{1,2}d\d{1,9})$/)[0].split('d');
  4243.         rolls = parseInt(d[0]);
  4244.         sides = parseInt(d[1]);
  4245.         if (rolls > 20 || rolls < 1)
  4246.             rolls = 2;
  4247.         if (sides > 999999999 || sides < 1)
  4248.             sides = 6;
  4249.         sum = 0;
  4250.         die = (rolls > 1 ? "dice" : "die");
  4251.  
  4252.         sndmsg = '@'+data.un+' rolled '+rolls+' '+sides+'-sided '+die+' and got: '
  4253.  
  4254.         for (;rolls>=1;rolls--) {
  4255.             sum += Math.floor((Math.random()*sides)+1);
  4256.         }
  4257.  
  4258.  
  4259.         sndmsg += sum.toString();
  4260.  
  4261.  
  4262.     } else {
  4263.         let roll = 2;
  4264.         let combos = [' [dubs!]',' [trips!]',' [quads!]',' [quints!]',' [sexts!]',' [septs!!]',' [octs!!!]',' [nons!!!!]',' [decs!!!!!]'];
  4265.         let dig = parseInt(splitmsg[1]);
  4266.         if (!isNaN(dig) && dig > 0 && dig < 11) roll = dig;
  4267.         let rollnum = Math.floor(Math.random() * Math.pow(10, roll)).toString();
  4268.         rollnum = "0".repeat(roll - rollnum.length) + rollnum;
  4269.         sndmsg = '@' + data.un + ' rolled: ';
  4270.         sndmsg += rollnum;
  4271.  
  4272.         let j = 0,
  4273.             i,
  4274.             repeatcheck = rollnum[rollnum.length - 1];
  4275.  
  4276.         for (i = (rollnum.length - 1); i > -1; i--) {
  4277.             if (rollnum[i] === repeatcheck)
  4278.                 j++;
  4279.             else
  4280.                 break;
  4281.         }
  4282.         if (j > 1 && combos[j - 2] !== undefined) {
  4283.             sndmsg += combos[j - 2]
  4284.         } else if (rollnum.substr(rollnum.length - 2) === '69') {
  4285.             sndmsg += ' hehe xd';
  4286.         }
  4287.     }
  4288.     sendMessage(sndmsg);
  4289. });
  4290.  
  4291. commands['seentime'] = new Command(true,0,"seentime [@username|#userID] :: Returns the total amount of time a user has been seen in the room. Gets your own info if no valid argument. Any rank.",function() {
  4292.     if (arguments.length !== 3) return;
  4293.     let sndmsg = "";
  4294.     if (room && seen[room.slug] && seen[room.slug][arguments[2]]) {
  4295.         activeCheck(arguments[2]);
  4296.         let since = ".";
  4297.         if (seen[room.slug][arguments[2]]['firstSeen']) since = " since " + new Date(seen[room.slug][arguments[2]].firstSeen).toDateString().substr(4) + ".";
  4298.         sndmsg = arguments[1]+" has been in this room for approximately "+secsToLabelTime(seen[room.slug][arguments[2]].activeTime, true)+since;
  4299.     } else {
  4300.         sndmsg = arguments[1]+" was not found.";
  4301.     }
  4302.     sendMessage(sndmsg);
  4303. });
  4304.  
  4305. commands['set'] = new Command(true,3,"set <option> <value> :: Sets a bot option to the given value. If no value is given, returns the current value of it. List of valid options: https://git.io/vM9aD Requires Manager+.",function(){
  4306.     const username = arguments[1],
  4307.         option = arguments[2].toLowerCase(),
  4308.         valid = {
  4309.             'autowoot':BotSettings.autoWoot,
  4310.             'welcomeusers':BotSettings.welcomeUsers,
  4311.             'chatdeletetriggermessages':BotSettings.chatDeleteTriggerMessages,
  4312.             'chatdeleteresponses':BotSettings.chatDeleteResponses,
  4313.             'announcementinterval':BotSettings.announcementInterval,
  4314.             'announcementrandom':BotSettings.announcementRandom,
  4315.             'sendannouncements':BotSettings.sendAnnouncements,
  4316.             'usemessagecommands':BotSettings.useMessageCommands,
  4317.             'doafkcheck':BotSettings.doAFKCheck,
  4318.             'doautodisable':BotSettings.doAutoDisable,
  4319.             'autostuckskip':BotSettings.autoStuckSkip,
  4320.             'doskipcheck':BotSettings.doSkipCheck,
  4321.             'doautoskip':BotSettings.doAutoSkip,
  4322.             'hostbypassautoskip':BotSettings.hostBypassAutoSkip,
  4323.             'sendmotd':BotSettings.sendMOTD,
  4324.             'motdinterval':BotSettings.motdInterval,
  4325.             'motd':BotSettings.motd
  4326.         };
  4327.  
  4328.     let val = arguments[3];
  4329.     let message = "";
  4330.     if (!valid.hasOwnProperty(option)) message = "That option is either invalid or was not found.";
  4331.     else {
  4332.         let i;
  4333.         const getName = function() {
  4334.             for (i in BotSettings) {
  4335.                 if (i.toLowerCase() === option)
  4336.                     return i;
  4337.             }
  4338.             return "(NOT FOUND)";
  4339.         }
  4340.         if (val === null) {
  4341.             message = getName() + " is currently set to: " + valid[option];
  4342.         } else {
  4343.             if (val === "true" || val === "on") val = true;
  4344.             else if (val === "false" || val === "off") val = false;
  4345.             else if (strIsNum(val)) val = parseInt(val);
  4346.  
  4347.             if (typeof val === typeof valid[option]) {
  4348.                 if (BotSettings[getName()] === val) message = getName() + " is already set to " + val + ".";
  4349.                 else if (typeof val === "number") {
  4350.                     if ((option === "announcementinterval" || option === "motdinterval") && val < 5000)
  4351.                         message = "That is an invalid interval amount. Must be 5000+.";
  4352.                     else
  4353.                         BotSettings._fn.changeSetting(option, val);
  4354.                         message = "Changed " + getName() + " to: " + val;
  4355.                 } else {
  4356.                     BotSettings._fn.changeSetting(option, val);
  4357.                     message = "Changed " + getName() + " to: " + val;
  4358.                 }
  4359.             }
  4360.         }
  4361.     }
  4362.     if (message !== "")
  4363.         sendMessage("/me [@" + username + "] " + message);
  4364. });
  4365.  
  4366. commands['shots'] = new Command(true,0,"shots|shot|k1tt [@username] :: Buy a random shot for a user! Any rank.",function() {
  4367.     if (arguments[2].toLowerCase() === me.username.toLowerCase()) {
  4368.         sendMessage("/me takes a shot.");
  4369.     } else if (arguments[1].toLowerCase() === arguments[2].toLowerCase() && arguments[3] !== "k1tt") {
  4370.         sendMessage("/me @" + arguments[1] + ", buy someone else a shot for once.");
  4371.     } else if (arguments[2] !== "") {
  4372.         const toUser = getUser(arguments[2]);
  4373.         if (!~toUser) return;
  4374.         //shots list from ureadmyname's basicBot fork
  4375.         const shots = [
  4376.                 "Slippery Nipple", "Tequila Slammer", "Irish Car Bomb",
  4377.                 "Liquid Cocaine", "Redheaded Slut", "Johnny Walker",
  4378.                 "Cement Mixer", "Jack Daniels", "Grasshopper",
  4379.                 "Jell-O-Shot", "Sammy Jager", "Black Rose",
  4380.                 "Jager Bomb", "Lemon Drop", "Melon Ball",
  4381.                 "Fireball", "Jim Beam", "Kamikaze",
  4382.                 "Smirnoff", "Tequila", "B-52",
  4383.                 "Hot Damn", "Pineapple Upside Down Cake", "White Gummy Bear",
  4384.                 "Absolut Bitch", "Absolut Legspreader", "Alice in Wonderland",
  4385.                 "Jolly Rancher", "Buttery Nipple", "252",
  4386.                 "Captain Coke", "Panty Man", "Kick in the balls",
  4387.                 "Mind Eraser", "Motor Oil", "Afterburner",
  4388.                 "Partybar Schuffel", "Jack Daniels", "Tes Generaciones",
  4389.                 "Passed Out Naked in the Bathroom", "A Kick in the Crotch", "Flaming Lemon Drop",
  4390.                 "Purple Hooter", "Cherry Tootsie Pops", "Blow Job",
  4391.                 "Scooby Snack", "Surfer on Acid", "Alabama Slammer",
  4392.                 "Ohio State Redeye", "Washington Apple", "Cherry Bomb", "Three Wise Men"
  4393.               ],
  4394.               shot = shots[Math.floor(Math.random() * shots.length)];
  4395.         if (arguments[3] === "k1tt" && arguments[2].toLowerCase() === arguments[1].toLowerCase()) {
  4396.             sendMessage("/me @" + arguments[1] + " bought themselves a shot of " + shot + "!");
  4397.         } else {
  4398.             sendMessage("/me @" + arguments[1] + " bought @" + toUser.username + " a shot of " + shot + "!");
  4399.         }
  4400.     }
  4401. });
  4402.  
  4403. commands['skip'] = new Command(true,2,"skip [reason] :: Skips current song with optional reason, if valid. Requires Bouncer+.",function() {
  4404.     if (arguments.length !== 3) return;
  4405.     skipSong(arguments[1], arguments[2], false, true);
  4406. });
  4407.  
  4408. commands['skipreasons'] = new Command(true,2,"skipreasons :: Lists reasons that can be used with " + TRIGGER + "skip. Requires Bouncer+.", function() {
  4409.     if (arguments.length !== 2) return;
  4410.     let reasons = BotSettings.skipReasons;
  4411.     let sndmsg = "[@" + arguments[1] + "] Skip reasons: ";
  4412.     if (typeof reasons !== "object" || Object.keys(reasons).length < 1) sndmsg += "(none found)";
  4413.     else {
  4414.         let temp = [],
  4415.             i;
  4416.         for (i in reasons) {
  4417.             temp.push(i);
  4418.         }
  4419.         temp = temp.join(", ");
  4420.         sndmsg += temp;
  4421.     }
  4422.     sendMessage(sndmsg);
  4423. });
  4424.  
  4425. commands['stats'] = new Command(true,0,"stats [@username|#userID] :: Returns the user's recorded amount of plays and votes received. Gets your own info if no valid argument.",function() {
  4426.     if (arguments.length !== 3) return;
  4427.     let sndmsg = "";
  4428.     if (room && seen[room.slug] && seen[room.slug][arguments[2]]) {
  4429.         let usr = seen[room.slug][arguments[2]];
  4430.         sndmsg = arguments[1]+"'s overall stats: Plays:"+usr.plays+", W:"+usr.votes.woot+", G:"+usr.votes.grab+", M:"+usr.votes.meh+"." + (usr.votes.meh ? "Woot/Meh Ratio: " + (usr.votes.woot/usr.votes.meh) : "");
  4431.     } else {
  4432.         sndmsg = arguments[1]+" was not found.";
  4433.     }
  4434.     sendMessage(sndmsg);
  4435. });
  4436.  
  4437. commands['swap'] = new Command(true,3,"swap @user1 @user2 :: Swaps positions of two users in the waitlist. At least one must be in the waitlist. Requires Manager+.", function () {
  4438.     if (arguments.length !== 2) return;
  4439.     const message = arguments[1],
  4440.         atx = message.indexOf('@'),
  4441.         aty = message.indexOf('@', atx + 1);
  4442.  
  4443.     if ((room && room.booth.isLocked) || atx < 0 || aty < 0 || atx === aty) {
  4444.         return;
  4445.     } else {
  4446.         const userx = getUser(message.slice(atx+1,aty-1)),
  4447.               usery = getUser(message.slice(aty+1));
  4448.         if (~userx && ~usery) {
  4449.             const posx = getWaitlistPos(userx.id),
  4450.                   posy = getWaitlistPos(usery.id);
  4451.             if (posx < 0 && posy < 0) {
  4452.                 return;
  4453.             } else if (posx >= 0 && posy >= 0) {
  4454.                 moveDJ(userx.id, posy, function() {
  4455.                     moveDJ(usery.id, posx);
  4456.                 });
  4457.             } else if (posx < 0 && posy >= 0) {
  4458.                 removeUserFromWaitlist(usery.id, function() {
  4459.                     addUserToWaitlist(userx.id, function() {
  4460.                         moveDJ(userx.id, posy);
  4461.                     });
  4462.                 });
  4463.             } else if (posy < 0 && posx >= 0) {
  4464.                 removeUserFromWaitlist(userx.id, function() {
  4465.                     addUserToWaitlist(usery.id, function() {
  4466.                         moveDJ(usery.id, posx);
  4467.                     });
  4468.                 });
  4469.             } else {
  4470.                 return;
  4471.             }
  4472.         }
  4473.     }
  4474. });
  4475.  
  4476. commands['trigger'] = new Command(true,0,"trigger :: Returns current trigger of the bot. This can be called with any valid trigger character. Any rank.",function() {
  4477.     validateTrigger();
  4478.  
  4479.     sendMessage('Current command trigger: '+TRIGGER);
  4480. });
  4481.  
  4482. commands['uptime'] = new Command(true,0,"uptime :: Returns uptime of this bot. Any rank.",function() {
  4483.     if (STARTTIME) {
  4484.         let uptime = secsToLabelTime(Date.now() - STARTTIME, true);
  4485.         let sndmsg = "Bot uptime: " + uptime;
  4486.         sendMessage(sndmsg);
  4487.     }
  4488. });
  4489.  
  4490. //only call this when logged out!
  4491. //don't forget to close the websocket too
  4492. //does not work correctly yet
  4493. function cleanState() {
  4494.     validateTrigger();
  4495.     HIGHWAY = 21;
  4496.     LASTSENTMSG = 0;
  4497.     if (room) room.roulette._cleanup();
  4498.     sessJar = me = {};
  4499.     clearInterval(SAVESEENTIME);
  4500.     clearInterval(AFKCHECKTIME);
  4501.     clearInterval(AUTODISABLETIME);
  4502.     clearTimeout(ANNOUNCEMENTTIME);
  4503.     clearTimeout(HEARTBEAT.timer);
  4504.     clearTimeout(MOTDTIME);
  4505.     room = null;
  4506.     rl.close();
  4507. }
  4508.  
  4509. //http://stackoverflow.com/questions/12672193/fixed-position-command-prompt-in-node-js
  4510. //write() is adapted from Hors Sujet's answer in the above link
  4511. function write(args) {
  4512.     let t = Math.ceil((rl.line.length + 3) / process.stdout.columns);
  4513.     let text = util.format.apply(console, args);
  4514.     process.stdout.write("\n\x1B[" + t + "A\x1B[0J");
  4515.     process.stdout.write(text+'\n');
  4516.     process.stdout.write("\n\x1B[E".repeat(t - 1));
  4517.     rl._refreshLine();
  4518. }
  4519.  
  4520. clearWindow();
  4521.  
  4522. console.log(cc.bgWhiteBright(' '.repeat(process.stdout.columns-1)));
  4523. console.log(cc.bgWhite(' '.repeat(process.stdout.columns-1)));
  4524. console.log(cc.bgBlackBright(' '.repeat(process.stdout.columns-1)));
  4525. console.log('\n');
  4526. console.log(cc.magentaBright(TITLE + ' v' + VER));
  4527. console.log(cc.blackBright('https://github.com/'+cc.blueBright("zeratul0")));
  4528. console.log(cc.blackBright('https://plug.dj/@/'+cc.blueBright("zeratul-")));
  4529.  
  4530. console.log = function() { write(arguments); }
  4531.  
  4532. function log(msg) { console.log(timestamp() + msg); }
  4533. function nodeLog(msg) { console.log(timestamp() + cc.black.bgGreenBright(' Node ') + ' '.repeat(HIGHWAY-6) + msg); }
  4534. function error(msg) { console.log(timestamp() + cc.black.bgRedBright(' ERR  ') + ' '.repeat(HIGHWAY-6) + msg); }
  4535. function warn(msg) { console.log(timestamp() + cc.black.bgYellowBright(' WARN ') + ' '.repeat(HIGHWAY-6) + msg); }
  4536. function info(msg) { console.log(timestamp() + cc.black.bgBlueBright(' Info ') + ' '.repeat(HIGHWAY-6) + msg); }
  4537. function debug(msg) { if (DEBUG) console.log(timestamp() + cc.black.bgMagentaBright(' DEBUG ') + ' '.repeat(HIGHWAY-7) + msg); }
  4538.  
  4539. if (EXTERNALSETTINGS)
  4540.     BotSettings._fn.loadFromFile();
  4541. else
  4542.     BotSettings._fn.apply();
  4543.  
  4544. fs.readFile('data/seenUsers.json', (e,data)=>{if (e) return; else if (data && data+"" !== "") {seen = JSON.parse(data+""); startTimer("seen");}});
  4545.  
  4546. login();
Add Comment
Please, Sign In to add comment