[
  {
    "path": ".eslintrc.json",
    "content": "{\n    \"env\": {\n        \"node\": true\n    },\n    \"extends\": \"airbnb\",\n    \"globals\": {\n        \"index\": true,\n\t\"has\": true,\n\t\"selfId\": true,\n\t\"vaebId\": true,\n\t\"botDir\": true,\n\t\"Util\": true,\n\t\"Data\": true,\n\t\"Trello\": true,\n\t\"Admin\": true,\n\t\"Music\": true,\n\t\"Music2\": true,\n\t\"Cmds\": true,\n\t\"Events\": true,\n\t\"Discord\": true,\n        \"client\": true,\n        \"colAction\": true,\n\t\"colUser\": true,\n\t\"colMessage\": true,\n\t\"colCommand\": true,\n\t\"colGreen\": true,\n        \"colBlue\": true,\n        \"cmd\": true, \"args\": true, \"msgObj\": true, \"speaker\": true, \"channel\": true, \"guild\": true\n    },\n    \"rules\": {\n        \"default-case\": \"off\",\n        \"eqeqeq\": \"off\",\n        \"import/no-dynamic-require\": \"off\",\n        \"indent\": [\"error\", 4],\n        \"func-names\": \"off\",\n        \"global-require\": \"off\",\n        \"jsx-a11y/href-no-hash\": \"off\",\n        \"max-len\": \"off\",\n        \"no-bitwise\": \"off\",\n        \"no-cond-assign\": \"off\",\n        \"no-console\": \"off\",\n        \"no-continue\": \"off\",\n        \"no-control-regex\": \"off\",\n        \"no-eval\": \"off\",\n        \"no-irregular-whitespace\": \"off\",\n        \"no-lonely-if\": \"off\",\n        \"no-mixed-operators\": \"off\",\n        \"no-multi-spaces\": [\"error\", {\"ignoreEOLComments\": true}],\n        \"no-param-reassign\": \"off\",\n        \"no-plusplus\": \"off\",\n        \"no-restricted-syntax\": \"off\",\n        \"no-underscore-dangle\": \"off\",\n        \"quote-props\": \"off\"\n    }\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".gitignore",
    "content": "# Packages\nnode_modules/\nyarn.lock\n\n# Log files\n*.log\n\n# Data Storage\ndata/autoroles.json\ndata/history.json\ndata/mutes.json\ndata/playlist.json\n\n# Testing\nAuth.js\nTest.js\n\n# =========================\n# Everything below is .gitignore template\n# =========================\n\n# Miscellaneous\n.tmp/\n.vscode/\n\n# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# =========================\n# Operating System Files\n# =========================\n\n# OSX\n# =========================\n\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Adam\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# VaeBot - A Multi-Purpose Discord Bot\n\n## Includes\n- Full warn/mute based moderation system\n- Powerful anti-raid and anti-spam system\n- Hierarchy-based permission reasoning\n- Complete music system\n- A huge array of commands\n\n## Staff-Only Commands\n​\n**;actions** OR **;guild** actions OR **;all** actions - Output all actions that can be used in **;link**\n\n**;addauto** OR **;adda** OR **;addtoauto** - Adds a song to the music auto-playlist\n\n**;addrole** - Add a role to a user\n\n**;alert** OR **;dm** OR **;announce** - Sends a DM to everyone in the guild with a certain role\n\n**;allinfo** - Get guild, role, channel and permission info in one huge set of messages\n\n**;ban** OR **;banhammer** OR **;permaban** - Ban a user from the guild\n\n**;bans** OR **;getbans** - Get all banned users\n\n**;calm** OR **;calmchat** OR **;slow** OR **;slowchat** - Slows down chat speed\n\n**;changemute** OR **;change** OR **;setmute** OR altermute - Change details of an active mute\n\n**;clear** OR **;clean** OR **;wipe** OR **;clearchats** OR **;cleanchats** - Delete the last <1-1000> messages matching a [user | regex-pattern | message-type] in the channel\n\n**;clearqueue** - Clears VaeBot's queue of music\n\n**;events** OR **;guild** events OR **;all** events - Output all events that can be used in **;link**\n\n**;getlinks** OR **;links** OR **;triggers** - Output all created links\n\n**;hardkick** OR **;hardeject** OR **;softban** - Kick a user from the guild (extra hard)\n\n**;history** OR **;mutehistory** - Get all users with mute history\n\n**;join** OR **;summon** - Make VaeBot join a voice channel\n\n**;kick** OR **;eject** - Kick a user from the guild\n\n**;leave** OR **;exit** - Make VaeBot leave it's voice channel\n\n**;link** OR **;addlink** OR **;trigger** OR **;event** - Link an event to an action\n\n**;mute** OR **;mutehammer** - Mute a user (in all guild channels) and add the mute to their record\n\n**;mutes** OR **;usermutes** OR **;history** OR **;userhistory** - Get the mute history of a user\n\n**;mutes** OR **;muted** - Get all currently muted users\n\n**;nick** OR **;nickname** - Set a user's nickname\n\n**;playf** - Make VaeBot play some bangin' tunes... from a file :o\n\n**;remauto** OR **;rema** - Remove a song from the music auto-playlist\n\n**;remautorole** OR **;delautorole** OR aroledel - Remove an autorole\n\n**;remqueue** OR **;remq** - Remove a song from the music queue\n\n**;remrole** OR **;removerole** OR **;delrole** - Remove a role from a user\n\n**;setautorole** OR **;addautorole** OR **;arole** - Set a new autorole\n\n**;skip** - Skip to the next song\n\n**;stop** OR **;silence** - Cancel the party, the bangin' tunes can wait for another day\n\n**;switch** - Specific command\n\n**;tempban** OR **;tban** OR **;temporaryban** OR **;timeban** OR **;bantime** - Temporarily ban a user from the guild\n\n**;tempbans** OR **;tbans** OR **;timebans** OR **;timedbans** - Get all temporarily banned users\n\n**;unban** OR **;remban** - Unban a user from the guild\n\n**;uncalm** OR **;uncalmchat** OR **;unslow** OR **;unslowchat** - Removes chat slowdown\n\n**;undomute** OR **;popmute** - Remove a user's last mute from their record and unmute them if they are muted\n\n**;unlink** OR **;remlink** OR **;dellink** OR **;untrigger** OR **;unevent** - UnLink an event from an action\n\n**;unmute** OR **;unwarn** OR **;unmutehammer** - Unmute a user\n\n**;warn** OR **;warnhammer** - Warns a user and puts the warning on their record\n\n\n## Public Commands\n​\n**;autoplaylist** OR **;ap** - Output all the bangin' tunes in the auto-playlist\n\n**;autoroles** - Get all autoroles (roles which users are allowed to assign to themselves)\n\n**;channels** - Get all guild channels\n\n**;closeticket** OR **;closesupport** OR **;stopticket** OR **;endticket** OR **;close** - Create a ticket to be viewed by Support\n\n**;cmds** OR **;commands** OR **;help** - Output all commands\n\n**;decrypt** - Decrypt text using One Time Pad\n\n**;define** OR **;urban** - Output the definition for a word/phrase using Urban Dictionary\n\n**;encrypt** - Encrypt text using One Time Pad\n\n**;ginfo** OR **;guildinfo** - Get guild info\n\n**;img** OR **;image** - Output an image for a word/phrase using Google\n\n**;info** - Get info about a user\n\n**;nowplaying** OR **;np** - Get info about the currently playing song\n\n**;offenses** OR **;badoffenses** OR **;listoffenses** OR **;rules** - Output the list of offenses with defined mute times\n\n**;ping** - Pings a user\n\n**;play** OR **;add** OR **;addqueue** - Make VaeBot play some bangin' tunes (or add them to the queue if the party's already started)\n\n**;playauto** OR **;playa** - Plays a tune already stored in the auto-playlist\n\n**;power** OR **;rank** OR **;rate** - Are you over 9000?!\n\n**;queue** - List all queued songs\n\n**;roles** - Get all guild roles\n\n**;startauto** OR **;startap** - Start playing the auto-playlist music\n\n**;startqueue** - Start playing the queued music\n\n**;syntax** OR **;help** OR **;cmd** - Display command information\n\n**;ticket** OR **;support** OR **;ask** OR **;addticket** OR **;submitticket** OR **;sendticket** OR **;newticket** - Create a ticket to be viewed by Support\n\n**;tickets** OR **;gettickets** OR **;showtickets** OR **;activetickets** OR **;displaytickets** OR **;supporttickets** - Display all open support tickets\n\n**;toggle** - Toggle an autorole on the speaker\n\n**;translate** - Translate a word/sentence into English\n\n**;txt** OR **;text** OR **;type** - Echo your text with emojis\n\n**;undo** OR **;pop** - Remove the last song from the queue which was added by the speaker\n\n**;voteskip** - Vote to skip the current song (will skip when the vote reaches 50% of the users in the voice channel)\n"
  },
  {
    "path": "Util.js",
    "content": "/*\n\naddCommand\\((\\[.+?\\]), (\\w+?), (\\w+?), (\\w+?), function\\([\\w, ]+?\\) {.*\\n\\t([\\s\\S]+?)\\n},\\n\\t(\".*?\"),\\n\\t(\".*?\"),\\n\\t(\".*?\")\\n\\)\n\nmodule.exports = Cmds.addCommand({\n    cmds: \\1,\n\n    requires: {\n        guild: \\4,\n        loud: false\n    },\n\n    desc: \\6,\n\n    args: \\7,\n\n    example: \\8,\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n    \\5\n    }\n})\n\n*/\n\nconst FileSys = index.FileSys;\nconst DateFormat = index.DateFormat;\nconst Exec = index.Exec;\nconst Path = index.Path;\nconst NodeUtil = index.NodeUtil;\n\nexports.charLimit = 1999;\n\nexports.regexURLPerfect = new RegExp(\n    '^' +\n        // protocol identifier\n        '(?:(?:https?|ftp)://)' +\n        // user:pass authentication\n        '(?:\\\\S+(?::\\\\S*)?@)?' +\n        '(?:' +\n            // IP address exclusion\n            // private & local networks\n            '(?!(?:10|127)(?:\\\\.\\\\d{1,3}){3})' +\n            '(?!(?:169\\\\.254|192\\\\.168)(?:\\\\.\\\\d{1,3}){2})' +\n            '(?!172\\\\.(?:1[6-9]|2\\\\d|3[0-1])(?:\\\\.\\\\d{1,3}){2})' +\n            // IP address dotted notation octets\n            // excludes loopback network 0.0.0.0\n            // excludes reserved space >= 224.0.0.0\n            // excludes network & broacast addresses\n            // (first & last IP address of each class)\n            '(?:[1-9]\\\\d?|1\\\\d\\\\d|2[01]\\\\d|22[0-3])' +\n            '(?:\\\\.(?:1?\\\\d{1,2}|2[0-4]\\\\d|25[0-5])){2}' +\n            '(?:\\\\.(?:[1-9]\\\\d?|1\\\\d\\\\d|2[0-4]\\\\d|25[0-4]))' +\n        '|' +\n            // host name\n            '(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)' +\n            // domain name\n            '(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*' +\n            // TLD identifier\n            '(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,}))' +\n            // TLD may end with dot\n            '\\\\.?' +\n        ')' +\n        // port number\n        '(?::\\\\d{2,5})?' +\n        // resource path\n        '(?:[/?#]\\\\S*)?' +\n    '$', 'i');\n\nexports.rolePermissions = [\n    'CREATE_INSTANT_INVITE',\n    'KICK_MEMBERS',\n    'BAN_MEMBERS',\n    'VIEW_AUDIT_LOG',\n    'ADMINISTRATOR',\n    'MANAGE_CHANNELS',\n    'MANAGE_GUILD',\n    'ADD_REACTIONS', // add reactions to messages\n    'VIEW_CHANNEL',\n    'SEND_MESSAGES',\n    'SEND_TTS_MESSAGES',\n    'MANAGE_MESSAGES',\n    'EMBED_LINKS',\n    'ATTACH_FILES',\n    'READ_MESSAGE_HISTORY',\n    'MENTION_EVERYONE',\n    'USE_EXTERNAL_EMOJIS', // use external emojis\n    'CONNECT', // connect to voice\n    'SPEAK', // speak on voice\n    'MUTE_MEMBERS', // globally mute members on voice\n    'DEAFEN_MEMBERS', // globally deafen members on voice\n    'MOVE_MEMBERS', // move member's voice channels\n    'USE_VAD', // use voice activity detection\n    'CHANGE_NICKNAME',\n    'MANAGE_NICKNAMES', // change nicknames of others\n    'MANAGE_ROLES',\n    'MANAGE_WEBHOOKS',\n    'MANAGE_EMOJIS',\n];\n\nexports.rolePermissionsObj = {\n    CREATE_INSTANT_INVITE: true,\n    KICK_MEMBERS: true,\n    BAN_MEMBERS: true,\n    VIEW_AUDIT_LOG: true,\n    ADMINISTRATOR: true,\n    MANAGE_CHANNELS: true,\n    MANAGE_GUILD: true,\n    ADD_REACTIONS: true, // add reactions to messages\n    VIEW_CHANNEL: true,\n    SEND_MESSAGES: true,\n    SEND_TTS_MESSAGES: true,\n    MANAGE_MESSAGES: true,\n    EMBED_LINKS: true,\n    ATTACH_FILES: true,\n    READ_MESSAGE_HISTORY: true,\n    MENTION_EVERYONE: true,\n    USE_EXTERNAL_EMOJIS: true, // use external emojis\n    CONNECT: true, // connect to voice\n    SPEAK: true, // speak on voice\n    MUTE_MEMBERS: true, // globally mute members on voice\n    DEAFEN_MEMBERS: true, // globally deafen members on voice\n    MOVE_MEMBERS: true, // move member's voice channels\n    USE_VAD: true, // use voice activity detection\n    CHANGE_NICKNAME: true,\n    MANAGE_NICKNAMES: true, // change nicknames of others\n    MANAGE_ROLES: true,\n    MANAGE_WEBHOOKS: true,\n    MANAGE_EMOJIS: true,\n};\n\nexports.textChannelPermissions = [\n    'CREATE_INSTANT_INVITE',\n    'MANAGE_CHANNEL',\n    'ADD_REACTIONS', // add reactions to messages\n    'VIEW_CHANNEL',\n    'SEND_MESSAGES',\n    'SEND_TTS_MESSAGES',\n    'MANAGE_MESSAGES',\n    'EMBED_LINKS',\n    'ATTACH_FILES',\n    'READ_MESSAGE_HISTORY',\n    'MENTION_EVERYONE',\n    'USE_EXTERNAL_EMOJIS', // use external emojis\n    'MANAGE_PERMISSIONS',\n    'MANAGE_WEBHOOKS',\n];\n\nexports.textChannelPermissionsObj = {\n    ADD_REACTIONS: true, // add reactions to messages\n    VIEW_CHANNEL: true,\n    SEND_MESSAGES: true,\n    SEND_TTS_MESSAGES: true,\n    MANAGE_MESSAGES: true,\n    EMBED_LINKS: true,\n    ATTACH_FILES: true,\n    READ_MESSAGE_HISTORY: true,\n    MENTION_EVERYONE: true,\n    USE_EXTERNAL_EMOJIS: true, // use external emojis\n    CREATE_INSTANT_INVITE: true,\n    MANAGE_CHANNEL: true,\n    MANAGE_PERMISSIONS: true,\n    MANAGE_WEBHOOKS: true,\n};\n\nexports.voiceChannelPermissions = [\n    'CONNECT', // connect to voice\n    'SPEAK', // speak on voice\n    'MUTE_MEMBERS', // globally mute members on voice\n    'DEAFEN_MEMBERS', // globally deafen members on voice\n    'MOVE_MEMBERS', // move member's voice channels\n    'USE_VAD', // use voice activity detection\n    'CREATE_INSTANT_INVITE',\n    'MANAGE_CHANNEL',\n    'MANAGE_PERMISSIONS',\n    'MANAGE_WEBHOOKS',\n];\n\nexports.voiceChannelPermissionsObj = {\n    CONNECT: true, // connect to voice\n    SPEAK: true, // speak on voice\n    MUTE_MEMBERS: true, // globally mute members on voice\n    DEAFEN_MEMBERS: true, // globally deafen members on voice\n    MOVE_MEMBERS: true, // move member's voice channels\n    USE_VAD: true, // use voice activity detection\n    CREATE_INSTANT_INVITE: true,\n    MANAGE_CHANNEL: true,\n    MANAGE_PERMISSIONS: true,\n    MANAGE_WEBHOOKS: true,\n};\n\nexports.permissionsOrder = {\n    ADMINISTRATOR: 27,\n    MANAGE_GUILD: 26,\n    MANAGE_ROLES: 25,\n    MANAGE_CHANNELS: 24,\n    MANAGE_CHANNEL: 24, // Channel\n    MANAGE_WEBHOOKS: 23,\n    MANAGE_EMOJIS: 22,\n    MANAGE_PERMISSIONS: 22, // Channel\n    VIEW_AUDIT_LOG: 21,\n    MENTION_EVERYONE: 20,\n    BAN_MEMBERS: 19,\n    KICK_MEMBERS: 18,\n    MOVE_MEMBERS: 17,\n    DEAFEN_MEMBERS: 16,\n    MUTE_MEMBERS: 15,\n    MANAGE_MESSAGES: 14,\n    MANAGE_NICKNAMES: 13,\n    USE_EXTERNAL_EMOJIS: 12,\n    ATTACH_FILES: 11,\n    SEND_TTS_MESSAGES: 10,\n    ADD_REACTIONS: 9,\n    EMBED_LINKS: 8,\n    CHANGE_NICKNAME: 7,\n    USE_VAD: 6,\n    SPEAK: 5,\n    CONNECT: 4,\n    CREATE_INSTANT_INVITE: 3,\n    SEND_MESSAGES: 2,\n    READ_MESSAGE_HISTORY: 1,\n    VIEW_CHANNEL: 0,\n};\n\nexports.permRating = [\n    ['ADMINISTRATOR', 100],\n    ['MANAGE_GUILD', 90],\n    ['MANAGE_ROLES', 80],\n    ['MANAGE_CHANNELS', 70],\n    ['MANAGE_EMOJIS', 60],\n    ['MENTION_EVERYONE', 50],\n    ['VIEW_AUDIT_LOG', 50],\n    ['BAN_MEMBERS', 40],\n    ['KICK_MEMBERS', 30],\n    ['MANAGE_MESSAGES', 20],\n    ['MANAGE_NICKNAMES', 20],\n    ['MOVE_MEMBERS', 20],\n    ['ATTACH_FILES', 10],\n    ['ADD_REACTIONS', 10],\n    ['SEND_MESSAGES', 10],\n];\n\nexports.replaceAll = (str, search, replacement) => str.split(search).join(replacement);\n\nfunction getURLChecker() {\n    const SCHEME = '[a-z\\\\d.-]+://';\n    const IPV4 = '(?:(?:[0-9]|[1-9]\\\\d|1\\\\d{2}|2[0-4]\\\\d|25[0-5])\\\\.){3}(?:[0-9]|[1-9]\\\\d|1\\\\d{2}|2[0-4]\\\\d|25[0-5])';\n    const HOSTNAME = \"(?:(?:[^\\\\s!@#$%^&*()_=+[\\\\]{}\\\\\\\\|;:'\\\",.<>/?]+)\\\\.)+\";\n    const TLD = `(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br\n    |bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu\n    |fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq\n    |ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi\n    |mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|place|pl|pm|pn\n    |pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm\n    |tn|to|tp|trade|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wiki|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f\n    |xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)`;\n\n    const HOST_OR_IP = `(?:${HOSTNAME}${TLD}|${IPV4})`;\n    const PATH = '(?:[;/][^#?<>\\\\s]*)?';\n    const QUERY_FRAG = '(?:\\\\?[^#<>\\\\s]*)?(?:#[^<>\\\\s]*)?';\n    const URI1 = `\\\\b${SCHEME}[^<>\\\\s]+`;\n    const URI2 = `\\\\b${HOST_OR_IP}${PATH}${QUERY_FRAG}(?!\\\\w)`;\n\n    const MAILTO = 'mailto:';\n    const EMAIL = `(?:${MAILTO})?[a-z0-9!#$%&'*+/=?^_\\`{|}~-]+(?:\\\\.[a-z0-9!#$%&'*+/=?^_\\`{|}~-]+)*@${HOST_OR_IP}${QUERY_FRAG}(?!\\\\w)`;\n\n    const URI_RE = new RegExp(`(?:${URI1}|${URI2}|${EMAIL})`, 'ig');\n    const SCHEME_RE = new RegExp(`^${SCHEME}`, 'i');\n\n    const quotes = {\n        \"'\": '`',\n        '>': '<',\n        ')': '(',\n        ']': '[',\n        '}': '{',\n        '»': '«',\n        '›': '‹',\n    };\n\n    const defaultOptions = {\n        callback(text, href) {\n            return href || null;\n        },\n        punct_regexp: /(?:[!?.,:;'\"]|(?:&|&amp;)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/,\n    };\n\n    function checkURLs(txtParam, optionsParam) {\n        let txt = exports.replaceAll(txtParam, '\\\\', '');\n        txt = exports.replaceAll(txt, '*', '');\n        txt = exports.replaceAll(txt, '_', '');\n\n        if (txt.includes('roblox')) Util.log(txt);\n\n        const options = optionsParam || {};\n\n        // Temp variables.\n        let arr;\n        let i;\n        let link;\n        let href;\n\n        // Output HTML.\n        // const html = '';\n\n        // Store text / link parts, in order, for re-combination.\n        const parts = [];\n\n        // Used for keeping track of indices in the text.\n        let idxPrev;\n        let idxLast;\n        let idx;\n        let linkLast;\n\n        // Used for trimming trailing punctuation and quotes from links.\n        let matchesBegin;\n        let matchesEnd;\n        let quoteBegin;\n        let quoteEnd;\n\n        // Initialize options.\n        for (i of Object.keys(defaultOptions)) {\n            if (options[i] == null) {\n                options[i] = defaultOptions[i];\n            }\n        }\n\n        const inRep = (a) => {\n            idxLast -= a.length;\n            return '';\n        };\n\n        // Find links.\n        while (arr = URI_RE.exec(txt)) {\n            link = arr[0];\n            idxLast = URI_RE.lastIndex;\n            idx = idxLast - link.length;\n\n            // Not a link if preceded by certain characters.\n            if (/[/:]/.test(txt.charAt(idx - 1))) {\n                continue;\n            }\n\n            // Trim trailing punctuation.\n            do {\n                // If no changes are made, we don't want to loop forever!\n                linkLast = link;\n\n                quoteEnd = link.substr(-1);\n                quoteBegin = quotes[quoteEnd];\n\n                // Ending quote character?\n                if (quoteBegin) {\n                    matchesBegin = link.match(new RegExp(`\\\\${quoteBegin}(?!$)`, 'g'));\n                    matchesEnd = link.match(new RegExp(`\\\\${quoteEnd}`, 'g'));\n\n                    // If quotes are unbalanced, remove trailing quote character.\n                    if ((matchesBegin ? matchesBegin.length : 0) < (matchesEnd ? matchesEnd.length : 0)) {\n                        link = link.substr(0, link.length - 1);\n                        idxLast--;\n                    }\n                }\n\n                // Ending non-quote punctuation character?\n                if (options.punct_regexp) {\n                    link = link.replace(options.punct_regexp, inRep);\n                }\n            } while (link.length && link !== linkLast);\n\n            href = link;\n\n            // Add appropriate protocol to naked links.\n            if (!SCHEME_RE.test(href)) {\n                const origHref = href;\n                if (href.indexOf('@') != -1) {\n                    if (!href.indexOf(MAILTO)) {\n                        href = '';\n                    } else {\n                        href = MAILTO;\n                    }\n                } else if (!href.indexOf('irc.')) {\n                    href = 'irc://';\n                } else if (!href.indexOf('ftp.')) {\n                    href = 'ftp://';\n                } else {\n                    href = 'http://';\n                }\n                href += origHref;\n            }\n\n            // Push preceding non-link text onto the array.\n            if (idxPrev !== idx) {\n                parts.push([txt.slice(idxPrev, idx)]);\n                idxPrev = idxLast;\n            }\n\n            // Push massaged link onto the array\n            parts.push([link, href]);\n        }\n\n        // Push remaining non-link text onto the array.\n        parts.push([txt.substr(idxPrev)]);\n\n        // Process the array items.\n        const URLs = [];\n\n        for (i = 0; i < parts.length; i++) {\n            const result = options.callback.apply('nooone', parts[i]);\n            if (result) {\n                URLs.push(result);\n            }\n        }\n\n        return URLs;\n    }\n\n    return checkURLs;\n}\n\nexports.checkURLs = getURLChecker();\n\nfunction forceAddRolesInner(guild, sendRole, iterNum = 1) {\n    let didError = false;\n\n    guild.members.forEach((member) => {\n        if (!exports.hasRole(member, sendRole)) {\n            member.addRole(sendRole)\n                .then(() => Util.log(`Assigned role to ${exports.getName(member)}`))\n                .catch((error) => {\n                    didError = true;\n                    Util.log(`[E_InitRoles] addRole: ${error}`);\n                });\n        }\n    });\n\n    if (!didError || iterNum >= 10) return;\n\n    setTimeout(() => {\n        forceAddRolesInner(guild, sendRole, iterNum + 1);\n    }, 1000 * 4);\n}\n\nfunction forceAddRoles(guild, sendRole) {\n    forceAddRolesInner(guild, sendRole);\n}\n\nexports.initRoles = async function (sendRole, guild, guildChannel) {\n    try {\n        await Promise.all(guild.roles.map(async (role) => {\n            if (role.name !== 'SendMessages' && role.hasPermission('SEND_MESSAGES', null, false)) {\n                try {\n                    await role.setPermissions(role.permissions & (~2048));\n                } catch (err) {\n                    console.log('[RolePermRem]', err);\n                }\n            }\n        }));\n\n        await Promise.all(guild.channels.map(async (channel) => {\n            const deniesMessages = channel.permissionOverwrites.some(channelPerm => channelPerm.type === 'role' && channelPerm.denied.toArray(false).includes('SEND_MESSAGES'));\n\n            if (deniesMessages) return;\n\n            const newOverwrites = channel.permissionOverwrites.map((channelPerm) => {\n                // const permObj = channelPerm.type === 'role' ? guild.roles.get(channelPerm.id) : guild.members.get(channelPerm.id);\n\n                const allowed = channelPerm.allowed.toArray(false).filter(perm => perm !== 'SEND_MESSAGES');\n                const denied = channelPerm.denied.toArray(false).filter(perm => perm !== 'SEND_MESSAGES');\n\n                return {\n                    allowed,\n                    denied,\n                    id: channelPerm.id,\n                    type: channelPerm.type,\n                };\n            });\n\n            channel.replacePermissionOverwrites({ overwrites: newOverwrites }).catch((err) => {\n                console.log('[RepPermOverwrites]', err);\n            });\n        }));\n\n        if (guildChannel) {\n            Util.sendDescEmbed(guildChannel, 'Setup VaeBot', 'Server roles and channels have been setup appropriately', null, null, null);\n        }\n    } catch (err) {\n        console.log('InitRolesInner Error:', err);\n    }\n\n    forceAddRoles(guild, sendRole);\n};\n\nexports.arrayToObj = function (arr) {\n    const obj = {};\n    for (let i = 0; i < arr.length; i++) {\n        const val = arr[i];\n        obj[val] = true;\n    }\n    return obj;\n};\n\nexports.capitalize = function (strParam) {\n    let str = strParam;\n    str = String(str);\n    return str.charAt(0).toUpperCase() + str.slice(1);\n};\n\nexports.runLua = function (args, channel) {\n    // args = \"os=nil;io=nil;debug=nil;package=nil;require=nil;loadfile=nil;dofile=nil;collectgarbage=nil;\" + args;\n    const tagNum = Math.floor((new Date()).getTime());\n    const fileDir = `/tmp/script_${tagNum}.lua`;\n    FileSys.writeFile(fileDir, args, (err) => {\n        if (err) {\n            Util.log(`Script creation error: ${err}`);\n            Util.print(channel, `Script creation error: ${err}`);\n        }\n        Exec(`lua ${fileDir}`, (error, stdoutParam, stderr) => {\n            let stdout = stdoutParam;\n            if (!stdout) stdout = '';\n            const safeOut = Util.safe(stdout);\n            // var safeErr = Util.safe(stderr);\n            const outStr = [];\n            if (error) {\n                outStr.push('**Execution error:**');\n                outStr.push('```');\n                Util.log(`Execution Error: ${stderr}`);\n                outStr.push(error);\n                outStr.push('```');\n            } else {\n                if (safeOut.length <= 1980) {\n                    outStr.push('**Output:**');\n                    outStr.push('```');\n                    outStr.push(safeOut);\n                    outStr.push('```');\n                } else {\n                    const options = {\n                        url: 'https://hastebin.com/documents',\n                        method: 'POST',\n                        headers: { 'Content-Type': 'text/plain' },\n                        body: stdout,\n                    };\n                    index.Request(options, (error2, response, bodyParam) => {\n                        const body = JSON.parse(bodyParam);\n                        if (error2 || !body || !body.key) {\n                            Util.print(channel, 'Hastebin upload error:', error2);\n                        } else {\n                            Util.print(channel, 'Output:', `https://hastebin.com/raw/${body.key}`);\n                        }\n                    });\n                }\n                if (stderr) {\n                    outStr.push('**Lua Error:**');\n                    outStr.push('```');\n                    Util.log(`Lua Error: ${stderr}`);\n                    outStr.push(stderr);\n                    outStr.push('```');\n                }\n            }\n            Util.print(channel, outStr.join('\\n'));\n            FileSys.unlink(fileDir);\n        });\n    });\n};\n\nexports.doXOR = function (a, b) {\n    const result = ((a == 1 || b == 1) && !(a == 1 && b == 1)) ? 1 : 0;\n    return result;\n};\n\nexports.capitalize2 = function (strParam, repUnder) {\n    let str = String(strParam);\n    if (repUnder) str = exports.replaceAll(str, '_', ' ');\n    str = str.replace(/[0-9a-z]+/ig, (txt) => { Util.log(txt); return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });\n    return str;\n};\n\nexports.boolToAns = function (bool) {\n    const result = bool ? 'Yes' : 'No';\n    return result;\n};\n\nexports.safe = function (str) {\n    if (typeof (str) === 'string') return str.replace(/`/g, '\\\\`').replace(/@/g, '@­');\n    return undefined;\n};\n\nexports.safe2 = function (str) {\n    if (typeof (str) === 'string') return str.replace(/`/g, '\\\\`');\n    return undefined;\n};\n\nexports.safeEveryone = function (str) {\n    if (typeof (str) === 'string') {\n        const newStr = str.replace(/@everyone/g, '@​everyone');\n        return newStr.replace(/@here/g, '@​here');\n    }\n    return undefined;\n};\n\nexports.fix = str => (`\\`${exports.safe(str)}\\``);\n\nexports.toFixedCut = (num, decimals) => Number(num.toFixed(decimals)).toString();\n\nexports.grabFiles = function (filePath, filter = () => true) {\n    const dirFiles = FileSys.readdirSync(filePath);\n    let fullFiles = [];\n    dirFiles.forEach((file) => {\n        const fileData = FileSys.lstatSync(`${filePath}${file}`);\n        if (fileData.isDirectory()) {\n            const toAdd = exports.grabFiles(`${filePath}${file}/`, filter);\n            fullFiles = fullFiles.concat(toAdd);\n        } else if (filter(file)) {\n            fullFiles.push(`${filePath}${file}`);\n        }\n    });\n    return fullFiles;\n};\n\nexports.bulkRequire = function (filePath) {\n    const bulkFiles = exports.grabFiles(filePath, file => file.endsWith('.js'));\n\n    for (const data of Object.values(bulkFiles)) {\n        exports.pathRequire(data);\n    }\n};\n\nexports.pathRequire = function (filePath) {\n    const file = Path.resolve(filePath);\n    delete require.cache[require.resolve(file)];\n\n    const fileData = require(filePath);\n\n    const dirName = /(\\w+)[/\\\\]\\w+\\.js$/.exec(file)[1];\n\n    if (dirName && has.call(index.commandTypes, dirName)) {\n        const cmdTypes = index.commandTypes;\n        for (const [commandType, commandKey] of Object.entries(cmdTypes)) {\n            if (commandKey !== 'null') {\n                if (commandType === dirName) {\n                    fileData[2][commandKey] = true;\n                } else {\n                    fileData[2][commandKey] = false;\n                }\n            }\n        }\n    }\n};\n\nexports.checkStaff = function (guild, member) {\n    if (guild == null || member == null) {\n        Util.log(`>>> CHECK STAFF ISSUE: ${guild} ${member} <<<`);\n    }\n\n    if (member.id === vaebId || member.id === selfId || member.id === guild.ownerID) return true;\n    if (member.hasPermission('ADMINISTRATOR')) return true;\n    if (member.id === '126710973737336833') return true;\n    const speakerRoles = member.roles;\n    if (!speakerRoles) return false;\n    // if (exports.getPermRating(guild, member) >= 30) return true;\n    return speakerRoles.some(role => /\\bstaff\\b/i.test(role.name) || role.name === 'Owner/Seller' || role.name === 'Bot Admin'\n        || role.name === 'Moderator' || role.name.includes('Head Mod') || role.name === 'Trial Moderator' || /OP$/.test(role.name));\n};\n\nexports.commandFailed = function (channel, speaker, tag, message) {\n    if (message == null) {\n        message = tag;\n        tag = null;\n    }\n\n    const tagMessage = tag ? `[${tag}] ` : '';\n\n    if (channel != null) {\n        exports.sendEmbed(channel, `${tagMessage}Command Failed`, message, exports.makeEmbedFooter(speaker), null, colGreen, null);\n    } else {\n        Util.log(`${tagMessage}[Command_Failed] ${speaker.id}: ${message}`);\n    }\n\n    return false;\n};\n\nexports.getRandomInt = function (minParam, maxParam) { // inclusive, exclusive\n    maxParam++; // inclusive, inclusive\n    const min = Math.ceil(minParam);\n    const max = Math.floor(maxParam);\n    return Math.floor(Math.random() * (max - min)) + min;\n};\n\n/* function chunkStringLine(str, size) {\n    var numChunks = Math.ceil(str.length / size);\n    var chunks = [];\n\n    for (var i = 0, o = 0; i < numChunks; ++i, o += size) {\n        chunks[i] = str.substr(o, size);\n    }\n\n    var chunkLength = chunks.length;\n\n    if (numChunks > 1) {\n        for (var i = 0; i < chunkLength; i++) {\n            var nowChunk = chunks[i];\n            var lastLine = nowChunk.lastIndexOf(\"\\n\");\n            if (lastLine >= 0) {\n                var nowChunkMsg = nowChunk.substring(0, lastLine);\n                chunks[i] = nowChunkMsg;\n                var nextChunkMsg = nowChunk.substring(lastLine+1);\n                if (chunks[i+1] == null) {\n                    if (nextChunkMsg == \"\" || nextChunkMsg == \"\\n\" || nextChunkMsg == \"```\" || nextChunkMsg == \"\\n```\") break;\n                    chunks[i+1] = \"\";\n                }\n                chunks[i+1] = nextChunkMsg + chunks[i+1];\n            }\n        }\n    }\n\n    return chunks;\n} */\n\n/*\n\n-Chunk string into sets of 2k chars\n-For each chunk\n    -If msg includes newline and first character of next message isn't newline\n        -Find last newline (unless start of next chunk is newline in which case use the if statement below), where the character before it isn't a codeblock\n        -Copy everything after the newline to the start of the next chunk\n        -Set msg to everything before the newline\n    -If number of code blocks is odd and there are non-whitespace characters after the last codeblock\n        -Add a codeblock to the end of the chunk\n    -If number of characters is above 2000\n        -Find last newline under (or equal) the 2001 character mark, where the character before it isn't a codeblock\n        -If no newline\n            -Append a newline as <= 2001st character (not between code blocks if possible)\n        -Copy everything after the newline (but before the code block), then append it (with an extra newline on the end) to the start of the next chunk\n        -Cut the chunk to everything before the newline\n\n*/\n\nexports.isObject = function (val) { // Or array\n    if (val == null) return false;\n    return (typeof (val) === 'object');\n};\n\nexports.cloneObj = function (obj, fixBuffer) {\n    let copy;\n\n    if (obj == null || typeof (obj) !== 'object') return obj;\n\n    if (obj instanceof Date) {\n        copy = new Date();\n        copy.setTime(obj.getTime());\n        return copy;\n    }\n\n    if (obj instanceof Array) {\n        copy = [];\n        const len = obj.length;\n        for (let i = 0; i < len; i++) {\n            copy[i] = exports.cloneObj(obj[i], fixBuffer);\n        }\n        return copy;\n    }\n\n    if (fixBuffer && obj instanceof Buffer) {\n        return obj.readUIntBE(0, 1);\n    }\n\n    if (obj instanceof Object && !(obj instanceof Buffer)) {\n        copy = {};\n        for (const [attr, objAttr] of Object.entries(obj)) {\n            copy[attr] = exports.cloneObj(objAttr, fixBuffer);\n        }\n        return copy;\n    }\n\n    console.log(\"Couldn't clone obj, returning real value\");\n\n    return obj;\n};\n\nexports.cloneObjDepth = function (obj, maxDepth = 1, nowDepth = 0) {\n    let copy;\n\n    if (obj == null || typeof (obj) !== 'object') return obj;\n\n    if (obj instanceof Date) {\n        copy = new Date();\n        copy.setTime(obj.getTime());\n        return copy;\n    }\n\n    if (obj instanceof Array) {\n        const len = obj.length;\n\n        if (nowDepth >= maxDepth && len > 0) return '[Array]';\n\n        copy = [];\n        for (let i = 0; i < len; i++) {\n            copy[i] = exports.cloneObjDepth(obj[i], maxDepth, nowDepth + 1);\n        }\n\n        return copy;\n    }\n\n    if (obj instanceof Object && !(obj instanceof Buffer)) {\n        const entries = Object.entries(obj);\n\n        if (nowDepth >= maxDepth && entries.length > 0) return '[Object]';\n\n        copy = {};\n        for (const [attr, objAttr] of entries) {\n            copy[attr] = exports.cloneObjDepth(objAttr, maxDepth, nowDepth + 1);\n        }\n\n        return copy;\n    }\n\n    console.log(\"Couldn't clone obj, returning real value\");\n\n    return obj;\n};\n\nconst elapseTimeTags = {};\n\nexports.throwErr = function () {\n    setTimeout(() => {\n        throw new Error('err');\n    }, 1000);\n};\n\nexports.getElapsed = function (tag, remove) {\n    let elapsed;\n\n    if (elapseTimeTags[tag] != null) {\n        const startTimeData = elapseTimeTags[tag];\n        const elapsedTimeData = process.hrtime(startTimeData); // Seconds, Nanoseconds (Seconds * 1e9)\n        elapsed = (elapsedTimeData[0] * 1e3) + Number((elapsedTimeData[1] / 1e6).toFixed(3));\n    }\n\n    if (remove) {\n        elapseTimeTags[tag] = null;\n        delete elapseTimeTags[tag]; // Remove time storage\n    } else {\n        elapseTimeTags[tag] = process.hrtime(); // Mark the start time\n    }\n\n    return elapsed;\n};\n\nexports.formatTime = function (time) {\n    let timeStr;\n    let formatStr;\n\n    const numSeconds = exports.round(time / 1000, 0.1);\n    const numMinutes = exports.round(time / (1000 * 60), 0.1);\n    const numHours = exports.round(time / (1000 * 60 * 60), 0.1);\n    const numDays = exports.round(time / (1000 * 60 * 60 * 24), 0.1);\n    const numWeeks = exports.round(time / (1000 * 60 * 60 * 24 * 7), 0.1);\n    const numMonths = exports.round(time / (1000 * 60 * 60 * 24 * 30.42), 0.1);\n    const numYears = exports.round(time / (1000 * 60 * 60 * 24 * 365.2422), 0.1);\n\n    if (numSeconds < 1) {\n        timeStr = exports.toFixedCut(time, 0);\n        formatStr = `${timeStr} millisecond`;\n    } else if (numMinutes < 1) {\n        timeStr = exports.toFixedCut(numSeconds, 1);\n        formatStr = `${timeStr} second`;\n    } else if (numHours < 1) {\n        timeStr = exports.toFixedCut(numMinutes, 1);\n        formatStr = `${timeStr} minute`;\n    } else if (numDays < 1) {\n        timeStr = exports.toFixedCut(numHours, 1);\n        formatStr = `${timeStr} hour`;\n    } else if (numWeeks < 1) {\n        timeStr = exports.toFixedCut(numDays, 1);\n        formatStr = `${timeStr} day`;\n    } else if (numMonths < 1) {\n        timeStr = exports.toFixedCut(numWeeks, 1);\n        formatStr = `${timeStr} week`;\n    } else if (numYears < 1) {\n        timeStr = exports.toFixedCut(numMonths, 1);\n        formatStr = `${timeStr} month`;\n    } else {\n        timeStr = exports.toFixedCut(numYears, 1);\n        formatStr = `${timeStr} year`;\n    }\n\n    if (timeStr !== '1') formatStr += 's';\n\n    return formatStr;\n};\n\nexports.chunkString = function (str, maxChars) {\n    const iterations = Math.ceil(str.length / maxChars);\n    const chunks = new Array(iterations);\n    for (let i = 0, j = 0; i < iterations; ++i, j += maxChars) chunks[i] = str.substr(j, maxChars);\n    return chunks;\n};\n\nexports.cutStringSafe = function (msg, postMsg, lastIsOpener) { // Tries to cut the string along a newline\n    let lastIndex = msg.lastIndexOf('\\n');\n    if (lastIndex < 0) return [msg, postMsg];\n    let preCut = msg.substring(0, lastIndex);\n    let postCut = msg.substring(lastIndex + 1);\n    const postHasBlock = postCut.includes('```');\n    if (postHasBlock && !lastIsOpener) { // If postCut is trying to pass over a code block (not allowed) might as well just cut after the code block (as long as it's a closer)\n        lastIndex = msg.lastIndexOf('```');\n        preCut = msg.substring(0, lastIndex + 3);\n        postCut = msg.substring(lastIndex + 3);\n    } else {\n        const strEnd1 = preCut.substr(Math.max(preCut.length - 3, 0), 3);\n        const strEnd2 = preCut.substr(Math.max(preCut.length - 4, 0), 4);\n        if (postHasBlock || (lastIsOpener && (strEnd1 === '```' || strEnd2 === '``` ' || strEnd2 === '```\\n'))) { // If post is triyng to pass over opener or last section of preCut is an opener\n            return [msg, postMsg];\n        }\n    }\n    return [preCut, postCut + postMsg];\n};\n\nexports.fixMessageLengthNew = function (msgParam) {\n    const argsFixed = exports.chunkString(msgParam, exports.charLimit); // Group string into sets of 2k chars\n    const minusLimit = exports.charLimit - 4;\n    // argsFixed.forEach(o => Util.log(\"---\\n\" + o));\n    let totalBlocks = 0; // Total number of *user created* code blocks come across so far (therefore if the number is odd then code block is currently open)\n    for (let i = 0; i < argsFixed.length; i++) {\n        let passOver = ''; // String to pass over as the start of the next chunk\n        let msg = argsFixed[i];\n        const numBlock = (msg.match(/```/g) || []).length; // Number of user created code blocks in this chunk\n        if (totalBlocks % 2 == 1) msg = `\\`\\`\\`\\n${msg}`; // If code block is currently open then this chunk needs to be formatted\n        totalBlocks += numBlock; // The user created code blocks may close/open new code block (don't need to include added ones because they just account for separate messages)\n        let lastIsOpener = totalBlocks % 2 == 1; // Checks whether the last code block is an opener or a closer\n        if (lastIsOpener && msg.length > minusLimit) { // If the chunk ends with the code block still open then it needs to be auto-closed so the chunk needs to be shortened so it can fit\n            passOver = msg.substring(minusLimit);\n            msg = msg.substr(0, minusLimit);\n            const numPass = (passOver.match(/```/g) || []).length; // If we end up passing over code blocks whilst trying to shorten the string, we need to account for the new amount\n            totalBlocks -= numPass;\n            if (numPass % 2 == 1) lastIsOpener = false;\n        }\n        const nextMsg = passOver + (argsFixed[i + 1] != null ? argsFixed[i + 1] : ''); // Message for next chunk (or empty string if none)\n        if (nextMsg !== '' && nextMsg[0] !== '\\n' && msg.includes('\\n')) { // If start of next chunk is a newline then can just leave the split as it is now (same goes for this chunk having no newlines)\n            const cutData = exports.cutStringSafe(msg, '', lastIsOpener);\n            msg = cutData[0];\n            passOver = cutData[1] + passOver;\n        }\n        if (lastIsOpener) msg += '\\n```'; // Close any left over code blocks (and re open on next chunk if they continue)\n        argsFixed[i] = msg;\n        if (passOver.length > 0) { // Whether any text actually needs to be passed\n            if (argsFixed[i + 1] == null) argsFixed[i + 1] = ''; // Create new chunk if this is the last one\n            argsFixed[i + 1] = passOver + argsFixed[i + 1];\n        }\n    }\n    return argsFixed;\n};\n\n/* function fixMessageLength(msg) {\n    var argsFixed = chunkStringLine(msg, 2000);\n    var argsLength = argsFixed.length;\n    for (var i = 0; i < argsFixed.length; i++) {\n        var passOver = \"\";\n        var msg = argsFixed[i];\n        //Util.log(\"Original message length: \" + msg.length);\n        if (msg.length > 1996) {\n            passOver = msg.substring(1996);\n            msg = msg.substring(0, 1996);\n            //Util.log(\"passStart orig: \" + passOver.length);\n            var lastLine = msg.lastIndexOf(\"\\n\");\n            if (lastLine >= 5) {\n                var msgEnd = lastLine;\n                var passStart = msgEnd+1;\n                passOver = msg.substring(passStart) + passOver;\n                msg = msg.substring(0, msgEnd);\n                //Util.log(\"passOver: \" + passOver.length);\n                //Util.log(\"msg: \" + msg.length);\n                //Util.log(\"lastLine: \" + lastLine);\n            }\n        }\n        var numBlock = (msg.match(/```/g) || []).length;\n        if (numBlock % 2 == 1) {\n            passOver = \"```\\n\" + passOver;\n            msg = msg + \"\\n```\";\n        }\n        argsFixed[i] = msg;\n        //Util.log(\"Message length: \" + msg.length);\n        //Util.log(\"Pass Over: \" + passOver.length);\n        if (passOver != \"\" && (argsFixed[i+1] != null || passOver != \"```\\n\")) {\n            if (argsFixed[i+1] == null) {\n                //Util.log(\"Created new print block extender\")\n                argsFixed[i+1] = \"\";\n            }\n            argsFixed[i+1] = passOver + argsFixed[i+1];\n        }\n    }\n\n    return argsFixed;\n} */\n\nexports.splitMessagesOld = function (messages) {\n    const fixed = exports.fixMessageLengthNew(messages.join(' '));\n    return fixed;\n};\n\nexports.escapeRegExp = function (str) {\n    return str.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n};\n\nfunction getMatchesWithBlock(str, matchChars, blockChars, useInside) { // Gets all matches of a substring that are in/out of a code block\n    const pattern = new RegExp(exports.escapeRegExp(blockChars), 'g');\n    let result;\n\n    let numMatches = 0;\n    let strPointer = 0;\n    let newStr = '';\n\n    while (result = pattern.exec(str)) {\n        numMatches++;\n        if (useInside) {\n            if (numMatches % 2 == 1) { // Open block\n                newStr += '.'.repeat(result.index - strPointer);\n                strPointer = result.index;\n            } else { // Close block (Store data)\n                newStr += '.'.repeat(blockChars.length) + str.substring(strPointer + blockChars.length, result.index);\n                strPointer = result.index;\n            }\n        } else {\n            if (numMatches % 2 == 1) { // Open block (Store data)\n                newStr += str.substring(strPointer, result.index);\n                strPointer = result.index;\n            } else { // Close block\n                newStr += '.'.repeat(result.index - strPointer + blockChars.length);\n                strPointer = result.index + blockChars.length;\n            }\n        }\n    }\n\n    if (useInside) {\n        newStr += '.'.repeat(str.length - strPointer);\n    } else {\n        newStr += str.substring(strPointer);\n    }\n\n    if (newStr.length != str.length) throw new Error('[E_GetMatchesWithBlock] Failed because the output string didn\\'t match input string length');\n\n    return (newStr.match(new RegExp(exports.escapeRegExp(matchChars), 'g')) || []);\n}\n\n/*\n\n    chunkMessage\n        -Split messages into chunks <= exports.charLimit characters\n        -Chunks retain format (`, ``, ```, *, **, ***, _, __, ___) and thus account for fitting in format characters\n        -Message splitting focuses on retaining format over reducing number of chunks:\n            End ```\n            Newline + Newline\n            Newline + Format character(s)\n            Newline\n            Space\n            Any\n        -Make it so that if last chunk continued string onto next chunk, next chunk cuts at end of that string\n*/\n\nconst formatSets = [\n    ['___', '__'],\n    ['***', '**', '*'],\n    ['```', '``', '`'],\n];\n\nconst splitSets = [ // pivot: -1 = Split Start, 0 = Remove, 1 = Split End\n    { chars: '```', pivot: 1 }, // Only applies to end ```\n    { chars: '\\n\\n', pivot: 0 },\n    { chars: '\\n', pivot: 0 },\n    { chars: ' ', pivot: 0 },\n];\n\nconst leaveExtra = formatSets.reduce((a, b) => a.concat(b)).length * 2;\n\nfunction chunkMessage(msg) {\n    const origChunks = [msg];\n    let content = msg;\n    let appendBeginning = [];\n\n    const baseChunkSize = exports.charLimit - leaveExtra;\n\n    for (let i = 0; content; ++i, content = origChunks[i]) {\n        for (let j = 0; j < appendBeginning.length; j++) {\n            content = appendBeginning[j] + content;\n        }\n\n        if (content.length < exports.charLimit) {\n            origChunks[i] = content;\n            break;\n        }\n\n        let chunk = content.substr(0, baseChunkSize);\n        let leftOver;\n\n        appendBeginning = [];\n\n        for (let j = 0; j < splitSets.length; j++) {\n            const splitSet = splitSets[j];\n            const splitChars = splitSet.chars;\n            const splitType = splitSet.pivot;\n\n            let pivotStart = chunk.lastIndexOf(splitChars); // exclusive\n            let pivotEnd = pivotStart; // inclusive\n\n            if (pivotStart == -1) continue;\n\n            if (splitType == 1) { // Split End\n                pivotStart += splitChars.length;\n                pivotEnd = pivotStart;\n            } else if (splitType == 0) { // Remove\n                pivotEnd += splitChars.length;\n            }\n\n            let chunkTemp = chunk.substring(0, pivotStart);\n\n            if (splitChars == '```') { // Has to be closing a block\n                const numSets = (chunkTemp.match(new RegExp(exports.escapeRegExp(splitChars), 'g')) || []).length;\n                if (numSets % 2 == 1) {\n                    if (numSets == 1) continue;\n                    pivotStart = chunk.substring(0, pivotStart - splitChars.length).lastIndexOf(splitChars);\n                    if (pivotStart == -1) continue;\n                    pivotStart += splitChars.length;\n                    pivotEnd = pivotStart;\n                    chunkTemp = chunk.substring(0, pivotStart);\n                }\n            }\n\n            if (chunkTemp.length <= leaveExtra) continue;\n\n            Util.log(`Split on ${splitChars} @ ${pivotStart} @ ${pivotEnd}`);\n\n            chunk = chunkTemp;\n            leftOver = content.substr(pivotEnd);\n\n            /* if (i == 1) {\n                Util.log(chunkTemp);\n                Util.log('---');\n                Util.log(leftOver);\n            } */\n\n            break;\n        }\n\n        if (leftOver == null) {\n            Util.log('Split on last');\n            leftOver = content.substr(baseChunkSize);\n        }\n\n        for (let j = 0; j < formatSets.length; j++) {\n            const formatSet = formatSets[j];\n\n            for (let k = 0; k < formatSet.length; k++) {\n                const formatChars = formatSet[k];\n                const numSets = getMatchesWithBlock(chunk, formatChars, '```', false).length; // Should really only be counting matches not inside code blocks\n\n                if (numSets % 2 == 1) {\n                    chunk += formatChars;\n                    appendBeginning.push(formatChars);\n                    break;\n                }\n            }\n        }\n\n        if (chunk.substr(chunk.length - 3, 3) == '```') appendBeginning.push('​\\n');\n\n        origChunks[i] = chunk;\n\n        if (leftOver && leftOver.length > 0) origChunks.push(leftOver);\n    }\n\n    return origChunks;\n}\n\nexports.splitMessages = function (messages) {\n    return chunkMessage(messages.join(' '));\n};\n\nexports.print = function (channel, ...args) {\n    const messages = exports.splitMessages(args);\n    const promises = [];\n    for (let i = 0; i < messages.length; i++) {\n        const msg = messages[i];\n        // Util.log(`${channel.name}: ${msg.length}`);\n        const msgPromise = channel.send(msg);\n        msgPromise.catch((err) => {\n            console.log('[PRINT_CATCH]', err);\n        });\n        promises.push(msgPromise);\n    }\n    return Promise.all(promises);\n};\n\nconst printPromise = async (channel, msg, resolveData, resolveErr) => {\n    try {\n        const data = await channel.send(msg);\n        resolveData.push(data);\n    } catch (err) {\n        resolveErr.push(err);\n        console.log('[PRINT_CATCH]', err);\n    }\n};\n\nexports.print = async function (channel, ...args) {\n    const messages = Util.splitMessages(args);\n    const resolveData = [];\n    const resolveErr = [];\n    const promises = [];\n    for (let i = 0; i < messages.length; i++) {\n        const msg = messages[i];\n        promises.push(printPromise(channel, msg, resolveData, resolveErr));\n    }\n    try {\n        await Promise.all(promises);\n        if (resolveData.length > 0 && resolveErr.length === 0) return resolveData[0];\n        return resolveErr[0];\n    } catch (err) {\n        throw new Error(\"SOMETHING WENT WRONG WITH PRINT'S CODE:\", err);\n    }\n};\n\nexports.sortPerms = function (permsArr) {\n    permsArr.sort((a, b) => exports.permissionsOrder[b] - exports.permissionsOrder[a]);\n};\n\nexports.getGuildRoles = function (guild) {\n    return Array.from(guild.roles.values()).sort((a, b) => b.position - a.position); // From highest to lowest\n};\n\nexports.getName = function (userResolvable) {\n    if (userResolvable == null) return null;\n    if (typeof userResolvable === 'string') return userResolvable;\n    return Util.isMember(userResolvable) ? userResolvable.user.username : userResolvable.username;\n};\n\nexports.getMostName = function (userResolvable) {\n    if (userResolvable == null) return null;\n    if (typeof userResolvable === 'string') return userResolvable;\n    const username = exports.getName(userResolvable);\n    const discrim = Util.isMember(userResolvable) ? userResolvable.user.discriminator : userResolvable.discriminator;\n    return `${username}#${discrim}`;\n};\n\nexports.getFullName = function (userResolvable, strict) {\n    if (userResolvable == null) return strict ? null : 'null'; // TODO: Make strict default at some point\n    if (typeof userResolvable === 'string') return userResolvable;\n    const mostName = exports.getMostName(userResolvable);\n    return `${mostName} (${userResolvable.id})`;\n};\n\nexports.getDisplayName = function (member) {\n    const result = member.displayName || member.username;\n    return result;\n};\n\nexports.getMention = function (userResolvable, full) {\n    let out;\n\n    if (userResolvable.user) { // Member\n        out = userResolvable.toString();\n    } else if (userResolvable.id) { // User\n        out = full ? exports.getFullName(userResolvable) : exports.getMostName(userResolvable);\n    } else { // Id\n        out = `<@${userResolvable}>`;\n    }\n\n    return out;\n};\n\nexports.getAvatar = function (userResolvable, outStr) {\n    if (userResolvable != null && exports.isObject(userResolvable)) {\n        if (userResolvable.user) userResolvable = userResolvable.user;\n        // return userResolvable.displayAvatarURL({ format: 'png' });\n        return userResolvable.displayAvatarURL;\n    }\n    return (outStr === true ? 'null' : null);\n};\n\nexports.isLoud = function (channel) {\n    const guild = channel.guild;\n\n    const botRegex = /\\bbot\\b|commands/i;\n    const botRegex2 = /\\bbot\\b/i;\n    const botRegex3 = /commands/i;\n\n    if (!botRegex.test(channel.name)) {\n        let botChannel = guild.channels.find(c => botRegex2.test(c.name) && botRegex3.test(c.name));\n        if (!botChannel) botChannel = guild.channels.find(c => botRegex.test(c.name));\n\n        if (botChannel) {\n            exports.print(channel, `Please use ${botChannel}`);\n        } else {\n            exports.print(\n                channel,\n                'Please get the server staff to create a bot commands channel (or to make sure any existing one has \"bot\" or \"commands\" in the name)',\n            );\n        }\n\n        return true;\n    }\n\n    return false;\n};\n\nexports.getDateString = function (d) {\n    if (d == null) d = new Date();\n    const result = `${DateFormat(d, 'ddd, mmm dS yyyy @ h:MM TT')} GMT`;\n    return result;\n};\n\nexports.hasRole = (member, role) => member.roles.has(role.id);\n\nexports.hasRoleName = (member, name) => {\n    name = name.toLowerCase();\n    const hasRoleVal = member.roles.some(role => role.name.toLowerCase().includes(name));\n    return hasRoleVal;\n};\n\nexports.makeEmbedFooter = function (user, dateParam) {\n    const memberName = exports.isObject(user) ? exports.getDisplayName(user) : String(user);\n    let date = dateParam;\n    if (date == null) date = new Date();\n    const dateStr = exports.getDateString(date);\n    return { text: `${memberName} | ${dateStr}`, icon_url: exports.getAvatar(user) };\n};\n\nexports.getSuffix = function (n) {\n    const j = n % 10;\n    const k = n % 100;\n    if (j == 1 && k != 11) {\n        return `${n}st`;\n    }\n    if (j == 2 && k != 12) {\n        return `${n}nd`;\n    }\n    if (j == 3 && k != 13) {\n        return `${n}rd`;\n    }\n    return `${n}th`;\n};\n\n/*\n\n    If nowString is less than or exactly 512 characters set as field value and return nowFieldNum\n    Find last newline under 512 characters\n    If none exists then trim, set and return nowFieldNum\n    Set everything before newline as value for the field\n    Create new field immediately after current field\n    Set name as zero width character\n    Return function on new field and string after newline\n\n*/\n\nexports.setFieldValue = function (embFields, nowFieldNum, nowString) {\n    const nowField = embFields[nowFieldNum];\n    if (nowString.length <= 512) {\n        nowField.value = nowString;\n        return nowFieldNum;\n    }\n    let subFirst = nowString.substr(0, 512);\n    let subNext;\n    const lastNewline = subFirst.lastIndexOf('\\n');\n    if (lastNewline < 0) {\n        const lastSpace = subFirst.lastIndexOf(' ');\n        if (lastSpace < 0) {\n            subNext = nowString.substring(512);\n        } else {\n            subFirst = nowString.substring(0, lastSpace);\n            subNext = nowString.substring(lastSpace + 1);\n        }\n    } else {\n        subFirst = nowString.substring(0, lastNewline);\n        subNext = nowString.substring(lastNewline + 1);\n    }\n    nowField.value = subFirst;\n    const newFieldNum = nowFieldNum + 1;\n    embFields.splice(newFieldNum, 0, { name: '​', value: '', inline: nowField.inline });\n    return exports.setFieldValue(embFields, newFieldNum, subNext);\n};\n\n/*\n\nMax characters:\n\nTitle: 256\nDescription: 2048\nFooter: 2048\nField Name: 256\nField Value: 512 (maybe 1024?)\n\n*/\n\nexports.sendEmbed = function (embChannel, embTitle, embDesc, embFooterParam, embImage, embColor, embFieldsParam, isContinued) {\n    if (embChannel == null) return;\n\n    let embFooter = embFooterParam;\n    let embFields = embFieldsParam;\n\n    let manyFields = false;\n    let extraFields;\n\n    if (embFields == null) embFields = [];\n\n    for (let i = embFields.length - 1; i >= 0; i--) {\n        if (!embFields[i].name) embFields.splice(i, 1);\n    }\n\n    if (embFields.length > 25) {\n        manyFields = true;\n        extraFields = embFields.splice(25);\n    }\n\n    for (let i = 0; i < embFields.length; i++) {\n        const nowField = embFields[i];\n\n        if (!has.call(nowField, 'inline')) nowField.inline = true;\n\n        let nowName = nowField.name;\n        let nowValue = nowField.value;\n\n        nowName = exports.safeEveryone(String(nowName == null ? 'N/A' : nowName));\n        nowValue = exports.safeEveryone(String(nowValue == null ? 'N/A' : nowValue));\n\n        nowField.name = nowName.trim().length < 1 ? 'N/A' : nowName.substr(0, 256);\n\n        if (nowValue.trim().length < 1) {\n            nowField.value = 'N/A';\n        } else if (nowValue.length > 512) {\n            i = exports.setFieldValue(embFields, i, nowValue);\n        } else {\n            nowField.value = nowValue;\n        }\n    }\n\n    const embDescStr = String(embDesc);\n\n    let newTitle;\n    let newFooter;\n    let newDesc = ((embDesc == null || embDescStr.trim().length < 1) ? '​' : embDescStr.substr(0, 2048));\n\n    if (embTitle) newTitle = embTitle.substr(0, 256);\n    if (embFooter) {\n        if (!exports.isObject(embFooter)) {\n            embFooter = { text: embFooter };\n        }\n        newFooter = exports.cloneObj(embFooter);\n        newFooter.text = (newFooter.text).substr(0, 2048);\n    }\n\n    if (isContinued) {\n        newTitle = null;\n        if (newDesc.length < 1 || newDesc === '​') newDesc = null;\n    }\n\n    if (manyFields) {\n        newFooter = null;\n    }\n\n    const embObj = {\n        title: newTitle,\n        description: newDesc,\n        fields: embFields,\n        footer: newFooter,\n        thumbnail: { url: embImage },\n        color: embColor,\n    };\n\n    // console.log(1111, embObj);\n\n    embChannel.send(undefined, { embed: embObj })\n        .then(() => {\n            // console.log(2222);\n        })\n        .catch((error) => {\n            // console.log(3333);\n            Util.log(`[E_SendEmbed] ${error} ${embChannel}`);\n            Util.log(embObj);\n            Util.log(JSON.stringify(embFields));\n        });\n\n    if (manyFields) {\n        exports.sendEmbed(embChannel, embTitle, embDesc, embFooter, embImage, embColor, extraFields, true);\n    }\n};\n\nexports.sendDescEmbed = function (embChannel, embTitle, embDesc, embFooter, embImage, embColorParam) {\n    if (embChannel == null) return;\n\n    let embColor = embColorParam;\n\n    if (embColor == null) embColor = colBlue;\n\n    if (embDesc != null && embDesc.length > 2048) {\n        let subFirst = embDesc.substr(0, 2048);\n        let subNext;\n        const lastNewline = subFirst.lastIndexOf('\\n');\n        if (lastNewline < 0) {\n            const lastSpace = subFirst.lastIndexOf(' ');\n            if (lastSpace < 0) {\n                subNext = embDesc.substring(2048);\n            } else {\n                subFirst = embDesc.substring(0, lastSpace);\n                subNext = embDesc.substring(lastSpace + 1);\n            }\n        } else {\n            subFirst = embDesc.substring(0, lastNewline);\n            subNext = embDesc.substring(lastNewline + 1);\n        }\n        exports.sendEmbed(embChannel, embTitle, subFirst, null, embImage, embColor, []);\n        exports.sendDescEmbed(embChannel, null, subNext, embFooter, embImage, embColor);\n    } else {\n        exports.sendEmbed(embChannel, embTitle, embDesc, embFooter, embImage, embColor, []);\n    }\n};\n\nexports.sendLog = function (embData, embColor) {\n    const embTitle = embData[0];\n    const embGuild = embData[1];\n    const embAuthor = embData[2];\n    const embFields = embData.splice(3);\n\n    for (let i = embFields.length - 1; i >= 0; i--) {\n        if (!embFields[i].name) embFields.splice(i, 1);\n    }\n\n    const embedTitleLower = embTitle.toLowerCase();\n\n    const logChannel = exports.findChannel('vaebot-log', embGuild);\n    if (logChannel) {\n        const embFooter = exports.makeEmbedFooter(embAuthor);\n        const embAvatar = exports.getAvatar(embAuthor);\n\n        exports.sendEmbed(\n            logChannel,\n            exports.cloneObj(embTitle),\n            null,\n            exports.cloneObj(embFooter),\n            exports.cloneObj(embAvatar),\n            exports.cloneObj(embColor),\n            exports.cloneObj(embFields));\n    }\n\n    const regex = /(\\S*)(?:warn|mute|kick|ban)/i;\n    const pre = (regex.exec(embedTitleLower) || [])[1];\n\n    const modChannel = exports.findChannel('mod-logs', embGuild);\n    if (modChannel && pre != null && pre != 'un' && !embedTitleLower.includes('revert') && !embedTitleLower.includes('cleared')) {\n        const embFooter = exports.makeEmbedFooter(embAuthor);\n        const embAvatar = exports.getAvatar(embAuthor);\n\n        exports.sendEmbed(\n            modChannel,\n            exports.cloneObj(embTitle),\n            null,\n            exports.cloneObj(embFooter),\n            exports.cloneObj(embAvatar),\n            exports.cloneObj(embColor),\n            exports.cloneObj(embFields));\n    }\n};\n\nexports.getHourStr = function (d) {\n    let valStr = (d.getHours()).toString();\n    if (valStr.length < 2) valStr = `0${valStr}`;\n    return valStr;\n};\n\nexports.getMinStr = function (d) {\n    let valStr = (d.getMinutes()).toString();\n    if (valStr.length < 2) valStr = `0${valStr}`;\n    return valStr;\n};\n\nexports.getYearStr = function (d) {\n    const valStr = (d.getFullYear()).toString();\n    return valStr;\n};\n\nexports.getMonthStr = function (d) {\n    let valStr = (d.getMonth() + 1).toString();\n    if (valStr.length < 2) valStr = `0${valStr}`;\n    return valStr;\n};\n\nexports.getDayStr = function (d) {\n    let valStr = (d.getDate()).toString();\n    if (valStr.length < 2) valStr = `0${valStr}`;\n    return valStr;\n};\n\n/* function searchPartial(array, name, checkPartial) {\n    if (checkPartial != false) {\n        var firstChar = name.substr(0, 1);\n        var endChar = name.substr(name.length-1, 1);\n        if (firstChar == \"\\\"\" && endChar == \"\\\"\") {\n            checkPartial = false;\n            name = name.substring(1, name.length-1);\n            if (name.length < 1) return;\n        }\n    }\n    name = name.toLowerCase()\n    var user = array.find(function(item) {\n        var user = exports.getName(item);\n        if (checkPartial != false ? exports.safe(user.toLowerCase()).includes(name) : exports.safe(user.toLowerCase()) == name) {\n            return true;\n        }\n        return false;\n    })\n    return user;\n} */\n\nexports.searchUserPartial = function (container, name) {\n    name = name.toLowerCase();\n    return container.find((user) => {\n        const username = exports.getName(user);\n        if (user.id === name || exports.safe(username.toLowerCase()).includes(name)) {\n            return true;\n        }\n        return false;\n    });\n};\n\nexports.round = function (num, inc) {\n    return inc == 0 ? num : Math.floor((num / inc) + 0.5) * inc;\n};\n\nexports.write = function (content, name) {\n    FileSys.writeFile(name, content);\n};\n\nexports.remove = function (name) {\n    FileSys.unlink(name);\n};\n\nexports.resolveUserMention = function (guild, id) {\n    let resolvedUser;\n\n    if (id == null) {\n        id = guild;\n        resolvedUser = client.users.get(id);\n    } else {\n        resolvedUser = exports.getMemberById(id, guild);\n    }\n\n    if (resolvedUser) return resolvedUser.toString();\n\n    return `<@${id}>`;\n};\n\nexports.getNumMutes = async function (id, guild) {\n    const pastMutes = await Data.getRecords(guild, 'mutes', { user_id: id });\n    return pastMutes.length;\n};\n\nexports.historyToStringOld = function (num) {\n    let timeHours = exports.round(num / 3600000, 0.1);\n    timeHours = (timeHours >= 1 || timeHours == 0) ? timeHours.toFixed(0) : timeHours.toFixed(1);\n    Util.log(`[RANDOM] timeHours: ${timeHours}`);\n    return timeHours + (timeHours == 1 ? ' hour' : ' hours');\n};\n\nexports.historyToStringOld2 = function (num) {\n    let timeHours = num / 3600000;\n    Util.log(`[RANDOM] timeHours: ${timeHours}`);\n    timeHours += (timeHours == 1 ? ' hour' : ' hours');\n    return timeHours;\n};\n\nexports.historyToString = function (num) {\n    const timeStr = exports.formatTime(num);\n    return timeStr;\n};\n\nexports.matchWholeNumber = function (str) {\n    let result = str.match(/^\\d*(?:\\.\\d+)?$/);\n    result = result ? result[0] : undefined;\n    return result;\n};\n\nexports.getSafeId = function (id) {\n    id = id.match(/\\d+/);\n\n    if (id == null) return undefined;\n\n    return id[0];\n};\n\nexports.getMemberById = function (id, guild) {\n    if (id == null || guild == null) return null;\n\n    if (id.substr(0, 1) === '<' && id.substr(id.length - 1, 1) === '>') id = exports.getSafeId(id);\n\n    if (id == null || id.length < 1) return null;\n\n    return guild.members.get(id);\n};\n\nexports.isId = function (str) {\n    let id = str.match(/^\\d+$/);\n\n    if (id == null) {\n        id = str.match(/^<.?(\\d+)>$/);\n        if (id == null) return undefined;\n        id = id[1];\n    } else {\n        id = id[0];\n    }\n\n    if (id.length < 17 || id.length > 19) return undefined;\n\n    return id;\n};\n\nexports.getMatchStrength = function (fullStr, subStr) { // [v2.0]\n    let value = 0;\n\n    const fullStrLower = fullStr.toLowerCase();\n    const subStrLower = subStr.toLowerCase();\n\n    const nameMatch = fullStrLower.indexOf(subStrLower);\n\n    if (nameMatch >= 0) {\n        const filled = Math.min(subStr.length / fullStr.length, 0.999);\n        value += 2 ** (2 + filled);\n\n        const maxCaps = Math.min(subStr.length, fullStr.length);\n        let numCaps = 0;\n        for (let j = 0; j < maxCaps; j++) {\n            if (subStr[j] === fullStr[nameMatch + j]) numCaps++;\n        }\n        const caps = Math.min(numCaps / maxCaps, 0.999);\n        value += 2 ** (1 + caps);\n\n        const totalPosition = fullStr.length - subStr.length;\n        const perc = 1 - (totalPosition * nameMatch == 0 ? 0.001 : nameMatch / totalPosition);\n        value += 2 ** perc;\n    }\n\n    return value;\n};\n\nexports.getDiscriminatorFromName = function (name) {\n    const discrimPattern = /#(\\d\\d\\d\\d)$/gm;\n    let discrim = discrimPattern.exec(name);\n    discrim = discrim ? discrim[1] : null;\n    return discrim;\n};\n\nexports.isNumeric = function (str) {\n    return !isNaN(parseFloat(str)) && isFinite(str);\n};\n\nexports.entirelyNumbers = function (str) {\n    return /^\\d+$/.test(str);\n};\n\nexports.getBestMatch = function (container, key, name) { // [v3.0] Visible name match, real name match, length match, caps match, position match\n    if (container == null) return undefined;\n\n    let removeUnicode = false;\n\n    if (key === 'username') {\n        removeUnicode = true;\n        const nameDiscrim = exports.getDiscriminatorFromName(name);\n        if (nameDiscrim) {\n            const namePre = name.substr(0, name.length - 5);\n            const user = container.find(m => m.username === namePre && m.discriminator === nameDiscrim);\n            if (user) return user;\n        }\n    }\n\n    const origName = name.trim();\n\n    if (removeUnicode) {\n        name = name.replace(/[^\\x00-\\x7F]/g, '').trim();\n        if (name.length == 0) {\n            name = origName;\n            removeUnicode = false;\n        }\n    }\n\n    const str2Lower = name.toLowerCase();\n    let strongest = null;\n\n    container.forEach((obj) => {\n        let realName = obj[key];\n        if (removeUnicode) realName = realName.replace(/[^\\x00-\\x7F]/g, '');\n        realName = realName.trim();\n        const nameMatch = realName.toLowerCase().indexOf(str2Lower);\n\n        const strength = { 'obj': obj };\n        let layer = 0;\n\n        if (nameMatch >= 0) {\n            strength[layer++] = 1;\n\n            // Util.log(\"(\" + i + \") \" + realName + \": \" + value);\n            const filled = Math.min(name.length / realName.length, 0.999);\n            // Util.log(\"filled: \" + filled);\n            strength[layer++] = filled;\n\n            const maxCaps = Math.min(name.length, realName.length);\n            let numCaps = 0;\n            for (let j = 0; j < maxCaps; j++) {\n                if (name[j] === realName[nameMatch + j]) numCaps++;\n            }\n            const caps = Math.min(numCaps / maxCaps, 0.999);\n            // const capsExp = (filledExp * 0.5 - 1 + caps);\n            // Util.log(\"caps: \" + caps + \" (\" + numCaps + \"/\" + maxCaps + \")\");\n            strength[layer++] = caps;\n\n            const totalPosition = realName.length - name.length;\n            const perc = 1 - (totalPosition * nameMatch == 0 ? 0.001 : nameMatch / totalPosition);\n            // const percExp = (capsExp - 2 + perc);\n            // Util.log(\"pos: \" + perc + \" (\" + nameMatch + \"/\" + totalPosition + \")\");\n            strength[layer++] = perc;\n\n            if (strongest == null) {\n                strongest = strength;\n            } else {\n                for (let i = 0; i < layer; i++) {\n                    if (strength[i] > strongest[i]) {\n                        strongest = strength;\n                        break;\n                    } else if (strength[i] < strongest[i]) {\n                        break;\n                    }\n                }\n            }\n        }\n    });\n\n    return strongest != null ? strongest.obj : undefined;\n};\n\nexports.getMemberByName = function (name, guild) { // [v3.0] Visible name match, real name match, length match, caps match, position match\n    if (guild == null) return undefined;\n\n    const nameDiscrim = exports.getDiscriminatorFromName(name);\n    if (nameDiscrim) {\n        const namePre = name.substr(0, name.length - 5);\n        const member = guild.members.find(m => m.user.username === namePre && m.user.discriminator === nameDiscrim);\n        if (member) return member;\n    }\n\n    let removeUnicode = true;\n    const origName = name.trim();\n\n    name = name.replace(/[^\\x00-\\x7F]/g, '').trim();\n\n    if (name.length == 0) {\n        name = origName;\n        removeUnicode = false;\n    }\n\n    const str2Lower = name.toLowerCase();\n    const members = guild.members;\n    let strongest = null;\n\n    if (str2Lower == 'vaeb') {\n        const selfMember = members.get(vaebId);\n        if (selfMember) return selfMember;\n    }\n\n    members.forEach((member) => {\n        let realName = member.nickname != null ? member.nickname : exports.getName(member);\n        if (removeUnicode) realName = realName.replace(/[^\\x00-\\x7F]/g, '');\n        realName = realName.trim();\n        let realstr2Lower = realName.toLowerCase();\n        let nameMatch = realstr2Lower.indexOf(str2Lower);\n\n        const strength = { 'member': member };\n        let layer = 0;\n\n        if (nameMatch >= 0) {\n            strength[layer++] = 2;\n        } else {\n            realName = exports.getName(member);\n            if (removeUnicode) realName = realName.replace(/[^\\x00-\\x7F]/g, '');\n            realName = realName.trim();\n            realstr2Lower = realName.toLowerCase();\n            nameMatch = realstr2Lower.indexOf(str2Lower);\n            if (nameMatch >= 0) {\n                strength[layer++] = 1;\n            }\n        }\n\n        if (nameMatch >= 0) {\n            // Util.log(\"(\" + i + \") \" + realName + \": \" + value);\n            const filled = Math.min(name.length / realName.length, 0.999);\n            // Util.log(\"filled: \" + filled);\n            strength[layer++] = filled;\n\n            const maxCaps = Math.min(name.length, realName.length);\n            let numCaps = 0;\n            for (let j = 0; j < maxCaps; j++) {\n                if (name[j] === realName[nameMatch + j]) numCaps++;\n            }\n            const caps = Math.min(numCaps / maxCaps, 0.999);\n            // const capsExp = (filledExp * 0.5 - 1 + caps);\n            // Util.log(\"caps: \" + caps + \" (\" + numCaps + \"/\" + maxCaps + \")\");\n            strength[layer++] = caps;\n\n            const totalPosition = realName.length - name.length;\n            const perc = 1 - (totalPosition * nameMatch == 0 ? 0.001 : nameMatch / totalPosition);\n            // const percExp = (capsExp - 2 + perc);\n            // Util.log(\"pos: \" + perc + \" (\" + nameMatch + \"/\" + totalPosition + \")\");\n            strength[layer++] = perc;\n\n            if (strongest == null) {\n                strongest = strength;\n            } else {\n                for (let i = 0; i < layer; i++) {\n                    if (strength[i] > strongest[i]) {\n                        strongest = strength;\n                        break;\n                    } else if (strength[i] < strongest[i]) {\n                        break;\n                    }\n                }\n            }\n        }\n    });\n\n    return strongest != null ? strongest.member : undefined;\n};\n\nexports.getMemberByNameOld = function (name, guild) { // [v2.0] Visible name match, real name match, length match, caps match, position match //\n    if (guild == null) return undefined;\n\n    const nameDiscrim = exports.getDiscriminatorFromName(name);\n    if (nameDiscrim) {\n        const namePre = name.substr(0, name.length - 5);\n        const member = guild.members.find(m => m.user.username === namePre && m.user.discriminator === nameDiscrim);\n        if (member) return member;\n    }\n\n    let removeUnicode = true;\n    const origName = name.trim();\n\n    name = name.replace(/[^\\x00-\\x7F]/g, '');\n    name = name.trim();\n\n    if (name.length == 0) {\n        name = origName;\n        removeUnicode = false;\n    }\n\n    const str2Lower = name.toLowerCase();\n\n    const members = guild.members;\n    const matchStrength = [];\n    let strongest = [0, undefined];\n\n    members.forEach((member) => {\n        let value = 0;\n\n        let realName = member.nickname != null ? member.nickname : exports.getName(member);\n        if (removeUnicode) realName = realName.replace(/[^\\x00-\\x7F]/g, '');\n        realName = realName.trim();\n        let realstr2Lower = realName.toLowerCase();\n        let nameMatch = realstr2Lower.indexOf(str2Lower);\n\n        if (nameMatch >= 0) {\n            value += 2 ** 5;\n        } else {\n            realName = exports.getName(member);\n            if (removeUnicode) realName = realName.replace(/[^\\x00-\\x7F]/g, '');\n            realName = realName.trim();\n            realstr2Lower = realName.toLowerCase();\n            nameMatch = realstr2Lower.indexOf(str2Lower);\n            if (nameMatch >= 0) {\n                value += 2 ** 4;\n            }\n        }\n\n        if (nameMatch >= 0) {\n            // Util.log(\"(\" + i + \") \" + realName + \": \" + value);\n            const filled = Math.min(name.length / realName.length, 0.999);\n            const filledExp = (2 + filled);\n            // Util.log(\"filled: \" + filled);\n            value += 2 ** filledExp;\n\n            const maxCaps = Math.min(name.length, realName.length);\n            let numCaps = 0;\n            for (let j = 0; j < maxCaps; j++) {\n                if (name[j] === realName[nameMatch + j]) numCaps++;\n            }\n            const caps = Math.min(numCaps / maxCaps, 0.999);\n            // const capsExp = (filledExp * 0.5 - 1 + caps);\n            const capsExp = (1 + caps);\n            // Util.log(\"caps: \" + caps + \" (\" + numCaps + \"/\" + maxCaps + \")\");\n            value += 2 ** capsExp;\n\n            const totalPosition = realName.length - name.length;\n            const perc = 1 - (totalPosition * nameMatch == 0 ? 0.001 : nameMatch / totalPosition);\n            // const percExp = (capsExp - 2 + perc);\n            const percExp = (0 + perc);\n            // Util.log(\"pos: \" + perc + \" (\" + nameMatch + \"/\" + totalPosition + \")\");\n            value += 2 ** percExp;\n\n            // Util.log(value);\n            matchStrength.push([value, member]);\n        }\n    });\n\n    for (let i = 0; i < matchStrength.length; i++) {\n        const strength = matchStrength[i];\n        if (strength[0] > strongest[0]) strongest = strength;\n    }\n\n    return strongest[1];\n};\n\nfunction getDataFromStringInner(str, funcs, returnExtra) {\n    const mix = str.split(' ');\n    const baseStart = mix.length - 1;\n    let start = baseStart;\n    let end = 0;\n    let pos = start;\n    let index = 0;\n    let combine = [];\n    const results = [];\n    while (start >= 0) {\n        const remainingFuncs = funcs.length - index - 1;\n        const remainingTerms = baseStart - (start);\n        if (remainingTerms < remainingFuncs) {\n            start--;\n            pos = start;\n            combine = [];\n            continue;\n        }\n        const chunk = mix[pos];\n        if (chunk != null) combine.unshift(chunk);\n        if (pos <= end) {\n            const result = funcs[index](combine.join(' '), results);\n            if (result != null) {\n                /* if (index == 1) {\n                    Util.log(\"[Z] \" + combine.join(\" \"));\n                    Util.log(\"[Z] \" + remainingFuncs);\n                    Util.log(\"[Z] \" + remainingTerms);\n                    Util.log(\"[Z] \" + pos);\n                    Util.log(\"[Z] \" + start);\n                    Util.log(\"[Z] \" + end);\n                    Util.log(\"[Z] \" + result);\n                } */\n                results.push(result);\n                index++;\n                if (index >= funcs.length) {\n                    if (returnExtra) {\n                        combine = [];\n                        for (let i = start + 1; i < mix.length; i++) {\n                            const extra = mix[i];\n                            if (extra != null) combine.push(extra);\n                        }\n                        let leftOver = '';\n                        if (combine.length > 0) leftOver = combine.join(' ');\n                        results.push(leftOver);\n                    }\n                    return results;\n                }\n                end = start + 1;\n                if (end > baseStart) return undefined;\n                start = baseStart;\n            } else {\n                start--;\n            }\n            pos = start;\n            combine = [];\n        } else {\n            pos--;\n        }\n    }\n\n    return undefined;\n}\n\n/*\n\n    If optional parameters:\n        -Fixed order\n        -Only one function per optional parameter (so no need for putting each function in its own array)\n        -Some optional parameters might only be an option if a previous optional parameter exists\n        -Some optional parameters might not have a space before them\n        -Don't know which parameters are being used\n\n    ;cmd optionalParam1 name optionalParam2DependsOnOP1 optionalParam2 time\n\n    const data = Util.getDataFromString(args,\n        [\n            {\n                func: function (str) {\n                    return Util.getMemberByMixed(str, guild) || Util.isId(str);\n                },\n            },\n            {\n                func: function (str) {\n                    const timeHours = Util.matchWholeNumber(str);\n                    return timeHours;\n                },\n                optional: true,\n            },\n            {\n                func: function (str) {\n                    let mult;\n                    str = str.toLowerCase();\n                    if (str.substr(str.length - 1, 1) == 's' && str.length > 2) str = str.substr(0, str.length - 1);\n                    if (str == 'millisecond' || str == 'ms') mult = 1 / 60 / 60 / 1000;\n                    if (str == 'second' || str == 's' || str == 'sec') mult = 1 / 60 / 60;\n                    if (str == 'minute' || str == 'm' || str == 'min') mult = 1 / 60;\n                    if (str == 'hour' || str == 'h') mult = 1;\n                    if (str == 'day' || str == 'd') mult = 24;\n                    if (str == 'week' || str == 'w') mult = 24 * 7;\n                    if (str == 'month' || str == 'mo') mult = 24 * 30.42;\n                    if (str == 'year' || str == 'y') mult = 24 * 365.2422;\n                    return mult;\n                },\n                optional: true,\n                requires: 1,\n                prefix: / ?/,\n            },\n        ]\n    , true);\n\n    - Get all possible variations that match the prefixes\n\n*/\n\nexports.getDataFromString = function (str, funcSets, returnExtra) {\n    if (typeof funcSets[0] == 'function') return getDataFromStringInner(str, funcSets, returnExtra);\n\n    const mainData = getDataFromStringInner(str, funcSets[0], returnExtra);\n\n    if (!mainData) return mainData;\n\n    let lastExtra = mainData[funcSets[0].length];\n    mainData.splice(funcSets[0].length);\n\n    for (let i = 1; i < funcSets.length; i++) {\n        if (!lastExtra || lastExtra.length == 0) break;\n        const nowData = getDataFromStringInner(lastExtra, funcSets[i], returnExtra);\n        if (nowData) {\n            for (let j = 0; j < funcSets[i].length; j++) mainData.push(nowData[j]);\n            lastExtra = nowData[funcSets[i].length];\n        } else {\n            for (let j = 0; j < funcSets[i].length; j++) mainData.push(null);\n        }\n    }\n\n    mainData.push(lastExtra);\n\n    return mainData;\n};\n\nfunction matchSet(str, funcSets, setIndex, data) {\n    if (setIndex >= funcSets.length) {\n        return true;\n    }\n    data.fail = Math.max(data.fail, setIndex);\n    Util.log(`Loop ${setIndex}`);\n    const set = funcSets[setIndex++];\n    if (set.requires && data[set.requires] === undefined) {\n        Util.log('Missing requires');\n        if (!set.optional) return false;\n        data.push(undefined);\n        if (matchSet(str, funcSets, setIndex, data)) return true;\n        data.pop();\n    } else if (str.length === 0) {\n        for (let i = setIndex - 1, s; s = funcSets[i]; i++) {\n            if (!s.optional) return false;\n        }\n        return true;\n    }\n    const pMatch = str.match(set.prefix || (setIndex === 1 ? /\\s*/ : /\\s+/));\n    Util.log('\\t', pMatch, setIndex, set.prefix || (setIndex === 1 ? /\\s*/ : /\\s+/));\n    if (!pMatch) return false;\n    if (pMatch.index !== 0) return false;\n    str = str.substr(pMatch[0].length);\n    for (let i = str.length; i >= 0; i--) {\n        const part = str.substr(0, i);\n        // Util.log(`\\tchecking part ${part}`);\n        let good = true;\n        if (set.match) {\n            const mMatch = part.match(set.match);\n            good = mMatch[0] == part;\n        }\n        const res = good && set.func(part);\n        if (!res || !good) continue;\n        // Util.log(\"Got\", res, \"for\", data.length, \"with length\", i);\n        data.push(res);\n        const left = str.substr(i);\n        if (matchSet(left, funcSets, setIndex, data)) {\n            // Util.log(\"Reached the end, yeuy!\");\n            return true;\n        }\n        data.pop();\n        if (set.longest) return false;\n    }\n    if (!set.optional) return false;\n    return matchSet(str, funcSets, setIndex, data);\n}\n\nexports.getDataFromString2 = function (str, funcSets, returnExtra) {\n    const data = [];\n    data.fail = 0;\n    const done = [];\n    if (returnExtra) {\n        funcSets.push({\n            func(extra) {\n                return extra;\n            },\n            optional: true,\n        });\n    }\n    const success = matchSet(str, funcSets, 0, data, done);\n    data.success = success;\n    if (success) {\n        for (let i = data.length; i < funcSets.length; i++) {\n            data.push(undefined);\n        }\n        delete data.fail;\n    }\n    return data;\n};\n\nexports.clamp = function (num, minParam, maxParam) {\n    let min = minParam;\n    let max = maxParam;\n    if (min == null) min = num;\n    if (max == null) max = num;\n    return Math.min(Math.max(num, min), max);\n};\n\nexports.noBlock = function (str) {\n    return ` \\`\\`\\`\\n${str}\\n\\`\\`\\`\\n `;\n};\n\nexports.toBoolean = function (str) {\n    const result = (typeof (str) === 'boolean' ? str : (str === 'true' || (str === 'false' ? false : undefined)));\n    return result;\n};\n\nexports.getNum = function (str, min, max) {\n    const num = Number(str);\n    if (isNaN(num)) return undefined;\n    return exports.clamp(num, min, max);\n};\n\nexports.getInt = function (str, min, max) {\n    const num = parseInt(str, 10); // Number() is better generally\n    if (isNaN(num)) return undefined;\n    return exports.clamp(num, min, max);\n};\n\nexports.isTextChannel = channel => channel.type === 'text';\n\nexports.isVoiceChannel = channel => channel.type === 'voice';\n\nexports.getTextChannels = guild => guild.channels.filter(exports.isTextChannel);\n\nexports.getVoiceChannels = guild => guild.channels.filter(exports.isVoiceChannel);\n\nexports.findChannel = function (nameParam, guild) {\n    if (guild == null) return undefined;\n\n    let name = nameParam;\n\n    name = name.toLowerCase();\n    const channels = exports.getTextChannels(guild);\n    return channels.find(nowChannel => nowChannel.id === name || nowChannel.name.toLowerCase() === name);\n};\n\nexports.findVoiceChannel = function (nameParam, guild) {\n    if (guild == null) return undefined;\n\n    let name = nameParam;\n\n    name = name.toLowerCase();\n    const channels = exports.getVoiceChannels(guild);\n    return channels.find(nowChannel => nowChannel.id === name || nowChannel.name.toLowerCase() === name);\n};\n\nexports.isAdmin = function (member) {\n    const highestRole = member.highestRole;\n    const guildRolesFromTop = exports.getGuildRoles(member.guild);\n\n    for (let i = 0; i < guildRolesFromTop.length; i++) {\n        const role = guildRolesFromTop[i];\n        if (/\\bmod/g.test(role.name.toLowerCase())) {\n            return false;\n        } else if (role.id == highestRole.id) {\n            return true;\n        }\n    }\n\n    return false;\n};\n\nexports.getRole = function (name, obj) {\n    if (obj == null) return undefined;\n\n    name = name.toLowerCase();\n\n    const nameId = exports.getSafeId(name);\n    const roles = obj.roles;\n    if (roles.has(nameId)) return roles.get(nameId);\n\n    const returnRole = exports.getBestMatch(roles, 'name', name);\n\n    return returnRole;\n};\n\nexports.getHighestRole = member => member.highestRole;\n\nexports.getPosition = function (speaker) {\n    if (speaker == null || !exports.isObject(speaker) || speaker.guild == null) return undefined;\n\n    if (speaker.id === speaker.guild.ownerID) return 999999999;\n\n    return speaker.highestRole.position;\n};\n\nexports.getUserById = id => client.users.get(id);\n\nexports.getUserByName = name => exports.getBestMatch(client.users, 'username', name);\n\nexports.getUserByMixed = function (name) {\n    let user = exports.getUserById(name);\n    if (user == null) user = exports.getUserByName(name);\n    return user;\n};\n\nexports.getMemberByMixed = function (name, guild) {\n    if (guild == null) return undefined;\n    let targetMember = exports.getMemberById(name, guild);\n    if (targetMember == null) targetMember = exports.getMemberByName(name, guild);\n    return targetMember;\n};\n\nexports.getMemberOrRoleByMixed = function (name, guild) {\n    if (guild == null) return undefined;\n    let targetObj = exports.getRole(name, guild);\n    if (targetObj == null) targetObj = exports.getMemberById(name, guild);\n    if (targetObj == null) targetObj = exports.getMemberByName(name, guild);\n    return targetObj;\n};\n\nexports.getEitherByMixed = function (name, guild) {\n    let user = exports.getMemberByMixed(name, guild);\n    if (user == null) user = exports.getUserByMixed(name);\n    return user;\n};\n\nexports.permEnabled = function (iPerms, permName) {\n    const allowGeneral = iPerms.General;\n    const allowText = iPerms.Text;\n    const allowVoice = iPerms.Voice;\n\n    if (has.call(allowGeneral, permName)) return allowGeneral[permName];\n    if (has.call(allowText, permName)) return allowText[permName];\n    if (has.call(allowVoice, permName)) return allowVoice[permName];\n\n    return undefined;\n};\n\nexports.getRolePermissions = function (role, channel) {\n    const outPerms = [];\n\n    if (!channel) {\n        for (let i = 0; i < exports.rolePermissions.length; i++) {\n            const permName = exports.rolePermissions[i];\n            if (role.hasPermission(permName)) {\n                outPerms.push(permName);\n            }\n        }\n    }\n\n    return outPerms;\n};\n\nexports.getPermRating = function (guild, userOrRole) {\n    if (userOrRole.hasPermission == null) return 0;\n\n    const tempPermRating = Util.cloneObj(Util.permRating);\n\n    let total = 0;\n    let foundTop = false;\n\n    for (let i = 0; i < tempPermRating.length; i++) {\n        const permData = tempPermRating[i];\n        if (userOrRole.hasPermission(permData[0], false)) {\n            if (!foundTop && i < tempPermRating.length -1) {\n                foundTop = true;\n\n                let lastVal = null;\n                let pointer0 = i + 1;\n                let pointer1 = i + 1;\n                let newVal = 5;\n\n                // Util.log(\"found\", permData[0]);\n\n                for (let i2 = i + 1; i2 < tempPermRating.length; i2++) {\n                    const nowVal = tempPermRating[i2][1];\n                    if (lastVal == null) lastVal = nowVal;\n                    if (nowVal !== lastVal) {\n                        const numPoints = (pointer1 - pointer0) + 1;\n                        newVal /= numPoints;\n                        for (let n = pointer0; n <= pointer1; n++) {\n                            tempPermRating[n][1] = newVal;\n                        }\n                        newVal /= 2;\n                        pointer0 = i2;\n                    }\n                    pointer1 = i2;\n                    lastVal = nowVal;\n                }\n\n                console.log('qqq', i, pointer0, pointer1);\n\n                const numPoints = (pointer1 - pointer0) + 1;\n                newVal /= numPoints;\n                for (let n = pointer0; n <= pointer1; n++) {\n                    tempPermRating[n][1] = newVal;\n                }\n\n                // Util.log(tempPermRating);\n            }\n            total += permData[1];\n        }\n    }\n\n    total = Math.min(total, 100);\n\n    return total;\n};\n\nexports.getMemberPowers = function (guild) {\n    const sorted = [];\n    const members = guild.members;\n    for (let i = 0; i < members.size; i++) {\n        const member = members[i];\n        const power = exports.getPermRating(guild, member);\n        let index = 0;\n        for (index = 0; index < sorted.length; index++) {\n            if (power >= sorted[index][1]) break;\n        }\n        sorted.splice(index, 0, [member, power]);\n    }\n    return sorted;\n};\n\nexports.strToPerm = function (strParam) {\n    let str = strParam;\n\n    str = exports.replaceAll(str.toUpperCase(), ' ', '_');\n\n    let matchPerm = null;\n    let matchTop = 0;\n\n    for (const [permName] of Object.entries(exports.permissionsOrder)) {\n        const matchScore = exports.getMatchStrength(permName, str);\n\n        if (matchScore > matchTop) {\n            matchTop = matchScore;\n            matchPerm = permName;\n        }\n    }\n\n    return matchPerm;\n};\n\nexports.setChannelPerms = function (channel, userOrRole, newPerms) {\n    channel.overwritePermissions(userOrRole, newPerms)\n        .catch(error => Util.log(`[E_SetChannelPerms] ${error}`));\n};\n\n// fetch more messages just like Discord client does\nexports.fetchMessagesEx = function (channel, left, store, lastParam) {\n    // message cache is sorted on insertion\n    // channel.messages[0] will get oldest message\n    let last = lastParam;\n\n    if (last) last = last.id;\n    return channel.fetchMessages({ limit: Math.min(left, 100), before: last })\n        .then(messages => exports.onFetch(messages, channel, left, store));\n};\n\nfunction mirrorProperties(member) {\n    const memberProto = Object.getPrototypeOf(member);\n    const userProto = Object.getPrototypeOf(member.user);\n    for (const key in member.user) {\n        if (!Object.getOwnPropertyDescriptor(memberProto, key)) {\n            Object.defineProperty(memberProto, key, {\n                get() {\n                    return this.user[key];\n                },\n                set(val) {\n                    this.user[key] = val;\n                },\n            });\n        }\n    }\n    const descriptors = Object.getOwnPropertyDescriptors(userProto);\n    for (const key in descriptors) {\n        if (!Object.getOwnPropertyDescriptor(memberProto, key)) {\n            Object.defineProperty(memberProto, key, {\n                get() {\n                    return this.user[key];\n                },\n                set(val) {\n                    this.user[key] = val;\n                },\n            });\n        }\n    }\n}\n\nexports.mergeUser = function (member) {\n    // Util.log('Adding new proxy:');\n    // Util.log(`Adding proxy to ${String(member)}`);\n\n    /* const oldPrototype = Object.getPrototypeOf(member);\n\n    if (Reflect.has(oldPrototype, 'proxyId')) return false;\n\n    const nowProxyId = proxyId++;\n\n    const userProxy = new Proxy({ proxyId: nowProxyId }, {\n        get(storage, prop) {\n            Util.log(`Getting ${prop} from ${nowProxyId}`);\n            if (Reflect.has(member, prop)) return Reflect.get(member, prop);\n            else if (Reflect.has(oldPrototype, prop)) return Reflect.get(oldPrototype, prop, member);\n            else if (Reflect.has(member.user, prop)) return Reflect.get(member.user, prop);\n            return storage[prop];\n        },\n        set(storage, prop, val) {\n            Util.logc('Setter', 'Setting', prop, 'to', val);\n            Reflect.set(member, prop, val); // Could 1st arg be oldPrototype and 4th be member?\n            return val;\n        },\n        getPrototypeOf() {\n            return Reflect.getPrototypeOf(member);\n        },\n    });\n    \n    Object.setPrototypeOf(member, userProxy); */\n\n    mirrorProperties(member);\n\n    return true;\n};\n\nexports.resolveMention = function (userResolvable) {\n    if (userResolvable == null) return undefined;\n    if (typeof user === 'string') return `<@${userResolvable}>`;\n    return `${Util.getMostName(userResolvable)} (${userResolvable.toString()})`;\n};\n\nexports.fieldsToDesc = function (fields) {\n    return `​\\n${fields.filter(fieldData => fieldData.name != null).map(fieldData => `**${fieldData.name}${fieldData.value != null ? ': ' : ''}**${fieldData.value != null ? fieldData.value : ''}`).join('\\n\\n')}`;\n};\n\nexports.resolveUser = function (guild, userResolvable, canBeSystem) { // If user can be system, userResolvable as text would be the bot/system\n    if (userResolvable == null) return undefined;\n\n    const resolvedData = {\n        member: userResolvable,\n        user: userResolvable,\n        id: userResolvable,\n        mention: userResolvable,\n        original: userResolvable,\n    };\n\n    let userType = 0; // Member\n    let system = false;\n\n    let wasId = false;\n\n    if (typeof userResolvable === 'string') {\n        const idMatch = exports.isId(userResolvable);\n        if (idMatch) {\n            userType = 1; // ID\n            resolvedData.id = idMatch;\n        } else {\n            userType = 2; // Name or System\n            system = canBeSystem && userResolvable.match(/[a-z]/i); // When resolving with system possibility the only use of text should be when the moderator is the system.\n        }\n    }\n\n    // exports.logc('Admin1', `User type: ${userType} (canBeSystem ${canBeSystem || false})`);\n\n    if (userType === 0) { // Member or User\n        if (!userResolvable.guild) resolvedData.member = guild.members.get(resolvedData.user.id); // User\n        else resolvedData.user = resolvedData.member.user; // Member\n        resolvedData.id = resolvedData.user.id;\n        resolvedData.mention = exports.resolveMention(resolvedData.member || resolvedData.user);\n    } else if (userType === 1) { // Contained ID\n        resolvedData.member = guild.members.get(resolvedData.id);\n        resolvedData.user = resolvedData.member ? resolvedData.member.user : client.users.get(resolvedData.id);\n        if (!resolvedData.user) { // Could be a name imitating an ID\n            wasId = true;\n            userType = 2;\n        } else {\n            resolvedData.mention = exports.resolveMention(resolvedData.member || resolvedData.user);\n        }\n    }\n\n    if (userType === 2) { // Name or System (Separate if statement for branching from userType_1)\n        if (system) { // VaeBot\n            resolvedData.member = guild.members.get(selfId);\n            resolvedData.user = resolvedData.member.user;\n            resolvedData.id = selfId;\n        } else { // Name\n            resolvedData.member = exports.getMemberByMixed(userResolvable, guild);\n            resolvedData.user = resolvedData.member ? resolvedData.member.user : exports.getUserByName(userResolvable);\n            if (resolvedData.user) {\n                resolvedData.id = resolvedData.user.id;\n                resolvedData.mention = exports.resolveMention(resolvedData.member || resolvedData.user);\n            } else if (!wasId) { // Didn't branch from id\n                return 'User not found'; // No user or member\n            }\n        }\n    }\n\n    return resolvedData; // [Definite Values] ID: Always | Mention: Always | Member/User: All inputs except ID and Name\n};\n\nexports.onFetch = function (messagesParam, channel, leftParam, store) {\n    let messages = messagesParam;\n    let left = leftParam;\n\n    messages = messages.array();\n\n    if (!messages.length) return Promise.resolve();\n\n    for (let i = 0; i < messages.length; i++) {\n        store.push(messages[i]);\n    }\n\n    left -= messages.length;\n\n    Util.log(`Received ${messages.length}, left: ${left}`);\n\n    if (left <= 0) return Promise.resolve();\n\n    return exports.fetchMessagesEx(channel, left, store, messages[messages.length - 1]);\n};\n\nexports.updateMessageCache = function (channel, speaker) {\n    exports.fetchMessagesEx(channel, 100, [], channel.messages[0]).then(() => {\n        if (speaker) {\n            exports.sendDescEmbed(channel, 'Message Cache', 'Refreshed', exports.makeEmbedFooter(speaker), null, colGreen);\n        }\n    });\n};\n\nexports.isMember = function (userRes) {\n    if (userRes.user != null) return true;\n    return false;\n};\n\nexports.getUser = function (userRes) {\n    if (!userRes) return null;\n    return userRes.user || userRes;\n};\n\nexports.isMap = function (obj) {\n    return obj instanceof Map;\n};\n\nexports.arrayToCollection = function (arr) {\n    const newCol = new Discord.Collection();\n    for (let i = 0; i < arr.length; i++) {\n        const value = arr[i];\n        newCol.set(value.id, value);\n    }\n    return newCol;\n};\n\nexports.chunkObj = function (obj, chunkSize) {\n    const chunks = [];\n    if (exports.isMap(obj)) {\n        const objArray = obj.array();\n        const size = obj.size;\n        for (let i = 0; i < size; i += chunkSize) {\n            const chunkArr = objArray.slice(i, i + chunkSize);\n            const chunk = exports.arrayToCollection(chunkArr);\n            chunks.push(chunk);\n        }\n    } else {\n        const size = obj.length;\n        for (let i = 0; i < size; i += chunkSize) {\n            const chunk = obj.slice(i, i + chunkSize);\n            chunks.push(chunk);\n        }\n    }\n    return chunks;\n};\n\nexports.deleteMessages = function (messages) {\n    let numMessages;\n    let firstMessage;\n\n    if (exports.isMap(messages)) {\n        numMessages = messages.size;\n        firstMessage = messages.first();\n    } else {\n        numMessages = messages.length;\n        firstMessage = messages[0];\n    }\n\n    if (numMessages < 1) {\n        Util.log('You must have at least 1 message to delete');\n    } else {\n        Util.log(`Deleting ${numMessages} messages`);\n    }\n\n    if (numMessages == 1) {\n        firstMessage.delete()\n            .catch((err) => {\n                Util.log(`[E_DeleteMessages1] ${err}`);\n            });\n    } else {\n        const chunks = exports.chunkObj(messages, 99);\n        for (let i = 0; i < chunks.length; i++) {\n            const chunk = chunks[i];\n            firstMessage.channel.bulkDelete(chunk)\n                .catch((err) => {\n                    Util.log(`[E_DeleteMessages2] ${err}`);\n                });\n        }\n    }\n};\n\nasync function fetchMessagesInner(channel, remaining, foundMessages, lastMessage) {\n    lastMessage = lastMessage != null ? lastMessage.id : undefined;\n\n    const messages = await channel.fetchMessages({ limit: Math.min(remaining, 99), before: lastMessage });\n\n    if (!messages || messages.size == 0) return foundMessages;\n\n    const messagesArr = messages.array();\n\n    for (let i = 0; i < messagesArr.length; i++) {\n        foundMessages.push(messagesArr[i]);\n    }\n\n    remaining -= messagesArr.length;\n\n    if (remaining <= 0) return foundMessages;\n\n    return fetchMessagesInner(channel, remaining, foundMessages, messagesArr[messagesArr.length - 1]);\n}\n\nexports.fetchMessages = async function (channel, numScan, checkFunc) {\n    if (!checkFunc) checkFunc = (() => true);\n\n    const scanMessages = await fetchMessagesInner(channel, numScan, [], null);\n    const foundMessages = scanMessages.filter(checkFunc);\n    Util.log(`Num Messages Found: ${foundMessages.length}`);\n    return foundMessages;\n};\n\nexports.banMember = function (member, moderator, reason, tempEnd) {\n    const guild = member.guild;\n    const memberId = member.id;\n    const memberMostName = exports.getMostName(member);\n\n    if (reason == null || reason.length < 1) reason = 'No reason provided';\n\n    let modFullName = moderator;\n    if (exports.isObject(moderator)) modFullName = exports.getFullName(moderator);\n\n    const linkedGuilds = Data.getLinkedGuilds(member.guild);\n\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const linkedGuild = linkedGuilds[i];\n        linkedGuild.ban(member.id, { days: 0, reason })\n            .then((userResolvable) => {\n                Util.logc('AddBan1', `Link-added ban for ${exports.getMention(userResolvable, true)} @ ${linkedGuild.name}`);\n            })\n            .catch(exports.logErr);\n    }\n\n    const sendLogData = [\n        `Guild ${tempEnd ? 'Temporary ' : ''}Ban`,\n        guild,\n        member,\n        { name: 'Username', value: member.toString() },\n        { name: 'Moderator', value: member.toString() },\n        { name: 'Ban Reason', value: reason },\n    ];\n\n    if (tempEnd) sendLogData.push({ name: 'Ban Ends', value: tempEnd });\n\n    exports.sendLog(sendLogData, colAction);\n\n    // Trello.addCard(member.guild, 'Bans', memberMostName, {\n    //     'User ID': memberId,\n    //     'Moderator': modFullName,\n    //     'Reason': `[TempBan] ${reason}`,\n    // });\n\n    return true;\n};\n\nexports.kickMember = function (member, moderator, reason) {\n    // const memberId = member.id;\n    // const memberMostName = exports.getMostName(member);\n\n    if (reason == null || reason.length < 1) reason = 'No reason provided';\n\n    // let modFullName = moderator;\n    // if (exports.isObject(moderator)) modFullName = exports.getFullName(moderator);\n\n    member.kick()\n        .catch(console.error);\n\n    // Trello.addCard(member.guild, 'Kicks', memberMostName, {\n    //     'User ID': memberId,\n    //     'Moderator': modFullName,\n    //     'Reason': reason,\n    // });\n};\n\nexports.getChanges = function (str1, str2) {\n    const len1 = str1.length;\n    const len2 = str2.length;\n    const matrix = []; // len1+1, len2+1\n\n    if (len1 == 0) {\n        return len2;\n    } else if (len2 == 0) {\n        return len1;\n    } else if (str1 == str2) {\n        return 0;\n    }\n\n    for (let i = 0; i <= len1; i++) {\n        matrix[i] = {};\n        matrix[i][0] = i;\n    }\n\n    for (let j = 0; j <= len2; j++) {\n        matrix[0][j] = j;\n    }\n\n    for (let i = 1; i <= len1; i++) {\n        for (let j = 1; j <= len2; j++) {\n            let cost = 1;\n\n            if (str1[i - 1] == str2[j - 1]) {\n                cost = 0;\n            }\n\n            matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);\n        }\n    }\n\n    return matrix[len1][len2];\n};\n\nexports.getLines = function (str) {\n    return str.split(/\\r\\n|\\r|\\n/);\n};\n\nexports.getLines2 = function (str) {\n    return exports.chunkString(str, 153); // Take 153 characters as average line length on average 1080p window\n};\n\nexports.simplifyStr = function (str) {\n    str = str.toLowerCase();\n    const strLength = str.length;\n    const midPoint = (str.length / 2) + 1;\n    for (let i = 1; i < midPoint; i++) { // Increment for number of characters in the string stopping before the last (no need to check if whole string is a repetition of itself)\n        const sub = str.substr(0, i); // Get the substring from start of length i\n        const num = Math.floor(strLength / i); // Get the number of times i goes into the length of the substring (number of times to repeat sub to make it fit)\n        const repeatedSub = sub.repeat(num); // Repeat the substring floor(num) times\n        if (repeatedSub == str) return [sub, num]; // If repeatedSub is equal to original string, return substring and repetition count\n    }\n    return [str, 1]; // Return substring and repetition count\n};\n\nexports.simplifyStrHeavy = function (str) {\n    // Assume str is already lowercase\n    str = str.replace(/\\s/g, '');\n    const strLength = str.length;\n    const midPoint = (str.length / 2) + 1; // The first int x for which floor(strLength / x) is 1, a.k.a the length when a substring is too large to repeat and fit into str\n    let numCanChange = 0;\n    let nextInc = 2;\n    for (let i = 1; i < midPoint; i++) { // Increments for number of characters in the string stopping before the midpoint\n        const sub = str.substr(0, i); // Get the str substring of length i\n        const num = Math.floor(strLength / i); // Get the number of times i goes into the length of the substring (number of times to repeat sub to make it fit)\n        const repeatedSub = sub.repeat(num); // Repeat the substring num times\n        const nowMaxChanges = Math.min(numCanChange * num, strLength / 2); // Get number of allowed alterations between strings to be classed as similar\n        if (exports.getChanges(repeatedSub, str) <= nowMaxChanges) return [sub, num]; // If repeatedSub is similar to original string, return substring and repetition count\n        if (i >= nextInc) { // Update multiplier for nowMaxChanges when length is large enough\n            numCanChange++;\n            nextInc *= 2;\n        }\n    }\n    return [str, 1]; // Return substring and repetition count\n};\n\nexports.similarStrings = function (str1, str2) {\n    str1 = str1.toLowerCase().trim();\n    str2 = str2.toLowerCase().trim();\n\n    // Get number of allowed alterations between strings to be classed as similar\n    let maxChanges = Math.floor(Math.min(Math.max(Math.max(str1.length, str2.length) / 3, Math.abs(str2.length - str1.length)), 6));\n\n    // Check if the original strings are similar (have a number of alterations between them [levenshtein distance] less/equal to maxChanges)\n    if (exports.getChanges(str1, str2) <= maxChanges) return true;\n\n    // Simplify both strings removing repeated similar data\n    [str1] = exports.simplifyStrHeavy(str1); // Reduce similar repeated strings (e.g. dog1dog2dog3 becomes dog1)\n    [str2] = exports.simplifyStrHeavy(str2);\n\n    // Update maxChanges for new string lengths\n    maxChanges = Math.floor(Math.min(Math.max(Math.max(str1.length, str2.length) / 3, Math.abs(str2.length - str1.length)), 6));\n\n    // Check if simplified strings are similar\n    return exports.getChanges(str1, str2) <= maxChanges;\n};\n\nexports.similarStringsStrict = function (str1, str2) {\n    str1 = str1.toLowerCase().trim();\n    str2 = str2.toLowerCase().trim();\n\n    const minStr = str1.length < str2.length ? str1 : str2;\n    const maxStr = str1.length < str2.length ? str2 : str1;\n\n    // if (minStr.replace(/[|. ,/#!$%^&*;:{}=\\-_`~()]/g, '').length < 10 && (str1.match(/\\s/g) || []).length < 2 && (str2.match(/\\s/g) || []).length < 2) return false;\n\n    if ((minStr.length < 4) && (minStr.length != 3 || maxStr.length < 4)) return str1 == str2;\n\n    // Get number of allowed alterations between strings to be classed as similar\n    let maxChanges = maxStr.length / 3;\n    maxChanges = Math.max(maxChanges, Math.abs(str2.length - str1.length));\n    maxChanges = Math.min(maxChanges, 6);\n    maxChanges = Math.floor(maxChanges);\n\n    // const maxChanges = Math.floor(Math.min(Math.max(Math.max(str1.length, str2.length) / 3, Math.abs(str2.length - str1.length)), 6));\n\n    // Check if the original strings are similar (have a number of alterations between them [levenshtein distance] less/equal to maxChanges)\n    if (exports.getChanges(str1, str2) <= maxChanges) return true;\n\n    return false;\n};\n\nexports.isSpam = function (content) {\n    if (exports.getLines2(content).length >= 500) return true; // If the message contains too many chunk-lines (so characters) consider it spam\n\n    const strLines = exports.getLines(content);\n\n    if (strLines.length > 1) {\n        let numSimilar = 0;\n        let mostCommon = strLines[0];\n        let numLines = strLines.length;\n\n        for (let i = 1; i < strLines.length; i++) {\n            const nowStr = strLines[i];\n            if (nowStr.trim().length < 1) {\n                numLines--;\n                continue;\n            }\n            const compStr = numSimilar === 0 ? strLines[i - 1] : mostCommon;\n            if (exports.similarStrings(nowStr, compStr)) {\n                if (numSimilar === 0) mostCommon = nowStr;\n                numSimilar++;\n            } else if (i === 2 && numSimilar === 0 && exports.similarStrings(nowStr, mostCommon)) {\n                numSimilar++;\n            }\n        }\n\n        if (numSimilar >= 3 || numSimilar == numLines) return true;\n    }\n\n    // ////////////////////////////////////////////////////////////////////////////////////////\n\n    const pattern = /\\S+/g; // Pattern for finding all matches for continuous substrings of non space characters\n    const matches = content.match(pattern); // Get the matches\n\n    for (let i = 0; i < matches.length; i++) { // Iterate through the matches\n        // Util.log(`---${i + 1}---`);\n        for (let j = 0; j < matches.length; j++) { // Iterate through the matches again in each iteration for concatenating multiple adjacent matches\n            let long = matches[j]; // Get the substring on non space characters\n            if (j + i >= matches.length) continue; // If there isn't a match at index j+i it can't be concatenated to joined-substring so skip\n            for (let k = 1; k <= i; k++) long += matches[j + k]; // Concatenate all matches after the one at j, onto the match at j, up until (inclusive) the match at i\n            // Util.log(long);\n            const [sub, num] = exports.simplifyStr(long); // Simplify the resultant concatenated substring that is made up of the match at j and the following matched substrings, to see if it consists of one repeated substring\n            // sub: The substring that can be repeated to make up the long var\n            // num: The number of times the substring needs to be repeated to make up the long var\n            const subLength = sub.length; // The number of characters in the repeated substring\n            let triggered = false; // Initialise spam detection variable\n            if (num >= 3) { // Only check for spam if substring has been repeated at least 3 times\n                if (subLength == 1) { // 1 character in substring, 100+ repetitions\n                    if (num >= 100) triggered = true; // Is spam\n                } else if (subLength == 2) { // 2 characters in substring, 20+ repetitions\n                    if (num >= 20) triggered = true; // Is spam\n                } else if (subLength == 3) { // 3 characters in substring, 7+ repetitions\n                    if (num >= 7) triggered = true; // Is spam\n                } else if (subLength <= 5) { // 4-5 characters in substring, 4+ repetitions\n                    if (num >= 4) triggered = true; // Is spam\n                } else { // 6+ characters in substring, 3+ repetitions\n                    triggered = true; // Is spam\n                }\n            }\n            if (triggered) { // If it was counted as spam\n                Util.log(long, ':', sub, ':', num);\n                return true; // Return true (spam)\n            }\n        }\n    }\n\n    return false; // Return false (not spam)\n};\n\nexports.reverse = function (str) {\n    return str.split('').reverse().join('');\n};\n\nexports.format = function (...args) {\n    const newArgs = [];\n\n    for (let i = 0; i < args.length; i++) {\n        newArgs[i] = exports.cloneObjDepth(args[i], 2);\n    }\n\n    return NodeUtil.format(...newArgs);\n};\n\nlet lastTag = null;\nlet lastWasEmpty = true;\n\nfunction postOutString(args, startNewline) {\n    const nowDate = new Date();\n    nowDate.setHours(nowDate.getHours() + 1);\n\n    let out = (startNewline && !lastWasEmpty) ? '\\n' : '';\n    out += NodeUtil.format(...args);\n\n    let outIndex = out.search(/[^\\n\\r]/g);\n    if (outIndex === -1) outIndex = 0;\n\n    out = out.slice(0, outIndex) + DateFormat(nowDate, '| dd/mm/yyyy | HH:MM | ') + out.slice(outIndex);\n\n    console.log(out);\n\n    lastWasEmpty = /[\\n\\r]\\s*$/.test(out);\n}\n\nexports.log = function (...args) {\n    postOutString(args, true);\n    lastTag = null;\n};\n\nexports.logc = function (...args) {\n    const nowTag = String(args.splice(0, 1)).toLowerCase();\n    const isNew = lastTag != nowTag;\n    postOutString(args, isNew);\n    lastTag = nowTag;\n};\n\nexports.logn = function (...args) {\n    postOutString(args, false);\n    lastTag = null;\n};\n\nexports.logErr = function (...args) {\n    args.unshift('[ERROR]');\n    postOutString(args, true);\n    lastTag = null;\n};\n\nconst getAuditLogChunk = 1;\nconst getAuditLogMax = 4; // This is completely pointless ...?\n\nasync function getAuditLogRec(guild, auditLogOptions, userData, checkedLogs) {\n    if (checkedLogs.length >= getAuditLogMax) return null;\n    const entries = (await guild.fetchAuditLogs(auditLogOptions)).entries;\n    const outLog = entries.find(log => userData.nowTimestamp - log.createdTimestamp < userData.maxElapsed && log.target.id === userData.target.id); // Needs to check latest first\n    if (outLog) return outLog;\n    entries.forEach((log) => { if (!checkedLogs.includes(log.id)) checkedLogs.push(log.id); });\n    auditLogOptions.limit++;\n    userData.maxElapsed += 700;\n    return getAuditLogRec(guild, auditLogOptions, userData, checkedLogs);\n}\n\nexports.getAuditLog = async function (guild, type, userData) {\n    userData.executor = Util.resolveUser(guild, userData.executor);\n\n    if (!userData.target) {\n        const auditLogOptions = { type, user: userData.executor, limit: 1 };\n        return (await guild.fetchAuditLogs(auditLogOptions)).entries.first();\n    }\n\n    userData.target = Util.resolveUser(guild, userData.target);\n    userData.maxElapsed = userData.maxElapsed || 3000;\n\n    const nowTimestamp = +new Date();\n    userData.nowTimestamp = nowTimestamp;\n\n    const auditLogOptions = { type, user: userData.executor, limit: getAuditLogChunk };\n    return getAuditLogRec(guild, auditLogOptions, userData, []);\n};\n"
  },
  {
    "path": "commands/administrator/Actions.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';actions', ';guild actions', ';all actions'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Output all actions that can be used in ;link',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel) => {\n        const sendEmbedFields = [];\n\n        for (const actionName in Events.Actions) {\n            if (!has.call(Events.Actions, actionName)) continue;\n\n            sendEmbedFields.push({ name: actionName, value: '​', inline: false });\n        }\n\n        Util.sendEmbed(channel, 'Actions', 'All actions which can be used in ;link\\n​', Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n    },\n});\n"
  },
  {
    "path": "commands/administrator/AddAction.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';addaction ', ';linkaction ', ';createaction ', ';action '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Creates an action to be used in ;link',\n\n    args: '',\n\n    example: 'EchoMessage (guild, eventName, actionArgs, eventArgs) => { Util.print(channel, ...eventArgs[3]) };',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel) => {\n        const spaceIndex = args.indexOf(' ');\n        if (spaceIndex == -1) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n\n        const actionName = args.substring(0, spaceIndex);\n        const actionFuncStr = args.substring(spaceIndex + 1);\n\n        const evalStr = `Events.Actions.${actionName} = ${actionFuncStr}`;\n\n        Util.log(evalStr);\n\n        eval(evalStr);\n\n        Util.sendDescEmbed(channel, 'Added Action', `Added action ${actionName} for linking`, Util.makeEmbedFooter(speaker), null, colGreen);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/administrator/AddAuto.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';addauto ', ';adda ', ';addtoauto '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Adds a song to the music auto-playlist',\n\n    args: '[song_name]',\n\n    example: 'gonna give you up',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        // if (args.includes('http')) {\n        //     let songId = /[^/=]+$/.exec(args);\n        //     if (songId != null && songId[0]) {\n        //         songId = songId[0];\n        //         index.YtInfo.getById(songId, (error, result) => {\n        //             const songData = result.items[0];\n        //             if (songData != null) {\n        //                 let autoPlaylist = Data.guildGet(guild, Data.playlist, 'songs');\n        //                 if (autoPlaylist == null) {\n        //                     autoPlaylist = [];\n        //                     Data.guildSet(guild, Data.playlist, 'songNum', 0);\n        //                     Data.guildSet(guild, Data.playlist, 'songs', autoPlaylist);\n        //                 }\n        //                 autoPlaylist.push([songData, speaker]);\n        //                 Data.guildSaveData(Data.playlist);\n        //                 Util.sendDescEmbed(channel, `[${autoPlaylist.length}] Auto-Playlist Appended`, songData.snippet.title, Util.makeEmbedFooter(speaker), null, colGreen);\n        //             } else {\n        //                 Util.print(channel, 'Audio not found');\n        //             }\n        //         });\n        //     } else {\n        //         Util.print(channel, 'Incorrect format for URL');\n        //     }\n        // } else {\n        //     index.YtInfo.search(args, 6, (error, result) => {\n        //         if (error) {\n        //             Util.print(channel, error);\n        //         } else {\n        //             const items = result.items;\n        //             let hasFound = false;\n        //             for (let i = 0; i < items.length; i++) {\n        //                 const songData = items[i];\n        //                 if (songData != null && has.call(songData, 'id') && songData.id.kind == 'youtube#video') {\n        //                     hasFound = true;\n        //                     let autoPlaylist = Data.guildGet(guild, Data.playlist, 'songs');\n        //                     if (autoPlaylist == null) {\n        //                         autoPlaylist = [];\n        //                         Data.guildSet(guild, Data.playlist, 'songNum', 0);\n        //                         Data.guildSet(guild, Data.playlist, 'songs', autoPlaylist);\n        //                     }\n        //                     autoPlaylist.push([songData, speaker]);\n        //                     Data.guildSaveData(Data.playlist);\n        //                     Util.sendDescEmbed(channel, `[${autoPlaylist.length}] Auto-Playlist Appended`, songData.snippet.title, Util.makeEmbedFooter(speaker), null, colGreen);\n        //                     break;\n        //                 }\n        //             }\n        //             if (!hasFound) {\n        //                 Util.print(channel, 'Audio not found');\n        //             }\n        //         }\n        //     });\n        // }\n        // // Data.guildSet(guild, playlist, args);\n    },\n});\n"
  },
  {
    "path": "commands/administrator/AddAutoRole.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";setautorole \", \";addautorole \", \";arole \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Set a new autorole\",\n\n    args: \"([role_name]) ([auto_role_name])\",\n\n    example: \"for-hire hire\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var data = Util.getDataFromString(args, [\n            function(str, results) {\n                return Util.getRole(str, guild);\n            },\n        ], true);\n        if (!data) return Util.commandFailed(channel, speaker, \"Role not found\");\n        var role = data[0];\n        var name = data[1].toLowerCase();\n        if (Util.getPosition(speaker) <= role.position) {\n            Util.commandFailed(channel, speaker, \"Role has equal or higher rank\");\n            return;\n        }\n        Data.guildSet(guild, Data.autoRoles, name, role.name);\n        Util.print(channel, \"Added the autorole\", Util.fix(name), \"for the\", role.name, \"role\");\n    }\n});"
  },
  {
    "path": "commands/administrator/AddRole.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';addrole '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Add a role to a user',\n\n    args: '([@user] | [id] | [name]) ([role_name])',\n\n    example: 'vae mod',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str) {\n                    return Util.getMemberByMixed(str, guild);\n                },\n                function (str) {\n                    return Util.getRole(str, guild);\n                },\n            ],\n            false,\n        );\n\n        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n\n        const user = data[0];\n        const role = data[1];\n\n        if (role.name == 'Vashta-Owner' && !Util.isAdmin(speaker)) {\n            return Util.commandFailed(channel, speaker, 'You are not allowed to add this role');\n        }\n\n        if (speaker != user && Util.getPosition(speaker) <= Util.getPosition(user)) {\n            Util.commandFailed(channel, speaker, 'User has equal or higher rank');\n            return false;\n        }\n\n        if (Util.getPosition(speaker) <= role.position) {\n            Util.commandFailed(channel, speaker, 'Role has equal or higher rank');\n            return false;\n        }\n        user.addRole(role).catch(console.error);\n\n        const sendEmbedFields = [\n            { name: 'Username', value: Util.getMention(user) },\n            // {name: \"Moderator\", value: Util.getMention(speaker)},\n            { name: 'Role Name', value: role.name },\n        ];\n        Util.sendEmbed(\n            channel, // Channel Object\n            'Assigned Role', // Title String\n            null, // Description String\n            Util.makeEmbedFooter(speaker), // Username + ID String\n            Util.getAvatar(user), // Avatar URL String\n            colGreen, // Color Number\n            sendEmbedFields,\n        );\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/administrator/Alert.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';alert ', ';dm ', ';announce '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Sends a DM to everyone in the guild with a certain role',\n\n    args: '([@role] | [id] | [name]) ([message])',\n\n    example: 'subscribers new update today',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (speaker.id !== guild.ownerID && speaker.id !== vaebId) return Util.commandFailed(channel, speaker, 'Command is owner-only');\n\n        const data = Util.getDataFromString(args, [\n            function (strOld) {\n                let str = strOld;\n                if (str[0] === '@') str = str.substring(1);\n                return Util.getRole(str, guild);\n            },\n            function (str) {\n                return str;\n            },\n        ], false);\n\n        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n\n        const role = data[0];\n        const message = data[1];\n\n        const title = `Alert | ${Util.getMostName(speaker)} | ${guild.name}`;\n        const footer = Util.makeEmbedFooter(speaker);\n\n        guild.members.forEach((member) => {\n            if (!Util.hasRole(member, role) || member.id === selfId) return;\n            Util.log(`Sent DM to ${Util.getFullName(member)}`);\n            Util.sendDescEmbed(member, title, message, footer, null, colBlue);\n        });\n\n        return undefined;\n    },\n});\n"
  },
  {
    "path": "commands/administrator/AllInfo.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';allinfo'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get guild, role, channel and permission info in one huge set of messages',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const outStr = ['**Guild Info**\\n```'];\n        outStr.push(`Name: ${guild.name}`);\n        outStr.push(`ID: ${guild.id}`);\n        outStr.push(`Owner: ${Util.getName(guild.owner)} (${guild.ownerID})`);\n        outStr.push(`Icon: ${guild.iconURL}`);\n        outStr.push(`AFK timeout: ${guild.afkTimeout} seconds`);\n        outStr.push(`Region: ${guild.region}`);\n        outStr.push(`Member count: ${guild.memberCount}`);\n        outStr.push(`Created: ${guild.createdAt}`);\n        outStr.push(`Main channel: #${guild.defaultChannel.name}`);\n        outStr.push(`Emojis: ${guild.emojis.size > 0 ? JSON.stringify(guild.emojis.array()) : 'null'}`);\n        outStr.push('```');\n        outStr.push('**Guild Text Channels**\\n```');\n        Util.getTextChannels(guild).forEach((value) => {\n            outStr.push(\n                `Channel: ${value.name} (${value.id}) | Topic: ${value.topic} | Position: ${value.position} | Created: ${value.createdAt}`,\n            );\n        });\n        outStr.push('```');\n        outStr.push('**Guild Voice Channels**\\n```');\n        Util.getVoiceChannels(guild).forEach((value) => {\n            outStr.push(\n                `Channel: ${value.name} (${value.id}) | Topic: ${value.topic} | Position: ${value.position} | Created: ${\n                    value.createdAt\n                } | Bitrate: ${value.bitrate}`,\n            );\n        });\n        outStr.push('```');\n        outStr.push('**Guild Roles**\\n```');\n        guild.roles.forEach((value) => {\n            outStr.push(\n                `Role: ${value.name} (${value.id}) | Position: ${value.position} | Mentionable: ${value.mentionable} | Color: ${\n                    value.color\n                }`,\n            );\n        });\n        outStr.push('```');\n        outStr.push('**Guild Permissions**\\n```');\n        guild.roles.forEach((value) => {\n            outStr.push(`Role: ${value.name} (${value.id})`);\n            outStr.push(JSON.stringify(Util.getRolePermissions(value)));\n            outStr.push('');\n        });\n        outStr.push('');\n        outStr.push('-END-');\n        outStr.push('```');\n        Util.print(channel, outStr.join('\\n'));\n    },\n});\n"
  },
  {
    "path": "commands/administrator/Ban.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';ban ', ';banhammer ', ';permaban '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Ban a user from the guild',\n\n    args: '([user_resolvable) (OPTIONAL: [reason])',\n\n    example: 'vaeb being weird',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.trim();\n\n        // const highestRoleLower = speaker.highestRole.name.toLowerCase();\n        // if (!highestRoleLower.includes('head ') && /\\bmod/g.test(highestRoleLower)) return Util.commandFailed(channel, speaker, 'Moderators are not allowed to use the ban command | Please use tempban instead');\n\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str) {\n                    return Util.getMemberByMixed(str, guild) || Util.isId(str);\n                },\n            ],\n            true,\n        );\n        if (!data) return Util.commandFailed(channel, speaker, 'User not found');\n\n        const target = data[0];\n        const reason = data[1];\n\n        Admin.addBan(guild, channel, target, speaker, { reason });\n\n        // if (guild.id == '168742643021512705') index.dailyBans.push([targId, `${targName}#${target.discriminator}`, reason]);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/administrator/Calm.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';calm', ';calmchat', ';slow', ';slowchat'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Slows down chat speed',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    /* func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (speaker.id != guild.ownerID && speaker.id != vaebId) return Util.commandFailed(channel, speaker, \"Command is owner-only\");\n        \n        if (index.slowChat[guild.id]) return;\n\n        var chatQueue = [];\n\n        index.chatQueue[guild.id] = chatQueue;\n\n        index.slowInterval[guild.id] = setInterval(function() {\n            if (chatQueue.length < 1) return;\n\n            var msgObj = (chatQueue.splice(0, 1))[0];\n\n            var msgChannel = msgObj.channel;\n            var msgGuild = msgObj.guild;\n            var msgSpeaker = msgObj.member;\n            var msgContent = msgObj.content;\n            var msgCreatedAt = msgObj.createdAt;\n\n            // Util.sendEmbed(msgChannel, Util.getMostName(msgSpeaker), msgContent, Util.makeEmbedFooter(msgSpeaker, msgCreatedAt), null, colGreen, null);\n            msgChannel.send(Util.getMostName(msgSpeaker) + \": \" + msgContent).catch(console.error);\n        }, index.calmSpeed);\n\n        index.slowChat[guild.id] = true;\n    } */\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        // if (speaker.id != guild.ownerID && speaker.id != vaebId) return Util.commandFailed(channel, speaker, 'Command is owner-only');\n\n        if (index.slowChat[guild.id]) return Util.log('Slow is already active');\n\n        index.chatNext[guild.id] = +new Date() + index.calmSpeed;\n\n        index.slowChat[guild.id] = true;\n\n        return undefined;\n    },\n});\n"
  },
  {
    "path": "commands/administrator/CheckPerms.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";perms \", \";checkperms \", \";getperms \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Get guild and channel permissions for a user\",\n\n    args: \"([@user] | [id] | [name]) ([reason])\",\n\n    example: \"vae\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var member = Util.getMemberByMixed(args, guild);\n        if (member == null) {\n            Util.commandFailed(channel, speaker, \"User not found\");\n            return;\n        }\n        var guildPerms = member.permissions.serialize();\n        var channelPerms = member.permissionsIn(channel).serialize();\n\n        var guildValue = [];\n        var channelValue = [];\n\n        for (let permName in guildPerms) {\n            if (!guildPerms.hasOwnProperty(permName)) continue;\n            let hasPerm = guildPerms[permName];\n            if (!hasPerm) continue;\n            guildValue.push(permName);\n        }\n\n        for (let permName in channelPerms) {\n            if (!channelPerms.hasOwnProperty(permName)) continue;\n            let hasPerm = channelPerms[permName];\n            if (!hasPerm) continue;\n            channelValue.push(permName);\n        }\n\n        Util.sortPerms(guildValue);\n        Util.sortPerms(channelValue);\n\n        var sendEmbedFields = [\n            {name: \"Guild Permissions\", value: \"​\\n\" + guildValue.join(\"\\n\\n\"), inline: false},\n            {name: \"Channel Permissions\", value: \"​\\n\" + channelValue.join(\"\\n\\n\"), inline: false}\n        ];\n\n        Util.sendEmbed(channel, Util.capitalize2(member.displayName + \"'s Permissions\"), null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/administrator/ClearMutes.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';clearmutes ', ';cleanslate ', ';clearhistory '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: \"Clear a user's mute history and unmute them if they are muted\",\n\n    args: '([@user] | [id] | [name])',\n\n    example: 'vae',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.trim();\n\n        Admin.clearMutes(guild, channel, args, speaker);\n    },\n});\n"
  },
  {
    "path": "commands/administrator/Events.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";events\", \";guild events\", \";all events\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Output all events that can be used in ;link\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var sendEmbedFields = [];\n\n        sendEmbedFields.push({name: \"MessageCreate\", value: \"​\", inline: false});\n        sendEmbedFields.push({name: \"MessageDelete\", value: \"​\", inline: false});\n        sendEmbedFields.push({name: \"UserJoin\", value: \"​\", inline: false});\n        sendEmbedFields.push({name: \"UserLeave\", value: \"​\", inline: false});\n        sendEmbedFields.push({name: \"UserMute\", value: \"​\", inline: false});\n        sendEmbedFields.push({name: \"UserUnMute\", value: \"​\", inline: false});\n        sendEmbedFields.push({name: \"UserUnMute\", value: \"​\", inline: false});\n        sendEmbedFields.push({name: \"UserRoleAdd\", value: \"​\", inline: false});\n        sendEmbedFields.push({name: \"UserRoleRemove\", value: \"​\", inline: false});\n        sendEmbedFields.push({name: \"UserNicknameUpdate\", value: \"​\", inline: false});\n\n        Util.sendEmbed(channel, \"Events\", \"All events which can be used in ;link\\n​\", Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/administrator/Fire.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';fire '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Set fire to the server',\n\n    args: '(on | off)',\n\n    example: 'on',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (!args || !/on|true|off|false/i.test(args)) return;\n\n        const isOn = /on|true/i.test(args);\n\n        if (isOn) {\n            guild.channels.forEach((c) => {\n                if (!c.name.endsWith('🔥')) c.setName(`${c.name}🔥`);\n            });\n        } else {\n            guild.channels.forEach((c) => {\n                if (c.name.endsWith('🔥')) c.setName(c.name.substr(0, c.name.length - 1));\n            });\n        }\n    },\n});\n"
  },
  {
    "path": "commands/administrator/FixRoles.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';fix roles'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Fix new role permissions',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const defRole = Util.getRole('@everyone', guild);\n        const sendRole = Util.getRole('SendMessages', guild);\n        const guildRoles = guild.roles;\n        const defNew = {};\n        const sendNew = {};\n\n        // const newRolePerms = [\n        //     'CHANGE_NICKNAME',\n        //     'EMBED_LINKS',\n        //     'SEND_MESSAGES',\n        //     'VIEW_CHANNEL',\n        //     'READ_MESSAGE_HISTORY',\n        //     'ADD_REACTIONS',\n        //     'USE_EXTERNAL_EMOJIS',\n        //     'CONNECT',\n        //     'SPEAK',\n        //     'USE_VAD',\n        // ];\n\n        const newRolePerms = ['CHANGE_NICKNAME', 'SEND_MESSAGES', 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'CONNECT', 'SPEAK', 'USE_VAD'];\n\n        const newRolePermsObj = Util.arrayToObj(newRolePerms);\n\n        sendRole.setPermissions(newRolePerms).catch(error => Util.log(`[E_FixRoles1] ${error}`));\n\n        guildRoles.forEach((role) => {\n            if (role.id == sendRole.id) return;\n            const newPerms = [];\n            const rolePerms = role.serialize();\n            for (const permName in rolePerms) {\n                if (!rolePerms.hasOwnProperty(permName)) continue;\n                if (!newRolePermsObj.hasOwnProperty(permName) && rolePerms[permName] == true) {\n                    newPerms.push(permName);\n                }\n            }\n            role.setPermissions(newPerms).catch(error => Util.log(`[E_FixRoles2] ${error}`));\n        });\n\n        /* var textChannels = Util.getTextChannels(guild);\n        for (var i = 0; i < textChannels.length; i++) {\n            var channel = textChannels[i];\n            Util.setChannelPerms(channel, defRole, defNew);\n            Util.setChannelPerms(channel, sendRole, sendNew);\n        } */\n    },\n});\n"
  },
  {
    "path": "commands/administrator/GetLinks.js",
    "content": "/*\n\n    [\n        [\n            eventName,\n            [actionName, [actionArgs]],\n            [actionName, [actionArgs]],\n            [actionName, [actionArgs]]\n        ],\n        [\n            eventName,\n            [actionName, [actionArgs]],\n            [actionName, [actionArgs]],\n            [actionName, [actionArgs]]\n        ]\n    ]\n\n*/\n\nmodule.exports = Cmds.addCommand({\n    cmds: [\";getlinks\", \";links\", \";triggers\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Output all created links\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var guildEvents = Events.getEvents(guild);\n\n        var sendEmbedFields = [];\n\n        for (let i = 0; i < guildEvents.length; i++) {\n            let eventData = guildEvents[i];\n\n            let eventName = eventData[0];\n\n            let actionStr = [];\n\n            for (let j = 1; j < eventData.length; j++) {\n                let actionData = eventData[j];\n                \n                let actionName = actionData[0];\n                let actionArgs = actionData[1];\n\n                actionStr.push(actionName + \" \" + actionArgs.join(\" \"));\n            }\n\n            sendEmbedFields.push({name: eventName, value: actionStr.join(\"\\n\"), inline: false});\n        }\n\n        Util.sendEmbed(channel, \"Guild Links\", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/administrator/HasPerm.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";haspermission \", \";has permission \", \";hasperm \", \";has perm \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Checks guild member for permission\",\n\n    args: \"([@user] | [id] | [name]) ([perm_name])\",\n\n    example: \"vaeb audit log\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var data = Util.getDataFromString(args, [\n            function(str, results) {\n                return Util.getMemberByMixed(str, guild);\n            },\n            function(str, results) {\n                return Util.strToPerm(str);\n            }\n        ], false);\n        if (!data) return Util.commandFailed(channel, speaker, \"Invalid parameters\");\n\n        var member = data[0];\n        var permName = data[1];\n\n        var hasPermGuild = member.hasPermission(permName, undefined, true, true);\n        var hasPermChannel = member.permissionsIn(channel).has(permName, true);\n\n        var sendEmbedFields = [\n            {name: \"Guild\", value: Util.boolToAns(hasPermGuild), inline: false}\n        ];\n\n        if (Util.textChannelPermissionsObj.hasOwnProperty(permName)) {\n            sendEmbedFields.push({name: \"Channel\", value: Util.boolToAns(hasPermChannel), inline: false});\n        }\n\n        Util.sendEmbed(channel, Util.capitalize2(\"Does \" + member.displayName + \" Have [\" + permName + \"] ?\"), null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/administrator/InitRoles.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';setup', ';init roles', ';initroles'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Assign all SendMessages roles',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        let sendRole = Util.getRole('SendMessages', guild);\n\n        if (sendRole != null) {\n            Util.initRoles(sendRole, guild, channel);\n            return;\n        }\n\n        const newRolePerms = ['CHANGE_NICKNAME', 'SEND_MESSAGES', 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'CONNECT', 'SPEAK', 'USE_VAD'];\n\n        try {\n            sendRole = await guild.createRole({\n                name: 'SendMessages',\n                hoist: false,\n                position: 1,\n                permissions: newRolePerms,\n                mentionable: false,\n            });\n\n            Util.initRoles(sendRole, guild, channel);\n        } catch (err) {\n            console.log('InitRolesCmd Error:', err);\n        }\n    },\n});\n"
  },
  {
    "path": "commands/administrator/Link.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';link ', ';addlink', ';trigger ', ';event '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Link an event to an action',\n\n    args: '({ [event_name_1] ... [event_name_n] }) ({ [action_1_name] [action_1_param_1] ... [action_1_param_o] }) ... ({ [action_p_name] [action_p_param_1] ... [action_p_param_q] })',\n    // args: \"([event_name_1] ... [event_name_n]) (-[action_1_name] [action_1_param_1] ... [action_param_n]) ... (-[action_n_name] [action_n_param_1] ... [action_n_param_o])\",\n\n    example: '{ UserJoin UserUnMute } { AddRole SendMessages RandomRole } { DM CmdInfo }',\n    // example: \"UserJoin UserUnMute -AddRole SendMessages RandomRole -DM CmdInfo\",\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        let event = null;\n        const actions = [];\n\n        let numOpen = 0;\n        let lastOpen = 0;\n\n        for (let i = 0; i < args.length; i++) {\n            const char = args[i];\n\n            if (char == '{') {\n                if (numOpen == 0) {\n                    lastOpen = i;\n                }\n\n                numOpen++;\n            } else if (char == '}') {\n                numOpen--;\n\n                if (numOpen == 0) {\n                    const paramStr = args.substring(lastOpen + 1, i);\n\n                    if (event == null) {\n                        event = paramStr.trim().split(' ');\n                    } else {\n                        actions.push(paramStr.trim().split(' '));\n                    }\n                }\n            }\n        }\n\n        if (event == null || event.length == 0) {\n            return Util.commandFailed(channel, speaker, 'Invalid parameters: Event not provided');\n        } else if (actions.length == 0) {\n            return Util.commandFailed(channel, speaker, 'Invalid parameters: Action(s) not provided');\n        }\n\n        Util.log(event);\n        Util.log(actions);\n\n        for (let i = 0; i < event.length; i++) {\n            const eventName = event[i];\n\n            for (let j = 0; j < actions.length; j++) {\n                const actionData = actions[j];\n\n                const actionName = actionData[0];\n                const actionFunc = Events.Actions[actionName];\n                const actionArgs = actionData.splice(1);\n\n                if (!actionFunc) {\n                    Util.sendDescEmbed(channel, 'Action Not Found', actionName, Util.makeEmbedFooter(speaker));\n                    continue;\n                }\n\n                Events.addEvent(guild, eventName, actionName, actionFunc, actionArgs);\n\n                const sendEmbedFields = [];\n\n                sendEmbedFields.push({ name: 'Event', value: eventName, inline: false });\n                sendEmbedFields.push({ name: 'Action', value: actionName, inline: false });\n\n                Util.sendEmbed(channel, 'Created Link', null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n            }\n        }\n    },\n});\n"
  },
  {
    "path": "commands/administrator/Permissions.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';perms '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get guild and channel permissions for a user',\n\n    args: '([@user] | [id] | [name])',\n\n    example: 'vae',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const member = Util.getMemberByMixed(args, guild);\n        if (member == null) {\n            Util.commandFailed(channel, speaker, 'User not found');\n            return;\n        }\n        const guildPerms = member.permissions.serialize();\n        const channelPerms = member.permissionsIn(channel).serialize();\n\n        const guildValue = [];\n        const channelValue = [];\n\n        for (const [permName, hasPerm] of Object.entries(guildPerms)) {\n            if (!hasPerm) continue;\n            guildValue.push(permName);\n        }\n\n        for (const [permName, hasPerm] of Object.entries(channelPerms)) {\n            if (!hasPerm) continue;\n            channelValue.push(permName);\n        }\n\n        Util.sortPerms(guildValue);\n        Util.sortPerms(channelValue);\n\n        const sendEmbedFields = [\n            { name: 'Guild Permissions', value: `​\\n${guildValue.join('\\n\\n')}`, inline: false },\n            { name: 'Channel Permissions', value: `​\\n${channelValue.join('\\n\\n')}`, inline: false },\n        ];\n\n        Util.sendEmbed(channel, Util.capitalize2(`${member.displayName}'s Permissions`), null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);\n    },\n});\n"
  },
  {
    "path": "commands/administrator/RaveBan.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';raveban ', ';crabban ', ';crabrave ', ';rave '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Crab rave a user from the guild',\n\n    args: '([user_resolvable) (OPTIONAL: [reason])',\n\n    example: 'vaeb being weird',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    /*\n\n        Include:\n            -Server gets renamed [DONE]\n            -Server icon changes\n            -Channel(s) get renamed\n            -Channel descriptions change\n            -Spam crab rave gif\n            -(TTS?) MSG: USER IS GONE [DONE]\n            -Target role gets role: [MID]\n                -Name: USER IS GONE [DONE]\n                -Rave colours\n            -Whenever they speak VaeBot sends a reply message?\n\n    */\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.trim();\n\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str) {\n                    return Util.getMemberByMixed(str, guild) || Util.isId(str);\n                },\n            ],\n            true,\n        );\n        if (!data) return Util.commandFailed(channel, speaker, 'User not found');\n\n        const target = data[0];\n        const reason = data[1];\n\n        index.crabRave.goneId = target.id;\n        index.crabRave.goneName = target.displayName.toUpperCase();\n        index.crabRave.goneGuild = guild.id;\n\n        await Admin.addBan(guild, channel, target, speaker, { reason }); // Don't actually ban them for now...\n\n        const crabRaveGif = './resources/images/CrabRaveGif.gif';\n\n        const goneRole = guild.roles.find(r => / gone(?: \\S*)?$/i.test(r.name));\n\n        await goneRole.setName(`🦀 ${index.crabRave.goneName} IS GONE 🦀`);\n\n        target.addRole(goneRole).catch(console.error); // YOUSEEF IS GONE\n\n        let count = 0;\n\n        const intervalFunc = () => {\n            channel.send(`🦀 ${index.crabRave.goneName} IS GONE 🦀`, { tts: count++ % 1 == 0, files: [crabRaveGif] }).catch(console.error);\n        };\n\n        index.crabRave.interval = setInterval(intervalFunc, 5000);\n\n        channel.setName('🦀🦀🦀').catch(console.error);\n        channel.setTopic(`🦀 ${index.crabRave.goneName} IS GONE 🦀`).catch(console.error);\n\n        guild.setName(`🦀 ${index.crabRave.goneName} IS GONE 🦀`).catch(console.error);\n\n        intervalFunc();\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/administrator/RemAuto.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';remauto ', ';rema '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Remove a song from the music auto-playlist',\n\n    args: '[song_name]',\n\n    example: 'gonna give you up',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        // args = args.toLowerCase();\n        // var autoPlaylist = Data.guildGet(guild, Data.playlist);\n        // var autoSongs = autoPlaylist.songs;\n        // for (var i = autoSongs.length-1; i >= 0; i--) {\n        //     var newSong = autoSongs[i];\n        //     var songData = newSong[0];\n        //     var author = newSong[1];\n        //     var title = songData.snippet.title;\n        //     if (title.toLowerCase().indexOf(args) >= 0) {\n        //         Util.print(channel, \"Removed\", title, \"from the auto-playlist\");\n        //         autoSongs.splice(i, 1);\n        //     }\n        // }\n        // Data.guildSaveData(Data.playlist);\n    },\n});\n"
  },
  {
    "path": "commands/administrator/RemAutoRole.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";remautorole \", \";delautorole \", \"aroledel \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Remove an autorole\",\n\n    args: \"([auto_role_name])\",\n\n    example: \"hire\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var prop = args.toLowerCase();\n        var guildAutoRoles = Data.guildGet(guild, Data.autoRoles);\n        if (!guildAutoRoles.hasOwnProperty(prop)) {\n            Util.commandFailed(channel, speaker, \"AutoRole not found\");\n            return;\n        }\n        Data.guildDelete(guild, Data.autoRoles, prop);\n        saveAutoRoles();\n        Util.print(channel, \"Removed the\", Util.fix(prop), \"autorole\");\n    }\n});\n"
  },
  {
    "path": "commands/administrator/RemRole.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';remrole ', ';removerole ', ';delrole '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Remove a role from a user',\n\n    args: '([@user] | [id] | [name]) ([role_name])',\n\n    example: 'vae mod',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str) {\n                    return Util.getMemberByMixed(str, guild);\n                },\n                function (str) {\n                    return Util.getRole(str, guild);\n                },\n            ],\n            false,\n        );\n\n        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n\n        const user = data[0];\n        const role = data[1];\n\n        if (role.name == 'Vashta-Owner' && !Util.isAdmin(speaker)) {\n            return Util.commandFailed(channel, speaker, 'You are not allowed to remove this role');\n        }\n\n        if (speaker != user && Util.getPosition(speaker) <= Util.getPosition(user)) {\n            Util.commandFailed(channel, speaker, 'User has equal or higher rank');\n            return false;\n        }\n\n        if (Util.getPosition(speaker) <= role.position) {\n            Util.commandFailed(channel, speaker, 'Role has equal or higher rank');\n            return false;\n        }\n        user.removeRole(role).catch(console.error);\n\n        const sendEmbedFields = [{ name: 'Username', value: Util.getMention(user) }, { name: 'Role Name', value: role.name }];\n        Util.sendEmbed(\n            channel, // Channel Object\n            'Removed Role', // Title String\n            null, // Description String\n            Util.makeEmbedFooter(speaker), // Username + ID String\n            Util.getAvatar(user), // Avatar URL String\n            colGreen, // Color Number\n            sendEmbedFields,\n        );\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/administrator/RolePerms.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";roleperms\", \";rolepermissions\", \";gperms\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Get all role permissions\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var outStr = [];\n        outStr.push(\"**Guild permissions:**\\n```\");\n        guild.roles.forEach(function(value, index, self) {\n            outStr.push(\"Role: \" + value.name + \" (\" + value.id + \")\");\n            outStr.push(JSON.stringify(value.serialize()));\n            outStr.push(\"\");\n        });\n        outStr.push(\"```\");\n        Util.print(channel, outStr.join(\"\\n\"));\n    }\n});"
  },
  {
    "path": "commands/administrator/Switch.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';switch'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Specific command',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (guild.id === '477270527535480834' && (speaker.id === vaebId || speaker.id === '75743432164773888' || speaker.id === '87185859949899776')) {\n            const salesChannel = Util.findChannel('477270527535480834', guild);\n            if (salesChannel) {\n                if (salesChannel.name.includes('open')) {\n                    salesChannel.setName('sales_closed')\n                    .catch(console.error);\n                } else {\n                    salesChannel.setName('sales_open')\n                    .catch(console.error);\n                }\n            }\n        }\n    },\n});\n"
  },
  {
    "path": "commands/administrator/UnCalm.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';uncalm', ';uncalmchat', ';unslow', ';unslowchat'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Removes chat slowdown',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    /* func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (speaker.id != guild.ownerID && speaker.id != vaebId) return Util.commandFailed(channel, speaker, \"Command is owner-only\");\n        \n        if (!index.slowChat[guild.id]) return;\n        \n        index.slowChat[guild.id] = null;\n        index.chatQueue[guild.id] = null;\n        clearInterval(index.slowInterval[guild.id]);\n        index.slowInterval[guild.id] = null;\n    } */\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        // if (speaker.id != guild.ownerID && speaker.id != vaebId) return Util.commandFailed(channel, speaker, \"Command is owner-only\");\n\n        if (!index.slowChat[guild.id]) return;\n\n        index.slowChat[guild.id] = null;\n        index.chatNext[guild.id] = null;\n    },\n});\n"
  },
  {
    "path": "commands/administrator/UnLink.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';unlink ', ';remlink ', ';dellink ', ';untrigger ', ';unevent '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'UnLink an event from an action',\n\n    args: '({ [event_name_1] ... [event_name_n] }) ({ [action_1_name] }) ... ({ [action_o_name] })',\n    // args: \"([event_name_1] ... [event_name_n]) (-[action_1_name] [action_1_param_1] ... [action_param_n]) ... (-[action_n_name] [action_n_param_1] ... [action_n_param_o])\",\n\n    example: '{ UserJoin UserUnMute } { AddRole } { DM }',\n    // example: \"UserJoin UserUnMute -AddRole SendMessages RandomRole -DM CmdInfo\",\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        let event = null;\n        const actions = [];\n\n        let numOpen = 0;\n        let lastOpen = 0;\n\n        for (let i = 0; i < args.length; i++) {\n            const char = args[i];\n\n            if (char == '{') {\n                if (numOpen == 0) {\n                    lastOpen = i;\n                }\n\n                numOpen++;\n            } else if (char == '}') {\n                numOpen--;\n\n                if (numOpen == 0) {\n                    const paramStr = args.substring(lastOpen + 1, i);\n\n                    if (event == null) {\n                        event = paramStr.trim().split(' ');\n                    } else {\n                        actions.push(paramStr.trim().split(' '));\n                    }\n                }\n            }\n        }\n\n        if (event == null || event.length == 0) {\n            return Util.commandFailed(channel, speaker, 'Invalid parameters: Event not provided');\n        }\n\n        Util.log(event);\n        Util.log(actions);\n\n        for (let i = 0; i < event.length; i++) {\n            const eventName = event[i];\n\n            const sendEmbedFields = [];\n\n            sendEmbedFields.push({ name: 'Event', value: eventName, inline: false });\n\n            if (actions.length == 0) {\n                Events.remEvent(guild, eventName);\n            } else {\n                for (let j = 0; j < actions.length; j++) {\n                    const actionData = actions[i];\n                    const actionName = actionData[0];\n\n                    Events.remEvent(guild, eventName, actionName);\n\n                    sendEmbedFields.push({ name: 'Action', value: actionName, inline: false });\n                }\n            }\n\n            Util.sendEmbed(channel, 'Removed Link', null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n        }\n    },\n});\n"
  },
  {
    "path": "commands/administrator/UnRaveBan.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';unraveban', ';uncrabban', ';uncrabrave', ';unrave'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Stop the party',\n\n    args: '',\n\n    example: 'vaeb being weird',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const target = guild.members.get(index.crabRave.goneId);\n\n        if (target) {\n            const goneRole = guild.roles.find(r => / gone(?: \\S*)?$/i.test(r.name));\n            target.removeRole(goneRole).catch(console.error);\n        }\n\n        index.crabRave.goneId = null;\n        index.crabRave.goneName = null;\n        index.crabRave.goneGuild = null;\n\n        clearInterval(index.crabRave.interval);\n        index.crabRave.interval = null;\n\n        channel.setName('general').catch(console.error);\n        channel\n            .setTopic(\"Main channel. Avoid using bot commands here if they're getting spammy or are annoying users.\")\n            .catch(console.error);\n\n        guild.setName('Vashta').catch(console.error);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/locked/Bother.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';bother '],\n\n    requires: {\n        guild: false,\n        loud: false,\n    },\n\n    desc: 'Hi friend',\n\n    args: '([@user] | [id] | [name])',\n\n    example: 'vae',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const target = Util.getEitherByMixed(args, guild);\n\n        if (target == null) return Util.commandFailed(channel, speaker, 'User not found');\n\n        const targName = Util.getName(target);\n\n        Util.print(channel, 'Bothering', targName);\n\n        let n = 0;\n\n        const interval = setInterval(() => {\n            if (n > 50) {\n                clearInterval(interval);\n                return;\n            }\n\n            let sendStr = [];\n\n            while (true) {\n                let rand = String(Math.random());\n                const dotPos = rand.indexOf('.');\n                if (dotPos) rand = rand.substring(dotPos + 1);\n                const subStr = 'HI FRIEND!';\n                if ((sendStr.join('\\n') + '\\n' + subStr).length >= 2000) break;\n                sendStr.push(subStr);\n            }\n\n            sendStr = sendStr.join('\\n');\n\n            try {\n                Util.print(target, sendStr);\n            } catch (err) {\n                Util.log(`BOTHER ERROR: ${err}`);\n            }\n            n += 1;\n        }, 50);\n\n        return undefined;\n    },\n});\n"
  },
  {
    "path": "commands/locked/Change.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';changeperms '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Change an existing something',\n\n    args: '([channelId]) ([memberOrRoleId]) ([permEnable1]) ([permEnable2]) ([permEnable3])',\n\n    example: '123456789 987654321 SEND_MESSAGES VIEW_CHANNEL',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str) {\n                    return Util.findChannel(str, guild);\n                },\n                function (str) {\n                    return Util.getMemberOrRoleByMixed(str, guild);\n                },\n                function (str) {\n                    return str;\n                },\n            ],\n            false,\n        );\n        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n\n        const newChannel = data[0];\n        const userOrRole = data[1];\n        const permStr = data[2];\n\n        const permSplit = permStr.split(' ');\n        const permObj = {};\n\n        for (let i = 0; i < permSplit.length; i++) {\n            const keySplit = permSplit[i].split(':');\n            if (keySplit.length == 2) {\n                const keyUpper = keySplit[0].toUpperCase();\n                const valLower = keySplit[1].toLowerCase();\n                if (valLower == 'true') {\n                    Util.log(`Changing ${keyUpper} to ${valLower}`);\n                    permObj[keyUpper] = true;\n                } else if (valLower == 'false') {\n                    Util.log(`Changing ${keyUpper} to ${valLower}`);\n                    permObj[keyUpper] = false;\n                }\n            }\n        }\n\n        Util.setChannelPerms(newChannel, userOrRole, permObj);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/locked/Commit.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';commit ', ';editr '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Commit some changes',\n\n    args: '([roleName]) ([newName]) ([newColor]) ([newHoist]) ([newMentionable]) ([newPosition])',\n\n    example: 'Vaeben Gaeben 0xFF0000 null null 3',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(args, [\n            function (str) {\n                return Util.getRole(str, guild);\n            },\n            function (str) {\n                return str;\n            },\n            function (str) {\n                if (str === 'null') return str;\n                return Util.getNum(str);\n            },\n            function (str) {\n                if (str === 'null') return str;\n                return Util.toBoolean(str);\n            },\n            function (str) {\n                if (str === 'null') return str;\n                return Util.toBoolean(str);\n            },\n            function (str) {\n                if (str === 'null') return str;\n                return Util.getNum(str);\n            },\n        ], false);\n        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n\n        for (let i = 1; i < data.length; i++) {\n            data[i] = data[i] !== 'null' ? data[i] : null;\n        }\n\n        const role = data[0];\n        const name = data[1];\n        const color = data[2];\n        const hoist = data[3];\n        const mentionable = data[4];\n        const pos = data[5];\n\n        if (!role) {\n            return Util.commandFailed(channel, speaker, 'Invalid parameters');\n        }\n\n        if (name) {\n            role.setName(name)\n            .catch(error => Util.log(`[E_RoleComm1] ${error}`));\n        }\n\n        if (color) {\n            role.setColor(color)\n            .catch(error => Util.log(`[E_RoleComm2] ${error}`));\n        }\n\n        if (hoist) {\n            role.setHoist(hoist)\n            .catch(error => Util.log(`[E_RoleComm3] ${error}`));\n        }\n\n        if (mentionable) {\n            role.setMentionable(mentionable)\n            .catch(error => Util.log(`[E_RoleComm4] ${error}`));\n        }\n\n        if (pos) {\n            role.setPosition(pos)\n            .catch(error => Util.log(`[E_RoleComm5] ${error}`));\n        }\n\n        return undefined;\n    },\n});\n"
  },
  {
    "path": "commands/locked/Create.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";create \", \";make \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Create something new\",\n\n    args: \"([newColor]) ([newHoist]) ([newPosition]) ([newName])\",\n\n    example: \"0xFF0000 false 3 Vaeben\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var data = Util.getDataFromString(args, [\n            function(str, results) {\n                return Util.getNum(str);\n            },\n            function(str, results) {\n                return Util.toBoolean(str);\n            },\n            function(str, results) {\n                return Util.getNum(str);\n            },\n            function(str, results) {\n                return str;\n            }\n        ], false);\n        if (!data) return Util.commandFailed(channel, speaker, \"Invalid parameters\");\n\n        var color = data[0];\n        var hoist = data[1];\n        var pos = data[2];\n        var name = data[3];\n\n        Util.log(pos);\n\n        guild.createRole({\n            name: name,\n            color: color,\n            hoist: hoist,\n            mentionable: true,\n            permissions: [],\n            position: pos\n        })\n        .then(role => {\n            role.setPosition(pos)\n            .catch(console.error);\n        })\n        .catch(error => Util.log(\"[E_CreateRole2] \" + error));\n    }\n});"
  },
  {
    "path": "commands/locked/DisabR.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';disabr '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Change even more existing somethings',\n\n    args: '([guildId]) ([roleName]) ([permEnable1]) ([permEnable2]) ([permEnable3])',\n\n    example: '123456789 Vaeben SEND_MESSAGES VIEW_CHANNEL',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str, results) {\n                    return client.guilds.get(str);\n                },\n                function (str, results) {\n                    return Util.getRole(str, results[0]);\n                },\n            ],\n            true,\n        );\n        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n        const newGuild = data[0];\n        const newRole = data[1];\n        const newPerms = data[2];\n\n        const setPerms = [];\n        const pattern = /[\\w_]+/g;\n\n        let matchData;\n        const allMatches = {};\n\n        while ((matchData = pattern.exec(newPerms))) {\n            const permName = matchData[0];\n            if (Util.rolePermissionsObj[permName]) {\n                Util.log(`Disabled ${permName} permission for ${newRole.name} role`);\n                allMatches[permName] = true;\n            }\n        }\n\n        for (let i = 0; i < Util.rolePermissions.length; i++) {\n            const permName = Util.rolePermissions[i];\n            if (newRole.hasPermission(permName) && !allMatches.hasOwnProperty(permName)) {\n                setPerms.push(permName);\n            }\n        }\n\n        newRole.setPermissions(setPerms).catch(error => Util.log(`[E_DisabR] ${error}`));\n    },\n});\n"
  },
  {
    "path": "commands/locked/Echo.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";echo \"],\n\n    requires: {\n        guild: false,\n        loud: false\n    },\n\n    desc: \"Echo text into another channel\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var channels = client.channels;\n        var data = Util.getDataFromString(args, [\n            function(str, results) {\n                var newChannel = channels.get(str);\n                return newChannel;\n            },\n            function(str, results) {\n                return str;\n            }\n        ], false);\n        if (!data) return Util.commandFailed(channel, speaker, \"User not found\");\n        //msgObj.delete();\n        var newChannel = data[0];\n        var msg = data[1];\n        Util.log(\"echo'd \" + msg);\n        Util.print(newChannel, msg);\n    }\n});"
  },
  {
    "path": "commands/locked/Effect.js",
    "content": "function byte2Hex(n) {\n    const nybHexString = '0123456789ABCDEF';\n    return String(nybHexString.substr((n >> 4) & 0x0F, 1)) + nybHexString.substr(n & 0x0F, 1);\n}\n\nfunction RGB2Color(r, g, b) {\n    return `#${byte2Hex(r)}${byte2Hex(g)}${byte2Hex(b)}`;\n}\n\nfunction colorText(i, maxExclusive, phaseParam) {\n    let phase = phaseParam;\n    if (phase == null) phase = 0;\n\n    const center = 128;\n    const width = 127;\n    const frequency = (Math.PI * 2) / maxExclusive;\n\n    const red = Math.sin(frequency * i + 2 + phase) * width + center;\n    const green = Math.sin(frequency * i + 0 + phase) * width + center;\n    const blue = Math.sin(frequency * i + 4 + phase) * width + center;\n\n    const hexColor = RGB2Color(red, green, blue);\n\n    return hexColor;\n}\n\nfunction setRoleColor(role, i, maxExclusive) {\n    let nowIter = i;\n\n    const newColor = colorText(nowIter, maxExclusive, 0);\n\n    nowIter++;\n\n    if (nowIter === maxExclusive) nowIter = 0;\n\n    // role.setColor(newColor)\n    // .catch(console.error); // 168, 184, 560, 175, 231, 179\n\n    // setTimeout(setRoleColor, 50, role, nowIter, maxExclusive);\n\n    role.setColor(newColor)\n        .then(() => setRoleColor(role, nowIter, maxExclusive))\n        .catch((err) => {\n            Util.log(err);\n            Util.log('^ This is from setRoleColor');\n        });\n}\n\nmodule.exports = Cmds.addCommand({\n    cmds: [';effect', ';color', ';addeffect'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Magic',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const Vaeben = Util.getRole('Vaeben', guild);\n        setRoleColor(Vaeben, 0, 100);\n    },\n});\n"
  },
  {
    "path": "commands/locked/EnabR.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';enabr ', ';setr '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Change more existing somethings',\n\n    args: '([guildId]) ([roleName]) ([permEnable1]) ([permEnable2]) ([permEnable3])',\n\n    example: '123456789 Vaeben SEND_MESSAGES VIEW_CHANNEL',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str, results) {\n                    return client.guilds.get(str);\n                },\n                function (str, results) {\n                    return Util.getRole(str, results[0]);\n                },\n            ],\n            true,\n        );\n        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n\n        const newGuild = data[0];\n        const newRole = data[1];\n        const newPerms = data[2];\n\n        const rolePerms = newRole.serialize();\n\n        const setPerms = [];\n\n        const pattern = /[\\w_]+/g;\n\n        for (const permName in rolePerms) {\n            if (!rolePerms.hasOwnProperty(permName)) continue;\n            if (rolePerms[permName]) {\n                setPerms.push(permName);\n            }\n        }\n\n        let matchData;\n        while ((matchData = pattern.exec(newPerms))) {\n            const permName = matchData[0];\n            if (Util.rolePermissionsObj[permName] && !newRole.hasPermission(permName)) {\n                setPerms.push(permName);\n                Util.log(`Enabled ${permName} permission for ${newRole.name} role`);\n            }\n        }\n\n        Util.log(setPerms);\n\n        newRole.setPermissions(setPerms).catch(console.error);\n    },\n});\n"
  },
  {
    "path": "commands/locked/Eval.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';eval ', ';run ', ';exec '],\n\n    requires: {\n        guild: false,\n        loud: false,\n    },\n\n    desc: 'Execute JavaScript code',\n\n    args: '[code]',\n\n    example: 'return 5*2;',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const print = (...args2) => Util.print(channel, ...args2);\n        args = `(async function() {\\n${args}\\n})()`;\n        const outStr = ['**Output:**'];\n        eval(args)\n            .then((result) => {\n                Util.log('Eval result:', result);\n                outStr.push('```');\n                outStr.push(Util.format(result));\n                outStr.push('```');\n                if (result !== undefined) Util.print(channel, outStr.join('\\n'));\n            })\n            .catch((err) => {\n                Util.log('Eval Error:');\n                Util.log(err);\n            });\n    },\n});\n"
  },
  {
    "path": "commands/locked/Lua.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";lua \"],\n\n    requires: {\n        guild: false,\n        loud: false\n    },\n\n    desc: \"Execute Lua code\",\n\n    args: \"[code]\",\n\n    example: \"print(\\\"Hello, world!\\\")\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (args.substr(0, 3) == \"-h \") {\n            var url = args.substring(3);\n            index.Request(url, function(error, response, body) {\n                if (error) {\n                    Util.print(channel, \"[HTTP]\", error);\n                } else {\n                    Util.runLua(body, channel);\n                }\n            });\n        } else {\n            Util.runLua(args, channel);\n        }\n    }\n});"
  },
  {
    "path": "commands/locked/PSA.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";psa\"],\n\n    requires: {\n        guild: false,\n        loud: false\n    },\n\n    desc: \"PSA when restarting the bot for an update\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        Util.sendDescEmbed(channel, null, \"Restarting the bot for an update, it will be down for a few seconds\", null, null, null);\n    }\n});"
  },
  {
    "path": "commands/locked/Reminder.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";reminder\"],\n\n    requires: {\n        guild: false,\n        loud: false\n    },\n\n    desc: \"Note to self\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var sendEmbedFields = [];\n\n        sendEmbedFields.push({name: \"Create\", value: \";create newColor newHoist newPosition newName\", inline: false});\n        sendEmbedFields.push({name: \"Commit\", value: \";commit roleName newName newColor newHoist newMentionable newPosition\", inline: false});\n        sendEmbedFields.push({name: \"EnabR\", value: \";enabr guildId roleName permEnable1 permEnable2 permEnable3\", inline: false});\n        sendEmbedFields.push({name: \"DisabR\", value: \";disabr guildId roleName permEnable1 permEnable2 permEnable3\", inline: false});\n        sendEmbedFields.push({name: \"Set\", value: \";set channelId memberId permEnable1 permEnable2 permEnable3\", inline: false});\n        sendEmbedFields.push({name: \"Change\", value: \";change channelId memberId permEnable1 permEnable2 permEnable3\", inline: false});\n    \n    Util.sendEmbed(channel, \"Reminder\", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/locked/Set.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';set '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Change an existing something',\n\n    args: '([channelId]) ([memberOrRoleId]) ([permEnable1]) ([permEnable2]) ([permEnable3])',\n\n    example: '123456789 987654321 SEND_MESSAGES VIEW_CHANNEL',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str, results) {\n                    return Util.findChannel(str, guild);\n                },\n                function (str, results) {\n                    return Util.getMemberOrRoleByMixed(str, guild);\n                },\n                function (str, results) {\n                    return str;\n                },\n            ],\n            false,\n        );\n        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n\n        const newChannel = data[0];\n        const userOrRole = data[1];\n        const permStr = data[2];\n\n        const permSplit = permStr.split(' ');\n        const permObj = {};\n\n        for (var i = 0; i < permSplit.length; i++) {\n            const keySplit = permSplit[i].split(':');\n            if (keySplit.length == 2) {\n                const keyUpper = keySplit[0].toUpperCase();\n                const valLower = keySplit[1].toLowerCase();\n                if (valLower == 'true') {\n                    Util.log(`Setting ${keyUpper} to ${valLower}`);\n                    permObj[keyUpper] = true;\n                } else if (valLower == 'false') {\n                    Util.log(`Setting ${keyUpper} to ${valLower}`);\n                    permObj[keyUpper] = false;\n                }\n            }\n        }\n\n        for (var i = 0; i < Util.textChannelPermissions.length; i++) {\n            const permName = Util.textChannelPermissions[i];\n            if (!permObj.hasOwnProperty(permName)) permObj[permName] = null;\n        }\n\n        Util.setChannelPerms(newChannel, userOrRole, permObj);\n    },\n});\n"
  },
  {
    "path": "commands/locked/Username.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";username \"],\n\n    requires: {\n        guild: false,\n        loud: false\n    },\n\n    desc: \"Set VaeBot's username\",\n\n    args: \"[username]\",\n\n    example: \"VaeBot9000\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        client.user.setUsername(args)\n            .catch(console.error);\n        Util.print(channel, \"Set username to \" + args);\n    }\n});"
  },
  {
    "path": "commands/public/AutoPlaylist.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";autoplaylist\", \";ap\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Output all the bangin' tunes in the auto-playlist\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var autoPlaylist = Data.guildGet(guild, Data.playlist);\n        var autoSongs = autoPlaylist.songs;\n\n        var sendEmbedFields = [];\n\n        for (var i = 0; i < autoSongs.length; i++) {\n            var songData = autoSongs[i][0];\n            var author = autoSongs[i][1];\n            sendEmbedFields.push({name: \"[\" + (i+1) + \"] \" + songData.snippet.title, value: \"​\", inline: false});\n        }\n\n        Util.sendEmbed(channel, \"Auto-Playlist\", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/public/AutoRoles.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";autoroles\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Get all autoroles (roles which users are allowed to assign to themselves)\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var sendEmbedFields = [];\n        var guildAutoRoles = Data.guildGet(guild, Data.autoRoles);\n\n        for (var name in guildAutoRoles) {\n            if (!guildAutoRoles.hasOwnProperty(name)) continue;\n            sendEmbedFields.push({name: name, value: \"Role: \" + guildAutoRoles[name], inline: false});\n        }\n\n        Util.sendEmbed(channel, \"AutoRoles\", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/public/Channels.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';channels'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get all guild channels',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const outStr = [];\n        outStr.push('**Guild text channels:**\\n```');\n        Util.getTextChannels(guild).forEach((tChannel) => {\n            outStr.push(\n                `Channel: ${tChannel.name} (${tChannel.id}) | Topic: ${tChannel.topic} | Position: ${tChannel.position} | Created: ${\n                    tChannel.createdAt\n                }`,\n            );\n        });\n        outStr.push('```');\n        outStr.push('**Guild voice channels:**\\n```');\n        Util.getVoiceChannels(guild).forEach((vChannel) => {\n            outStr.push(\n                `Channel: ${vChannel.name} (${vChannel.id}) | Topic: ${vChannel.topic} | Position: ${vChannel.position} | Created: ${\n                    vChannel.createdAt\n                } | Bitrate: ${vChannel.bitrate}`,\n            );\n        });\n        outStr.push('```');\n        Util.print(channel, outStr.join('\\n'));\n    },\n});\n"
  },
  {
    "path": "commands/public/CloseTicket.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';closeticket ', ';closesupport ', ';stopticket ', ';endticket ', ';close '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Create a ticket to be viewed by Support',\n\n    args: '([ticket_details])',\n\n    example: 'Am I able to give away my whitelist?',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        if (!Util.checkStaff(guild, speaker) && !Util.hasRoleName(speaker, 'Support')) {\n            return Util.commandFailed(channel, speaker, 'This command can only be used by Support and above');\n        }\n\n        const data = Util.getDataFromString(args, [\n            function (str) {\n                return (str.match(/\\d*(?:\\.\\d+)?/) || [])[0];\n            },\n        ], false);\n\n        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');\n\n        const numTicket = data[0];\n\n        const foundTicket = ((await Data.getRecords(guild, 'tickets', { ticket_id: numTicket })) || [])[0];\n\n        if (!foundTicket) return Util.commandFailed(channel, speaker, `Ticket #${numTicket} does not exist`);\n        if (!foundTicket.active) return Util.commandFailed(channel, speaker, `Ticket #${numTicket} is already closed`);\n\n        Data.updateRecords(guild, 'tickets', {\n            ticket_id: numTicket,\n        }, {\n            active: 0,\n        });\n\n        const sendEmbedFields = [\n            { name: 'Ticket User', value: `<@${foundTicket.user_id}>`, inline: false },\n            { name: 'Ticket Info', value: `${foundTicket.description}`, inline: false },\n            { name: 'Ticket Opened', value: Util.getDateString(new Date(foundTicket.open_tick)), inline: false },\n        ];\n        Util.sendEmbed(channel, `Closed Ticket #${foundTicket.ticket_id}`, null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/public/Commands.js",
    "content": "const commands = Cmds.commands;\n\nmodule.exports = Cmds.addCommand({\n    cmds: [';cmds', ';commands', ';help'],\n\n    requires: {\n        guild: false,\n        loud: true,\n    },\n\n    desc: 'Output all commands',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (Util.isLoud(channel) && !Util.checkStaff(guild, speaker)) return;\n\n        const separator = ' OR ';\n\n        const botUser = Util.getMemberById(selfId, guild);\n\n        const sendEmbedFields1 = [];\n        const sendEmbedFields2 = [];\n        const sendEmbedFields3 = [];\n        const sendEmbedFields4 = [];\n\n        for (let i = 0; i < commands.length; i++) {\n            const cmdData = commands[i];\n\n            const cmdNames = cmdData[0];\n            const cmdRequires = cmdData[2];\n            const cmdDesc = cmdData[3];\n\n            const trimCmds = [];\n\n            for (let c = 0; c < cmdNames.length; c++) {\n                trimCmds.push(cmdNames[c].trim());\n            }\n\n            const embedField = { name: trimCmds.join(separator), value: cmdDesc, inline: false };\n\n            if (cmdRequires.vaeb) {\n                sendEmbedFields1.push(embedField);\n            } else if (cmdRequires.administrator) {\n                sendEmbedFields4.push(embedField);\n            } else if (cmdRequires.staff) {\n                sendEmbedFields2.push(embedField);\n            } else {\n                sendEmbedFields3.push(embedField);\n            }\n        }\n\n        Util.sendEmbed(channel, 'Locked Commands', null, 'Locked Commands', null, 0xf44336, sendEmbedFields1);\n        Util.sendEmbed(channel, 'Administrator Commands', null, 'Administrator Commands', null, 0xffeb3b, sendEmbedFields4);\n        Util.sendEmbed(channel, 'Staff Commands', null, 'Staff Commands', null, 0x4caf50, sendEmbedFields2);\n        Util.sendEmbed(channel, 'Public Commands', null, 'Public Commands', null, 0x2196f3, sendEmbedFields3);\n    },\n});\n"
  },
  {
    "path": "commands/public/Decrypt.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';decrypt '],\n\n    requires: {\n        guild: false,\n        loud: false,\n    },\n\n    desc: 'Decrypt text using One Time Pad',\n\n    args: '([encryption]) ([key])',\n\n    example: '1000110100000110010111000000010111000011011111 1100010000100110000101100001010101101110111111',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const mix = args.split(' ');\n        const encBits = mix[0];\n        const keyBits = mix[1];\n        let msgBits = '';\n        let msgStr = '';\n\n        if (encBits == null || keyBits == null) {\n            Util.commandFailed(channel, speaker, 'Required syntax: `;decrypt result key`');\n            return;\n        }\n\n        for (let i = 0; i < encBits.length; i++) {\n            msgBits += Util.doXOR(encBits[i], keyBits[i]);\n            if ((i + 1) % 8 == 0) {\n                msgStr += String.fromCharCode(parseInt(msgBits.substr(i - 7, 8), 2).toString(10));\n            }\n        }\n        Util.print(channel, `Result: ${msgStr}`);\n    },\n});\n"
  },
  {
    "path": "commands/public/Define.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';define ', ';urban '],\n\n    requires: {\n        guild: false,\n        loud: false,\n    },\n\n    desc: 'Output the definition for a word/phrase using Urban Dictionary',\n\n    args: '([keyword])',\n\n    example: 'yorkshire',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const definitions = index.Urban(args);\n\n        definitions.first((data) => {\n            if (data == null) {\n                Util.commandFailed(channel, speaker, 'No definition found');\n                return;\n            }\n\n            const sendEmbedFields = [];\n\n            let desc = Util.safeEveryone(data.definition);\n            let example = Util.safeEveryone(data.example);\n\n            if (desc.length > 2048) {\n                desc = `**[This message was shortened due to excessive length]** ${desc}`.substr(0, 2048);\n            }\n\n            if (example.length > 512) {\n                example = `**[This message was shortened due to excessive length]** ${example}`.substr(0, 512);\n            }\n\n            sendEmbedFields.push({ name: '​', value: '​', inline: false });\n            sendEmbedFields.push({ name: 'Example', value: example, inline: false });\n\n            Util.sendEmbed(channel, Util.capitalize(data.word), desc, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n        });\n    },\n});\n"
  },
  {
    "path": "commands/public/Encrypt.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';encrypt '],\n\n    requires: {\n        guild: false,\n        loud: false,\n    },\n\n    desc: 'Encrypt text using One Time Pad',\n\n    args: '[message]',\n\n    example: 'yo dawg',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        let encBits = '';\n        let keyBits = '';\n        for (let c = 0; c < args.length; c++) {\n            let bit = args[c].charCodeAt(0).toString(2);\n            while (bit.length < 8) {\n                bit = `0${bit}`;\n            }\n            for (let b = 0; b < 8; b++) {\n                const keyBit = Util.getRandomInt(0, 1);\n                keyBits += keyBit;\n                encBits += Util.doXOR(bit[b], keyBit);\n            }\n        }\n        Util.print(channel, `Encryption: ${encBits}`);\n        Util.print(channel, `Key: ${keyBits}`);\n    },\n});\n"
  },
  {
    "path": "commands/public/GetTickets.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';tickets', ';gettickets', ';showtickets', ';activetickets', ';displaytickets', ';supporttickets'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Display all open support tickets',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        if (!Util.checkStaff(guild, speaker) && !Util.hasRoleName(speaker, 'Support')) {\n            return Util.commandFailed(channel, speaker, 'This command can only be used by Support and above');\n        }\n\n        const activeTickets = await Data.getRecords(guild, 'tickets', { active: 1 });\n\n        const sendEmbedFields = [];\n\n        for (let i = 0; i < activeTickets.length; i++) {\n            const record = activeTickets[i];\n            const ticketNum = record.ticket_id;\n            const userId = record.user_id;\n            const openTick = record.open_tick;\n            const description = record.description;\n\n            const openDateStr = Util.getDateString(new Date(openTick));\n\n            sendEmbedFields.push({ name: `Ticket #${ticketNum}`, value: `​User: <@${userId}>\\nDescription: ${description}\\nCreated: ${openDateStr}​`, inline: false });\n        }\n\n        Util.sendEmbed(channel, 'Open Tickets', null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/public/GuildInfo.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';ginfo', ';guildinfo'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get guild info',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const createdStr = Util.getDateString(guild.createdAt);\n\n        const sendEmbedFields = [];\n\n        sendEmbedFields.push({ name: 'ID', value: guild.id });\n        sendEmbedFields.push({ name: 'Name', value: guild.name });\n        sendEmbedFields.push({ name: 'Owner', value: guild.owner.toString() });\n        sendEmbedFields.push({ name: 'Region', value: Util.capitalize(guild.region) });\n        sendEmbedFields.push({ name: 'Members', value: guild.memberCount });\n        sendEmbedFields.push({ name: 'Text Channels', value: Util.getTextChannels(guild).size });\n        sendEmbedFields.push({ name: 'Voice Channels', value: Util.getVoiceChannels(guild).size });\n        sendEmbedFields.push({ name: 'Roles', value: guild.roles.size });\n        sendEmbedFields.push({ name: 'AFK Timeout', value: `${guild.afkTimeout} seconds` });\n        sendEmbedFields.push({ name: 'Created', value: createdStr });\n        sendEmbedFields.push({ name: 'Icon', value: guild.iconURL });\n\n        sendEmbedFields.sort((a, b) => (String(a.name) + String(a.value)).length - (String(b.name) + String(b.value)).length);\n\n        Util.sendEmbed(channel, 'Guild Info', null, Util.makeEmbedFooter(speaker), guild.iconURL, colGreen, sendEmbedFields);\n    },\n});\n"
  },
  {
    "path": "commands/public/Image.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';img ', ';image '],\n\n    requires: {\n        guild: false,\n        loud: false,\n    },\n\n    desc: 'Output an image for a word/phrase using Google',\n\n    args: '([keyword])',\n\n    example: 'puppy',\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const searchStart = Util.getRandomInt(1, 20);\n        const searchURL = `https://www.googleapis.com/customsearch/v1?q=${args}&num=1&start=${searchStart}&searchType=image&key=AIzaSyBNXuJaoDMdnlLFxZ20ykf68gT2Qk4eG4s&cx=003838813173771542491%3A0bxpubr42jq`;\n\n        index.Request(searchURL, (error, response, body) => {\n            if (error) {\n                Util.log(`[HTTP] ${error}`);\n            } else {\n                try {\n                    const bodyData = JSON.parse(body);\n                    if (has.call(bodyData, 'items')) {\n                        const imgURL = bodyData.items[0].link;\n                        // Util.log(imgURL);\n                        Util.print(channel, imgURL);\n                        if (channel.name.toLowerCase().includes('nsfw') && !index.warnedImage[speaker.id]) {\n                            index.warnedImage[speaker.id] = true;\n                            Util.print(\n                                channel,\n                                `${speaker.toString()} is using me to post images in a nsfw channel; if the images are nsfw remember to ban them <@107593015014486016>!`,\n                            );\n                        }\n                    } else {\n                        Util.print(channel, 'No image found');\n                    }\n                } catch (err) {\n                    Util.print(channel, 'Image search errored (invalid JSON)');\n                }\n            }\n        });\n    },\n});\n"
  },
  {
    "path": "commands/public/Info.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';info '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get info about a user',\n\n    args: '([@user] | [id] | [name])',\n\n    example: 'vae',\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        const target = Util.getEitherByMixed(args, guild);\n        if (target == null) return Util.commandFailed(channel, speaker, 'User not found');\n\n        const isMuted = Admin.checkMuted(guild, target.id);\n        const numMutes = await Util.getNumMutes(target.id, guild);\n        const historyStr = `${numMutes} mute${numMutes == 1 ? '' : 's'}`;\n\n        const createdAt = target.createdAt || target.user.createdAt;\n        // const timeStr = `${Util.getYearStr(createdAt)}-${Util.getMonthStr(createdAt)}-${Util.getDayStr(createdAt)}`;\n        const highestRole = Util.getHighestRole(target);\n        const powerRating = `${Util.toFixedCut(Util.getPermRating(guild, target), 3)}%`;\n\n        const guildMembers = Array.from(guild.members.values());\n        guildMembers.sort((a, b) => a.joinedTimestamp - b.joinedTimestamp);\n        const joinOrder = guildMembers.map(member => member.id);\n        const joinPos = joinOrder.indexOf(target.id) + 1;\n        const joinPosStr = `${Util.getSuffix(joinPos)} (${guild.members.size} Total)`;\n\n        const joinedAtStr = (target.joinedAt != null ? Util.getDateString(target.joinedAt) : 'N/A');\n\n        const sendEmbedFields = [];\n\n        sendEmbedFields.push({ name: 'ID', value: target.id });\n        sendEmbedFields.push({ name: 'Username', value: target.toString() });\n        sendEmbedFields.push({ name: 'Nickname', value: (target.nickname != null ? Util.safe(target.nickname) : 'N/A') });\n        sendEmbedFields.push({ name: 'Discriminator', value: target.discriminator });\n        sendEmbedFields.push({ name: 'Staff', value: Util.capitalize(Util.checkStaff(guild, target)) });\n        sendEmbedFields.push({ name: 'Rank', value: highestRole ? `${highestRole.name} (${highestRole.position})` : 'None' });\n        sendEmbedFields.push({ name: 'Power', value: powerRating });\n        sendEmbedFields.push({ name: 'Status', value: Util.capitalize(target.presence.status) });\n        sendEmbedFields.push({ name: 'Bot', value: Util.capitalize(target.bot || target.user.bot) });\n        sendEmbedFields.push({ name: 'Game', value: (target.presence.game != null) ? Util.capitalize(target.presence.game.name) : 'N/A' });\n        sendEmbedFields.push({ name: 'Muted', value: Util.capitalize(isMuted) });\n        sendEmbedFields.push({ name: 'Mute History', value: historyStr });\n        sendEmbedFields.push({ name: 'Mic Muted', value: Util.capitalize(target.selfMute) });\n        sendEmbedFields.push({ name: 'Deafened', value: Util.capitalize(target.selfDeaf) });\n        sendEmbedFields.push({ name: 'Server Mic Muted', value: Util.capitalize(target.serverMute) });\n        sendEmbedFields.push({ name: 'Server Deafened', value: Util.capitalize(target.serverDeaf) });\n        sendEmbedFields.push({ name: 'Registered', value: (createdAt != null ? Util.getDateString(createdAt) : 'N/A') });\n        sendEmbedFields.push({ name: `Joined ${guild.name}`, value: `${joinedAtStr} | ${joinPosStr}` });\n        sendEmbedFields.push({ name: 'Avatar', value: Util.getAvatar(target, true) });\n\n        // var newDesc = Util.getAvatar(target, true) // + \"\\n\" + */(\"+\").repeat(numSep);\n\n        Util.sendEmbed(channel, 'User Info', null, Util.makeEmbedFooter(speaker), Util.getAvatar(target), colGreen, sendEmbedFields);\n\n        return undefined;\n    },\n});\n"
  },
  {
    "path": "commands/public/JTranslate.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';translatej '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Translate a Japanese word/sentence into English, allowing for Romaji',\n\n    args: '([word] | [sentence])',\n\n    example: 'Sayonara!',\n\n    // export GOOGLE_APPLICATION_CREDENTIALS=\"/home/einsteink/VaebVPS-4cce7d4f015f.json\"\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        const useText = index.Hepburn.toHiragana(index.Hepburn.cleanRomaji(args));\n\n        index.Translate.translate(useText, 'ja', 'en', (err, res) => {\n            if (err) return console.log(err);\n\n            const embFields = [\n                { name: `[${res.detectedSourceLanguage}] Original`, value: res.originalText || args, inline: false },\n                { name: '[en] Translation', value: res.translatedText, inline: false },\n            ];\n\n            Util.sendEmbed(channel, 'Translated', null, Util.makeEmbedFooter(speaker), null, colGreen, embFields);\n\n            return null;\n        });\n\n        // const projectId = 'vaebvps';\n        // const location = 'global';\n\n        // const request = {\n        //     parent: index.TranslateClient.locationPath(projectId, location), // projects/vaebvps/locations/global\n        //     contents: [useText],\n        //     mimeType: 'text/plain', // mime types: text/plain, text/html\n        //     sourceLanguageCode: 'ja',\n        //     targetLanguageCode: 'en-US',\n        // };\n\n        // const [response] = await index.TranslateClient.translateText(request);\n\n        // for (const translation of response.translations) {\n        //     const embFields = [\n        //         { name: '[ja] Original', value: `${translation.originalText || args} (${useText})`, inline: false },\n        //         { name: '[en] Translation', value: translation.translatedText, inline: false },\n        //     ];\n\n        //     Util.sendEmbed(channel, 'Translated', null, Util.makeEmbedFooter(speaker), null, colGreen, embFields);\n        // }\n    },\n});\n"
  },
  {
    "path": "commands/public/NewDiscord.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';newdiscord'],\n\n    requires: {\n        guild: false,\n        loud: false,\n    },\n\n    desc: 'Update your connected Discord account',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel) => {\n        Util.print(channel, 'This command is currently disabled');\n        // Util.print(speaker, `Click this **personal** link to update your Veil Discord account: https://veil.pkamara.me/linkdiscord.php?discordid=${speaker.id}`);\n        // Util.sendDescEmbed(channel, speaker.displayName, 'Update link sent, please check your Discord Messages.', null, null, null);\n    },\n});\n"
  },
  {
    "path": "commands/public/NowPlaying.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";nowplaying\", \";np\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Get info about the currently playing song\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var guildMusicInfo = Music.guildMusicInfo[guild.id];\n        if (guildMusicInfo.activeSong != null) {\n            Util.sendDescEmbed(channel, \"Now Playing\", guildMusicInfo.activeSong.title, Util.makeEmbedFooter(speaker), null, colGreen);\n        } else {\n            Util.sendDescEmbed(channel, \"Now Playing\", \"No songs are being played\", Util.makeEmbedFooter(speaker), null, colGreen);\n        }\n    }\n});"
  },
  {
    "path": "commands/public/Offenses.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';offenses', ';badoffenses', ';listoffenses', ';rules'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Output the list of offenses with defined mute times',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        const sendEmbedFields = [];\n\n        for (let i = 0; i < Admin.badOffenses.length; i++) {\n            const offenseData = Admin.badOffenses[i];\n\n            const offenseStr = offenseData.offense;\n            const timeStr = Util.formatTime(offenseData.time);\n\n            sendEmbedFields.push({ name: `Tag: [${i}]`, value: `Offense: ${offenseStr}\\nDefined Time: ${timeStr}​`, inline: false });\n        }\n\n        Util.sendEmbed(channel, 'Bad Offenses', 'If a user commits an offense listed here, their maximum mute time is whichever is larger: The defined time for the offense or their next default mute time.\\n\\nPut an offense tag at the start of a mute reason to tag it as that offense.', Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);\n    },\n});\n"
  },
  {
    "path": "commands/public/Ping.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';ping '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Pings a user',\n\n    args: '([@user] | [id] | [name]) ([message])',\n\n    example: 'vaeb fix your bot',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(args, [\n            function (str) {\n                return Util.getMemberByMixed(str, guild);\n            },\n        ], true);\n        if (!data) return Util.commandFailed(channel, speaker, 'User not found');\n        // msgObj.delete();\n        const user = data[0];\n        const msg = data[1];\n        let pingMsg = `${user.toString()} - Ping from ${Util.getName(speaker)} (${speaker.id})`;\n        if (msg.length > 0) {\n            pingMsg += ` - ${Util.safe(msg)}`;\n        }\n        Util.print(channel, pingMsg);\n    },\n});\n"
  },
  {
    "path": "commands/public/PingPong.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: ['ping'],\n\n    requires: {\n        guild: false,\n        loud: true,\n    },\n\n    desc: 'pong',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel) => {\n        Util.sendDescEmbed(channel, null, 'pong', null, null, null);\n    },\n});\n"
  },
  {
    "path": "commands/public/Play.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';play ', ';add ', ';addqueue '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: \"Make VaeBot play some bangin' tunes (or add them to the queue if the party's already started)\",\n\n    args: '([song_name] | [youtube_id] | [youtube_url])',\n\n    example: 'never gonna give you up',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (has.call(Music.noPlay, speaker.id)) return;\n\n        Music.joinMusic(guild, channel, () => {\n            if (args.includes('http')) {\n                let songId = /[^/=]+$/.exec(args);\n                if (songId != null && songId[0]) {\n                    songId = songId[0];\n                    index.YtInfo.getById(songId, (error, result) => {\n                        const songData = result.items[0];\n                        if (songData != null) {\n                            Music.addSong(speaker, guild, channel, Music.formatSong(songData, false));\n                        } else {\n                            Util.print(channel, 'Audio not found');\n                        }\n                    });\n                } else {\n                    Util.print(channel, 'Incorrect format for URL');\n                }\n            } else {\n                index.YtInfo.search(args, 6, (error, result) => {\n                    if (error) {\n                        Util.print(channel, error);\n                    } else {\n                        const items = result.items;\n                        let hasFound = false;\n                        for (let i = 0; i < items.length; i++) {\n                            const songData = items[i];\n                            if (songData != null && has.call(songData, 'id') && songData.id.kind == 'youtube#video') {\n                                hasFound = true;\n                                Music.addSong(speaker, guild, channel, Music.formatSong(songData, false));\n                                break;\n                            }\n                        }\n                        if (!hasFound) {\n                            Util.print(channel, 'Audio not found');\n                        }\n                    }\n                });\n            }\n        });\n    },\n});\n"
  },
  {
    "path": "commands/public/PlayAuto.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";playauto \", \";playa \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Plays a tune already stored in the auto-playlist\",\n\n    args: \"[song_name]\",\n\n    example: \"gonna give you up\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.toLowerCase();\n        var autoPlaylist = Data.guildGet(guild, Data.playlist);\n        var autoSongs = autoPlaylist.songs;\n        for (var i = 0; i < autoSongs.length; i++) {\n            var newSong = autoSongs[i];\n            var songData = newSong[0];\n            var author = newSong[1];\n            var title = songData.snippet.title;\n            if (title.toLowerCase().indexOf(args) >= 0) {\n                Music.joinMusic(guild, channel, (connection) => {\n                    Music.addSong(speaker, guild, channel, Music.formatSong(songData, false));\n                });\n                break;\n            }\n        }\n    }\n});"
  },
  {
    "path": "commands/public/Pop.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";undo\", \";pop\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Remove the last song from the queue which was added by the speaker\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var guildQueue = Music.guildQueue[guild.id];\n        Util.log(\"pop\");\n        // Util.log(guildQueue);\n        Util.log(\"-------POP---------\");\n        if (guildQueue.length > 0) {\n            for (var i = guildQueue.length-1; i >= 0; i--) {\n                var lastSong = guildQueue[i];\n                Util.log(\"Checking \" + i + \"_\" + typeof(lastSong));\n                if (lastSong[1].id == speaker.id) {\n                    var title = lastSong[0].title;\n                    Util.print(channel, \"Removed\", title, \"from the queue\");\n                    var connection = guild.voiceConnection;\n                    guildQueue.splice(i, 1);\n                    if (connection != null && Music.guildMusicInfo[guild.id].activeSong != null && title == Music.guildMusicInfo[guild.id].activeSong.title) Music.playNextQueue(guild, channel, true);\n                    break;\n                }\n            }\n        }\n    }\n});"
  },
  {
    "path": "commands/public/Power.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";power \", \";rank \", \";rate \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Are you over 9000?!\",\n\n    args: \"([@user] | [id] | [name])\",\n\n    example: \"vae\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var target = Util.getMemberByMixed(args, guild);\n        if (target == null) return Util.commandFailed(channel, speaker, \"User not found\");\n\n        var highestRole = Util.getHighestRole(target);\n        var powerRating = Util.toFixedCut(Util.getPermRating(guild, target), 3) + \"%\";\n\n        var sendEmbedFields = [];\n\n        sendEmbedFields.push({name: \"Username\", value: target.toString()});\n        sendEmbedFields.push({name: \"Staff\", value: Util.capitalize(Util.checkStaff(guild, target))});\n        sendEmbedFields.push({name: \"Rank\", value: highestRole.name + \" (\" + highestRole.position + \")\"});\n        sendEmbedFields.push({name: \"Power\", value: powerRating});\n\n        Util.sendEmbed(channel, \"User Power Ranking\", null, Util.makeEmbedFooter(speaker), Util.getAvatar(target), colGreen, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/public/Queue.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";queue\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"List all queued songs\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var guildQueue = Music.guildQueue[guild.id];\n\n        var sendEmbedFields = [];\n\n        for (var i = 0; i < guildQueue.length; i++) {\n            var songData = guildQueue[i][0];\n            var author = guildQueue[i][1];\n            sendEmbedFields.push({name: \"[\" + (i+1) + \"] \" + songData.title, value: \"Added by \" + Util.safeEveryone(author.toString()), inline: false});\n        }\n\n        Util.sendEmbed(channel, \"Audio Queue\", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/public/Roles.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";roles\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Get all guild roles\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var sendEmbedFields = [];\n        var guildRoles = Util.getGuildRoles(guild);\n\n        for (var i = 0; i < guildRoles.length; i++) {\n            var nowRole = guildRoles[i];\n            sendEmbedFields.push({name: nowRole.name, value: \"Position: \" + nowRole.position + \" | Mentionable: \" + nowRole.mentionable  + \" | Color: \" + nowRole.color, inline: false});\n        }\n\n        Util.sendEmbed(channel, \"Guild Roles\", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n    }\n});"
  },
  {
    "path": "commands/public/StartAuto.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";startauto\", \";startap\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Start playing the auto-playlist music\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        Music.joinMusic(guild, channel, connection => {\n            var autoPlaylist = Data.guildGet(guild, Data.playlist);\n            if (autoPlaylist.hasOwnProperty(\"songs\") && autoPlaylist.songs.length > 0) {\n                Util.log(\"Cmd, Playing Next Auto\");\n                Music.playNextAuto(guild, channel, true);\n            } else {\n                Util.print(channel, \"No songs in the auto-playlist\");\n            }\n        });\n    }\n});"
  },
  {
    "path": "commands/public/StartQueue.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";startqueue\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Start playing the queued music\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        Music.joinMusic(guild, channel, connection => {\n            var guildQueue = Music.guildQueue[guild.id];\n            if (guildQueue.length > 0) {\n                Music.playNextQueue(guild, channel, true);\n            } else {\n                Util.print(channel, \"No songs in queue\");\n            }\n        });\n    }\n});"
  },
  {
    "path": "commands/public/Syntax.js",
    "content": "const commands = Cmds.commands;\n\nmodule.exports = Cmds.addCommand({\n    cmds: [';syntax ', ';help ', ';cmd '],\n\n    requires: {\n        guild: false,\n        loud: true,\n    },\n\n    desc: 'Display command information',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        let hasFound = false;\n\n        for (let i = 0; i < commands.length; i++) {\n            const sendEmbedFields = [];\n            const cmdData = commands[i];\n\n            const fullCmds = cmdData[0];\n            const trimCmds = [];\n            let isMatch = false;\n\n            for (let c = 0; c < fullCmds.length; c++) {\n                const nowTrim = fullCmds[c].trim();\n                trimCmds.push(nowTrim);\n                if (nowTrim == args || (nowTrim.substr(0, 1) == ';' && nowTrim.substring(1) == args)) {\n                    isMatch = true;\n                }\n            }\n\n            if (!isMatch) continue;\n\n            const cmdRequires = cmdData[2];\n            const cmdDesc = cmdData[3];\n            const cmdSyntax = cmdData[4];\n            const cmdExample = cmdData[5];\n\n            let cmdType;\n\n            if (cmdRequires.administrator) {\n                cmdType = 'Administrator';\n            } else if (cmdRequires.staff) {\n                cmdType = 'Staff';\n            } else if (cmdRequires.vaeb) {\n                cmdType = 'Vaeb';\n            } else {\n                cmdType = 'Public';\n            }\n\n            sendEmbedFields.push({ name: 'Commands', value: trimCmds.join(' | '), inline: false });\n            sendEmbedFields.push({ name: 'Permission Level', value: cmdType, inline: false });\n            sendEmbedFields.push({ name: 'Description', value: cmdDesc, inline: false });\n            sendEmbedFields.push({ name: 'Syntax', value: `${trimCmds[0]} ${cmdSyntax}`, inline: false });\n            sendEmbedFields.push({ name: 'Example', value: `${trimCmds[0]} ${cmdExample}`, inline: false });\n\n            Util.sendEmbed(channel, 'Command Syntax', null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n\n            hasFound = true;\n\n            // break;\n        }\n\n        if (!hasFound) {\n            Util.sendDescEmbed(channel, 'Command Syntax', 'Command not found', Util.makeEmbedFooter(speaker), null, colGreen);\n        }\n    },\n});\n"
  },
  {
    "path": "commands/public/Text.js",
    "content": "const charToId = {\n    a: '244832142956298240',\n    b: '244832158290673664',\n    c: '244832181501820928',\n    d: '244832192197296128',\n    e: '244832201395535873',\n    f: '244832212535607296',\n    g: '244832220513042432',\n    h: '244832228880678913',\n    i: '244832236463980545',\n    j: '244832243342639106',\n    k: '244832250963820545',\n    l: '244832258274492416',\n    m: '244832265270591489',\n    n: '244832272253976577',\n    o: '244832280281874433',\n    p: '244832289052164096',\n    q: '244832298552262657',\n    r: '244832306878087169',\n    s: '244832315174420480',\n    t: '244832323529342976',\n    u: '244832374343335936',\n    v: '244832383998754818',\n    w: '244832392873771019',\n    x: '244832402856214543',\n    y: '244832411366588416',\n    z: '244832419079782400',\n};\n\nmodule.exports = Cmds.addCommand({\n    cmds: [';txt ', ';text ', ';type '],\n\n    requires: {\n        guild: false,\n        loud: false,\n    },\n\n    desc: 'Echo your text with emojis',\n\n    args: '([message])',\n\n    example: 'hello there',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const argsLower = args.toLowerCase();\n\n        for (let i = 0; i < index.bannedLetters.length; i++) {\n            if (argsLower.includes(index.bannedLetters[i].toLowerCase())) {\n                Util.print(channel, 'Nope: The letter `F` is now banned.');\n                return;\n            }\n        }\n\n        let str = '';\n        let wasChar = false;\n        for (let i = 0; i < args.length; i++) {\n            let char = args[i];\n            const charl = char.toLowerCase();\n            if (charToId.hasOwnProperty(charl)) {\n                char = `<:${charl}_:${charToId[charl]}>`;\n                if (wasChar) {\n                    char = ` ${char}`;\n                    wasChar = false;\n                }\n            } else {\n                wasChar = true;\n            }\n            str += char;\n        }\n        // str = str.replace(/@/g, \"@​\");\n        str = Util.safe(str);\n        Util.print(channel, str);\n    },\n});\n"
  },
  {
    "path": "commands/public/Ticket.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';ticket ', ';support ', ';ask ', ';addticket ', ';submitticket ', ';sendticket ', ';newticket '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Create a ticket to be viewed by Support',\n\n    args: '([ticket_details])',\n\n    example: 'Am I able to give away my whitelist?',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (!channel.name.toLowerCase().includes('support') && !channel.name.toLowerCase().includes('staff') && speaker.id !== vaebId && speaker.id !== guild.ownerID) {\n            return Util.commandFailed(channel, speaker, 'Support tickets can only be generated in #support');\n        }\n\n        const nextTicketNum = Data.nextIncGet('tickets');\n\n        const newRow = {\n            user_id: speaker.id,\n            description: args,\n            open_tick: +new Date(),\n            active: 1,\n        };\n        Data.addRecord(guild, 'tickets', newRow);\n\n        const sendEmbedFields = [\n            { name: 'Ticket User', value: Util.resolveMention(speaker), inline: false },\n            { name: 'Ticket Info', value: args, inline: false },\n        ];\n        Util.sendEmbed(channel, `Generated Support Ticket #${nextTicketNum}`, null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);\n\n        const roleSupport = Util.getRole('Support', guild);\n\n        if (roleSupport) {\n            const roleTrialSupport = Util.getRole('Trial Support', guild);\n            const mentionSupport = roleTrialSupport ? `${roleSupport.toString()} ${roleTrialSupport.toString()}` : roleSupport.toString();\n            Util.print(channel, `${mentionSupport} A new support ticket has been generated by ${Util.resolveMention(speaker)}!`);\n        }\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/public/Toggle.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';toggle '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Toggle an autorole on the speaker',\n\n    args: '([auto_role_name_1] .. [auto_role_name_n])',\n\n    example: 'hire',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const guildAutoRoles = Data.guildGet(guild, Data.autoRoles);\n        const props = args\n            .toLowerCase()\n            .trim()\n            .split(' ');\n        const rolesAdded = [];\n        const rolesRemoved = [];\n\n        for (let i = 0; i < props.length; i++) {\n            const prop = props[i];\n\n            if (!Object.prototype.hasOwnProperty.call(guildAutoRoles, prop)) {\n                return;\n            }\n\n            const roleName = guildAutoRoles[prop];\n            const roleObj = Util.getRole(roleName, guild);\n\n            if (!Util.hasRole(speaker, roleObj)) {\n                speaker.addRole(roleObj).catch(console.error);\n                rolesAdded.push(roleObj.name);\n            } else {\n                speaker.removeRole(roleObj).catch(console.error);\n                rolesRemoved.push(roleObj.name);\n            }\n        }\n\n        const sendEmbedFields = [];\n\n        if (rolesAdded.length > 0) {\n            sendEmbedFields.push({ name: 'Roles Added', value: rolesAdded.join('\\n') });\n        }\n\n        if (rolesRemoved.length > 0) {\n            sendEmbedFields.push({ name: 'Roles Removed', value: rolesRemoved.join('\\n') });\n        }\n\n        Util.sendEmbed(\n            channel,\n            'User Roles Altered',\n            null,\n            Util.makeEmbedFooter(speaker),\n            Util.getAvatar(speaker),\n            colGreen,\n            sendEmbedFields,\n        );\n    },\n});\n"
  },
  {
    "path": "commands/public/Translate.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';translate '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Translate a word/sentence into English',\n\n    args: '([word] | [sentence])',\n\n    example: 'Hola mis amigos',\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        index.Translate.translate(args, 'en', (err, res) => {\n            if (err) return console.log(err);\n\n            const embFields = [\n                { name: `[${res.detectedSourceLanguage}] Original`, value: res.originalText || args, inline: false },\n                { name: '[en] Translation', value: res.translatedText, inline: false },\n            ];\n\n            Util.sendEmbed(channel, 'Translated', null, Util.makeEmbedFooter(speaker), null, colGreen, embFields);\n        });\n\n        return undefined;\n    },\n\n    // export GOOGLE_APPLICATION_CREDENTIALS=\"/home/einsteink/VaebVPS-4cce7d4f015f.json\"\n\n    // func: async (cmd, args, msgObj, speaker, channel, guild) => {\n    //     const projectId = 'vaebvps';\n    //     const location = 'global';\n\n    //     const request = {\n    //         parent: index.TranslateClient.locationPath(projectId, location), // projects/vaebvps/locations/global\n    //         contents: [args],\n    //         mimeType: 'text/plain', // mime types: text/plain, text/html\n    //         targetLanguageCode: 'en-US',\n    //     };\n\n    //     const [response] = await index.TranslateClient.translateText(request);\n\n    //     for (const translation of response.translations) {\n    //         const embFields = [\n    //             { name: `[${translation.detectedSourceLanguage}] Original`, value: translation.originalText || args, inline: false },\n    //             { name: '[en] Translation', value: translation.translatedText, inline: false },\n    //         ];\n\n    //         Util.sendEmbed(channel, 'Translated', null, Util.makeEmbedFooter(speaker), null, colGreen, embFields);\n    //     }\n    // },\n});\n"
  },
  {
    "path": "commands/public/UpdateOwner.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';updateowner'],\n\n    requires: {\n        guild: false,\n        loud: false,\n    },\n\n    desc: 'Update your Vashta Owner role',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        Util.print(channel, 'This command is currently disabled');\n\n        // const newBuyer = guild.roles.find('name', 'Vashta-Owner');\n\n        // Data.query(`SELECT * FROM whitelist WHERE Disabled IS NULL AND DiscordId=${speaker.id};`, null, Data.connectionVeil).then(\n        //     (whitelistData) => {\n        //         if (whitelistData.length > 0) {\n        //             Util.sendDescEmbed(channel, speaker.displayName, 'Vashta owner confirmed, role given.', null, null, null);\n\n        //             speaker.addRole(newBuyer).catch(console.error);\n        //         } else {\n        //             // Util.sendDescEmbed(channel, speaker.displayName, 'You are not registered as a Vashta owner, if you do own Veil please say `;newdiscord` and follow the given link before using this command.', null, null, null);\n        //             Util.sendDescEmbed(channel, speaker.displayName, 'You are not registered as a Vashta owner.', null, null, null);\n\n        //             if (Util.hasRole(speaker, newBuyer)) {\n        //                 speaker.removeRole(newBuyer).catch(console.error);\n        //             }\n        //         }\n        //     },\n        // );\n    },\n});\n"
  },
  {
    "path": "commands/public/VoteSkip.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';voteskip'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Vote to skip the current song (will skip when the vote reaches 50% of the users in the voice channel)',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        const connection = guild.voiceConnection;\n        if (connection) {\n            const allChannels = guild.channels;\n            const voiceChannel = allChannels.find((c) => {\n                if (!c.type == 'voice') return false;\n                const hasMember = c.members.find(o => o.id == selfId);\n                if (hasMember != null) return true;\n                return false;\n            });\n            const voiceMembers = voiceChannel.members;\n            const numMembers = voiceMembers.size - 1;\n            if (!voiceMembers.find(o => o.id == speaker.id)) {\n                Util.print(channel, 'You are not in the voice channel');\n                return;\n            }\n            let alreadyExists = false;\n            const guildMusicInfo = Music.guildMusicInfo[guild.id];\n            const voteSkips = guildMusicInfo.voteSkips;\n            for (let i = 0; i < voteSkips.length; i++) {\n                if (voteSkips[i] == speaker.id) {\n                    alreadyExists = true;\n                    break;\n                }\n            }\n            if (alreadyExists == true) {\n                Util.print(channel, \"You can't vote more than once!\");\n                return;\n            }\n            voteSkips.push(speaker.id);\n            const numVotes = voteSkips.length;\n            const voteStr = numVotes == 1 ? 'vote' : 'votes';\n            Util.print(channel, 'Vote skip:', numVotes, voteStr);\n            Util.log(`Vote skip: ${numVotes / numMembers}`);\n            if (numVotes / numMembers >= 0.5) {\n                const guildQueue = Music.guildQueue[guild.id];\n                const autoPlaylist = Data.guildGet(guild, Data.playlist);\n                const firstInQueue = guildQueue[0];\n                if (guildMusicInfo.isAuto == false && guildQueue.length > 0 && guildMusicInfo.activeSong != null && guildMusicInfo.activeSong.title == firstInQueue[0].title) guildQueue.splice(0, 1);\n                Music.playNextQueue(guild, channel, true);\n            }\n        }\n    },\n});\n"
  },
  {
    "path": "commands/staff/ChangeMute.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';changemute ', ';change ', ';setmute ', 'altermute '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Change details of an active mute',\n\n    args: '([user_resolvable]) (OPTIONAL: [mute_length]) (OPTIONAL: [mute_length_format]) (OPTIONAL: [reason])',\n\n    example: 'vae 3 spamming multiple channels',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.trim();\n\n        const data = Util.getDataFromString(args,\n            [\n                [\n                    function (str) {\n                        return Util.getMemberByMixed(str, guild) || Util.isId(str);\n                    },\n                ],\n                [\n                    function (str) {\n                        const timeHours = Util.matchWholeNumber(str);\n                        return timeHours;\n                    },\n                ],\n                [\n                    function (str) {\n                        let mult;\n                        str = str.toLowerCase();\n                        if (str.substr(str.length - 1, 1) == 's' && str != 'ms' && str != 's') str = str.substr(0, str.length - 1);\n                        if (str == 'millisecond' || str == 'ms') mult = 1 / 60 / 60 / 1000;\n                        if (str == 'second' || str == 's' || str == 'sec') mult = 1 / 60 / 60;\n                        if (str == 'minute' || str == 'm' || str == 'min') mult = 1 / 60;\n                        if (str == 'hour' || str == 'h') mult = 1;\n                        if (str == 'day' || str == 'd') mult = 24;\n                        if (str == 'week' || str == 'w') mult = 24 * 7;\n                        if (str == 'month' || str == 'mo') mult = 24 * 30.42;\n                        if (str == 'year' || str == 'y') mult = 24 * 365.2422;\n                        return mult;\n                    },\n                ],\n            ]\n        , true);\n\n        if (!data) {\n            return Util.sendEmbed(channel, 'ChangeMute Failed', 'User not found', Util.makeEmbedFooter(speaker), null, colGreen, null);\n        }\n\n        Util.log(`Change Arg Data: ${data}`);\n\n        const member = data[0];\n        const mult = data[2] || 1;\n        const time = data[1] ? data[1] * 1000 * 60 * 60 * mult : null;\n        const reason = data[3];\n\n        Admin.changeMute(guild, channel, member, speaker, { 'time': time, 'reason': reason });\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/staff/Clear.js",
    "content": "const checkFuncs = {\n    user: ((msgObj, userId) => msgObj.author.id === userId),\n\n    regex: ((msgObj, regexObj) => regexObj.test(msgObj.content)),\n\n    all: (() => true),\n\n    cmd: (msgObj => msgObj.author.id === selfId || Cmds.getCommand(msgObj.content) != null),\n\n    bot: (msgObj => msgObj.author.bot === true),\n\n    hook: (msgObj => msgObj.author.bot === true),\n\n    image: ((msgObj) => {\n        const embeds = msgObj.embeds || [];\n        const attachments = msgObj.attachments || [];\n\n        for (let i = 0; i < embeds.length; i++) {\n            const nowEmbed = embeds[i];\n            if (nowEmbed.type === 'image' || nowEmbed.type === 'gifv' || nowEmbed.type === 'gif' || nowEmbed.type === 'webm') {\n                return true;\n            }\n        }\n\n        for (let i = 0; i < attachments.length; i++) {\n            const nowAtt = attachments[i];\n            if (has.call(nowAtt, 'width')) {\n                return true;\n            }\n        }\n\n        return false;\n    }),\n\n    file: ((msgObj) => {\n        const attachments = msgObj.attachments != null ? msgObj.attachments : [];\n\n        for (let i = 0; i < attachments.length; i++) {\n            const nowAtt = attachments[i];\n            if (!has.call(nowAtt, 'width')) {\n                return true;\n            }\n        }\n\n        return false;\n    }),\n\n    link: ((msgObj) => {\n        if (Util.checkURLs(msgObj.content).length > 0) return true;\n\n        const embeds = msgObj.embeds != null ? msgObj.embeds : [];\n\n        for (let i = 0; i < embeds.length; i++) {\n            const nowEmbed = embeds[i];\n            if (nowEmbed.type === 'link') {\n                return true;\n            }\n        }\n\n        return false;\n    }),\n\n    mention: ((msgObj) => {\n        const mentions = msgObj.mentions;\n\n        return mentions.length > 0;\n    }),\n};\n\nmodule.exports = Cmds.addCommand({\n    cmds: [';clear ', ';clean ', ';wipe ', ';clearchats ', ';cleanchats '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Delete the last <1-1000> messages matching a [user | regex-pattern | message-type] in the channel',\n\n    args: '([userResolvable] | [/regex/] | [all | bots | hooks | images | files | links | mentions]) (<1-1000>)',\n\n    example: 'vaeb 30',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(args, [\n            function (str) {\n                let lower = str.toLowerCase();\n                if (lower.substring(lower.length - 1) === 's') lower = lower.substr(0, lower.length - 1);\n                // types\n                if (lower === 'all' || lower === 'cmd' || lower === 'bot' || lower === 'hook' || lower === 'image' || lower === 'file' || lower === 'link' || lower === 'mention') {\n                    return [lower];\n                }\n                // regex\n                const regMatch = /^\\/(.+)\\/$/.exec(str);\n                if (regMatch) return ['regex', regMatch[1]];\n                // member\n                const member = Util.getMemberByMixed(str, guild);\n                if (member) return ['member', member];\n                // user id\n                const userId = Util.isId(str);\n                if (userId) return ['id', userId];\n                return undefined;\n            },\n            function (str) {\n                let numArgs = Number(str);\n                if (!isNaN(numArgs) && numArgs >= 1) {\n                    numArgs = Util.round(numArgs, 1);\n                    numArgs = Math.min(numArgs, 1000);\n                    return numArgs;\n                }\n                return undefined;\n            },\n        ], true);\n        if (!data) {\n            return Util.commandFailed(channel, speaker, 'Invalid parameters');\n        }\n\n        const matchData = data[0];\n        let numArgs = data[1];\n        // const scope = data[2];\n        const matchType = matchData[0];\n        const matchVal = matchData[1];\n\n        let funcData;\n        if (matchType === 'member') {\n            funcData = matchVal.id;\n        } else if (matchType === 'id') {\n            funcData = matchVal;\n        } else if (matchType === 'regex') {\n            funcData = new RegExp(matchVal, 'gim');\n        }\n\n        let includeSelf = false;\n\n        if (matchType === 'all' || funcData === speaker.id) {\n            includeSelf = true;\n            numArgs++;\n        }\n\n        let checkFunc;\n\n        if (matchType === 'member' || matchType === 'id') {\n            checkFunc = checkFuncs.user;\n        } else {\n            checkFunc = checkFuncs[matchType];\n        }\n\n        let numSearch = matchType !== 'all' ? numArgs * 30 : numArgs;\n        numSearch = Math.min(numSearch, 1000);\n\n        let last = null;\n        if (matchType === 'cmd') {\n            last = msgObj;\n            numArgs *= 2;\n        }\n\n        const msgStore = [];\n\n        // +++++++++++++++++++++ MESSAGE SCANNING +++++++++++++++++++++\n\n        await Util.fetchMessagesEx(channel, numSearch, msgStore, last);\n        Util.log(`Messages checked: ${msgStore.length}`);\n\n        const msgStoreUser = [];\n        for (let i = 0; i < msgStore.length; i++) {\n            const nowMsgObj = msgStore[i];\n            if ((includeSelf || nowMsgObj.id !== msgObj.id) && checkFunc(nowMsgObj, funcData)) {\n                msgStoreUser.push(nowMsgObj);\n                if (msgStoreUser.length >= numArgs) break;\n            }\n        }\n\n        const storeLength = msgStoreUser.length;\n        const chunkLength = 99;\n        Util.log(`Matches found: ${msgStoreUser.length}`);\n\n        for (let i = 0; i < storeLength; i += chunkLength) {\n            const chunk = msgStoreUser.slice(i, i + chunkLength);\n\n            if (chunk.length > 1) {\n                channel.bulkDelete(chunk)\n                    .then(() => Util.log(`Cleared ${chunk.length} messages`))\n                    .catch(Util.logErr);\n            } else {\n                chunk[0].delete()\n                    .then(() => Util.log('Cleared 1 message'))\n                    .catch(Util.logErr);\n            }\n        }\n\n        return undefined;\n    },\n});\n"
  },
  {
    "path": "commands/staff/ClearQueue.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';clearqueue'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: \"Clears VaeBot's queue of music\",\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        Music.clearQueue(guild);\n        Util.print(channel, 'Cleared queue');\n    },\n});\n"
  },
  {
    "path": "commands/staff/GetBans.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';bans', ';getbans'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get all banned users',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        guild.fetchBans().then((bans) => {\n            const outStr = ['**Guild bans:**\\n```'];\n            bans.forEach((user) => {\n                outStr.push(`Username: ${Util.getName(user)}`);\n            });\n            outStr.push('```');\n            Util.print(channel, outStr.join('\\n'));\n        });\n    },\n});\n"
  },
  {
    "path": "commands/staff/HardKick.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';hardkick ', ';hardeject ', ';softban '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Kick a user from the guild (extra hard)', // Does this command even do anything?????????????????\n\n    args: '([@user] | [id] | [name]) ([reason])',\n\n    example: 'vaeb being weird',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (speaker.id == '138274235435974656') return Util.commandFailed(channel, speaker, 'Temporarily disabled kick permissions for this moderator as a precaution due to complaints');\n        const data = Util.getDataFromString(args, [\n            function (str) {\n                return Util.getMemberByMixed(str, guild);\n            },\n        ], true);\n        if (!data) return Util.commandFailed(channel, speaker, 'User not found');\n        const target = data[0];\n        const reason = data[1];\n        if (Util.getPosition(speaker) <= Util.getPosition(target)) {\n            Util.commandFailed(channel, speaker, 'User has equal or higher rank');\n            return false;\n        }\n        const targName = Util.getName(target);\n        const targId = Util.safe(target.id);\n\n        const outStr = ['**You have been hard-kicked**\\n```'];\n        outStr.push(`Guild: ${guild.name}`);\n        outStr.push(`Reason: ${reason}`);\n        outStr.push('What is a hardkick: Hardkicks ban and instantly unban the user, causing their recent messages to be deleted and possibly causing Discord to temporarily cache their IP as banned.');\n        outStr.push('```');\n        Util.print(target, outStr.join('\\n'));\n\n        guild.ban(target.id, { days: 1, reason })\n            .then(() => {\n                guild.unban(target.id)\n                    .catch(Util.logErr);\n            })\n            .catch(Util.logErr);\n\n        Util.print(channel, 'Hard Kicked', Util.fix(targName), `(${targId}) for`, Util.fix(reason));\n        if (guild.id == '168742643021512705') index.dailyKicks.push([targId, `${targName}#${target.discriminator}`, reason]);\n\n        const sendLogData = [\n            'Guild Hard Kick',\n            guild,\n            target,\n            { name: 'Username', value: target.toString() },\n            { name: 'Moderator', value: speaker.toString() },\n            { name: 'Kick Reason', value: reason },\n        ];\n        Util.sendLog(sendLogData, colAction);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/staff/History.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';history', ';mutehistory'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get all users with mute history',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        const sendEmbedFields = [];\n\n        const allMutes = await Data.getRecords(guild, 'mutes');\n\n        const numMutesKeys = {};\n        const numMutesArr = [];\n\n        const splitMutesKeys = {};\n        const splitMutesArr = [];\n\n        for (let i = 0; i < allMutes.length; i++) {\n            const record = allMutes[i];\n            const userId = record.user_id;\n            if (!has.call(numMutesKeys, userId)) numMutesKeys[userId] = numMutesArr.push([`<@${userId}>`, 0]) - 1;\n            ++numMutesArr[numMutesKeys[userId]][1];\n        }\n\n        for (let i = 0; i < numMutesArr.length; i++) {\n            const userMention = numMutesArr[i][0];\n            const numMutes = numMutesArr[i][1];\n            if (!has.call(splitMutesKeys, numMutes)) {\n                const chunk = [];\n                chunk.numMutes = numMutes;\n                splitMutesKeys[numMutes] = splitMutesArr.push(chunk) - 1;\n            }\n            splitMutesArr[splitMutesKeys[numMutes]].push(userMention);\n        }\n\n        splitMutesArr.sort((a, b) => a.numMutes - b.numMutes);\n\n        for (let i = 0; i < splitMutesArr.length; i++) {\n            const splitMutesChunk = splitMutesArr[i];\n            const numMutes = splitMutesChunk.numMutes;\n            sendEmbedFields.push({ name: `${numMutes} Mute${numMutes == 1 ? '' : 's'}`, value: splitMutesChunk.join('\\n'), inline: false });\n        }\n\n        Util.sendEmbed(channel, 'Mute History', null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);\n    },\n});\n\n"
  },
  {
    "path": "commands/staff/Join.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";join \", \";summon \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Make VaeBot join a voice channel\",\n\n    args: \"([voice_channel_name])\",\n\n    example: \"gaming\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var vChannel = Util.getVoiceChannels(guild).find(channel => args == \"<#\" + channel.id + \">\" || channel.name.toLowerCase().indexOf(args.toLowerCase()) >= 0);\n        if (vChannel) {\n            vChannel.join()\n            .then(connection => Util.print(channel, \"Joined\", vChannel.name))\n            .catch(error => Util.log(\"[E_JoinCmd] addRole: \" + error));\n        } else {\n            Util.print(channel, \"Voice channel not found\");\n        }\n    }\n});"
  },
  {
    "path": "commands/staff/Kick.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';kick ', ';eject '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Kick a user from the guild',\n\n    args: '([@user] | [id] | [name]) ([reason])',\n\n    example: 'vaeb being weird',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        if (speaker.id == '138274235435974656') {\n            return Util.commandFailed(\n                channel,\n                speaker,\n                'Temporarily disabled kick permissions for this moderator as a precaution due to complaints',\n            );\n        }\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str) {\n                    return Util.getMemberByMixed(str, guild);\n                },\n            ],\n            true,\n        );\n        if (!data) return Util.commandFailed(channel, speaker, 'User not found');\n        const target = data[0];\n        const reason = data[1];\n        if (Util.getPosition(speaker) <= Util.getPosition(target)) {\n            Util.commandFailed(channel, speaker, 'User has equal or higher rank');\n            return false;\n        }\n        const targName = Util.getName(target);\n        const targId = Util.safe(target.id);\n\n        const outStr = ['**You have been kicked**\\n```'];\n        outStr.push(`Guild: ${guild.name}`);\n        outStr.push(`Reason: ${reason}`);\n        outStr.push('```');\n        await Util.print(target, outStr.join('\\n'));\n\n        Util.kickMember(target, speaker, reason);\n\n        Util.print(channel, 'Kicked', Util.fix(targName), `(${targId}) for`, Util.fix(reason));\n        if (guild.id == '168742643021512705') index.dailyKicks.push([targId, `${targName}#${target.discriminator}`, reason]);\n\n        const sendLogData = [\n            'Guild Kick',\n            guild,\n            target,\n            { name: 'Username', value: target.toString() },\n            { name: 'Moderator', value: speaker.toString() },\n            { name: 'Kick Reason', value: reason },\n        ];\n        Util.sendLog(sendLogData, colAction);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/staff/Leave.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";leave\", \";exit\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Make VaeBot leave it's voice channel\",\n\n    args: \"\",\n\n    example: \"\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        Music.clearQueue(guild);\n        var connection = guild.voiceConnection;\n        if (!connection) return Util.commandFailed(channel, speaker, \"Not in a voice channel\");\n        var voiceChannel = connection.channel;\n        connection.disconnect();\n        Util.sendDescEmbed(channel, \"Left Voice Channel\", voiceChannel.name, Util.makeEmbedFooter(speaker), null, colGreen);\n    }\n});"
  },
  {
    "path": "commands/staff/Mute.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';mute ', ';mutehammer ', ';arrest '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Mute a user (in all guild channels) and add the mute to their record',\n\n    args: '([user_resolvable]) (OPTIONAL: [mute_length]) (OPTIONAL: [mute_length_format]) (OPTIONAL: [reason])',\n\n    example: 'vae 2 minutes being weird',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.trim();\n\n        const data = Util.getDataFromString(\n            args,\n            [\n                [\n                    function (str) {\n                        return Util.getMemberByMixed(str, guild) || Util.isId(str);\n                    },\n                ],\n                [\n                    function (str) {\n                        return Util.matchWholeNumber(str);\n                    },\n                ],\n                [\n                    function (str) {\n                        let timeMult;\n                        str = str.toLowerCase();\n                        if (str.substr(str.length - 1, 1) == 's' && str.length > 2) str = str.substr(0, str.length - 1);\n                        if (str == 'millisecond' || str == 'ms') timeMult = 1 / 60 / 60 / 1000;\n                        if (str == 'second' || str == 's' || str == 'sec') timeMult = 1 / 60 / 60;\n                        if (str == 'minute' || str == 'm' || str == 'min') timeMult = 1 / 60;\n                        if (str == 'hour' || str == 'h') timeMult = 1;\n                        if (str == 'day' || str == 'd') timeMult = 24;\n                        if (str == 'week' || str == 'w') timeMult = 24 * 7;\n                        if (str == 'month' || str == 'mo') timeMult = 24 * 30.42;\n                        if (str == 'year' || str == 'y') timeMult = 24 * 365.2422;\n                        return timeMult;\n                    },\n                ],\n            ],\n            true,\n        );\n\n        if (!data) {\n            return Util.sendEmbed(channel, 'Mute Failed', 'User not found', Util.makeEmbedFooter(speaker), null, colGreen, null);\n        }\n\n        Util.log(`Change Arg Data: ${data}`);\n\n        const member = data[0];\n        const mult = data[2] || 1 / 60;\n        const time = data[1] ? data[1] * 1000 * 60 * 60 * mult : null;\n        const reason = data[3];\n\n        let success;\n\n        /* if (speaker.id == '119203482598244356') {\n            member = speaker;\n            speaker = speaker.displayName;\n        } */\n\n        if (Admin.checkMuted(guild, member.id)) {\n            Util.print(\n                channel,\n                `<@${\n                    speaker.id\n                }> Are you sure you want to re-mute that guy instead of using \\`;changemute\\`...?\\n\\n You only need to re-mute if it's a separate offense.`,\n            );\n\n            const isResponse = msgObjTemp => msgObjTemp.author.id == speaker.id;\n\n            channel\n                .awaitMessages(isResponse, { max: 1, time: 1000 * 25, errors: ['time'] })\n                .then(async (collected) => {\n                    const response = collected.first();\n                    const responseMsg = response.content;\n\n                    if (/y[aeiouy]+?[shp]|y[aeiy]+?\\b|\\by\\b/.test(responseMsg.toLowerCase())) {\n                        Util.print(channel, 'Well okay then, your will is my command...');\n                        success = await Admin.addMute(guild, channel, member, speaker, { time, reason });\n                    } else {\n                        Util.print(channel, 'Guess you made a mistake eh... Well fine, your request has been cancelled.');\n                    }\n                })\n                .catch(() => {\n                    Util.print(\n                        channel,\n                        `What a drag, I've been waiting far too long for an answer <@${speaker.id}>, snails get stitches...`,\n                    );\n                    Admin.addMute(guild, channel, speaker, 'System', { time: 1000 * 60 * 1.5, reason: 'Snails get stitches' });\n                });\n        } else {\n            success = await Admin.addMute(guild, channel, member, speaker, { time, reason });\n        }\n\n        if (time === null && success === true) {\n            Util.print(\n                channel,\n                `This user was muted for their default mute time (based on their mute history), do you want to change it <@${\n                    speaker.id\n                }>? If you do, just tell me the new time now...`,\n            );\n\n            const isResponse = msgObjTemp => msgObjTemp.author.id == speaker.id;\n\n            channel\n                .awaitMessages(isResponse, { max: 1, time: 1000 * 25, errors: ['time'] })\n                .then((collected) => {\n                    const response = collected.first();\n                    let responseMsg = response.content;\n\n                    responseMsg = responseMsg\n                        .replace(/<@.?\\d+?>\\s*/g, '')\n                        .replace(/[^\\sa-z0-9]+/gi, '')\n                        .trim();\n\n                    if (/\\bno|no\\b|\\bcancel|\\bkeep|\\bdont|^n$/i.test(responseMsg)) {\n                        Util.print(channel, 'Got it!');\n                        return;\n                    }\n\n                    const data2 = Util.getDataFromString(\n                        responseMsg,\n                        [\n                            [\n                                function (str) {\n                                    return Util.matchWholeNumber(str);\n                                },\n                            ],\n                            [\n                                function (str) {\n                                    let timeMult;\n                                    str = str.toLowerCase();\n                                    if (str.substr(str.length - 1, 1) == 's' && str.length > 2) str = str.substr(0, str.length - 1);\n                                    if (str == 'millisecond' || str == 'ms') timeMult = 1 / 60 / 60 / 1000;\n                                    if (str == 'second' || str == 's' || str == 'sec') timeMult = 1 / 60 / 60;\n                                    if (str == 'minute' || str == 'm' || str == 'min') timeMult = 1 / 60;\n                                    if (str == 'hour' || str == 'h') timeMult = 1;\n                                    if (str == 'day' || str == 'd') timeMult = 24;\n                                    if (str == 'week' || str == 'w') timeMult = 24 * 7;\n                                    if (str == 'month' || str == 'mo') timeMult = 24 * 30.42;\n                                    if (str == 'year' || str == 'y') timeMult = 24 * 365.2422;\n                                    return timeMult;\n                                },\n                            ],\n                        ],\n                        true,\n                    );\n\n                    if (!data2) return;\n\n                    Util.log(`Change Arg Data New: ${data2}`);\n\n                    const mult2 = data2[1] || 1 / 60;\n                    const time2 = data2[0] ? data2[0] * 1000 * 60 * 60 * mult2 : null;\n\n                    if (time2 == null) return;\n\n                    Admin.changeMute(guild, channel, member, speaker, { time: time2 });\n                })\n                .catch(console.log);\n        }\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/staff/Mutes.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';mutes', ';muted'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get all currently muted users',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        const activeMutes = await Data.getRecords(guild, 'mutes', { active: 1 });\n\n        const nowDate = +new Date();\n\n        const sendEmbedFields = [];\n\n        for (let i = 0; i < activeMutes.length; i++) {\n            const muteRecord = activeMutes[i];\n            const targetId = muteRecord.user_id;\n            const modId = muteRecord.mod_id;\n            const startTime = muteRecord.start_tick;\n            const endTime = muteRecord.end_tick;\n            const reason = muteRecord.mute_reason;\n\n            const remaining = endTime - nowDate;\n            const timeStr = Util.formatTime(remaining);\n            // const targUser = Util.getUserById(targetId);\n            // const targName = targUser == null ? targetId : Util.getMostName(targUser);\n            const muteDate = new Date(startTime);\n            const muteDateStr = Util.getDateString(muteDate);\n\n            sendEmbedFields.push({ name: `[${i + 1}] ${muteDateStr}`, value: `​User: <@${targetId}>\\nReason: ${reason}\\nModerator: <@${modId}>\\nRemaining: ${timeStr}​`, inline: false });\n        }\n\n        Util.sendEmbed(channel, 'Active Mutes', null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);\n    },\n});\n"
  },
  {
    "path": "commands/staff/Nickname.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";nick \", \";nickname \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Set a user's nickname\",\n\n    args: \"([@user] | [id] | [name]) ([new_nickname])\",\n\n    example: \"vae vaeben\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var data = Util.getDataFromString(args, [\n            function(str, results) {\n                return Util.getMemberByMixed(str, guild);\n            }\n        ], true);\n\n        if (!data) return Util.commandFailed(channel, speaker, \"Invalid parameters\");\n\n        var member = data[0];\n        var nick = data[1];\n\n        if ((member.id == vaebId || member.id == selfId) && speaker.id != vaebId) {\n            Util.commandFailed(channel, speaker, \"You are not allowed to set developer nicknames\");\n            return;\n        }\n\n        if (Util.getPosition(speaker) <= Util.getPosition(member) && member != speaker) {\n            Util.commandFailed(channel, speaker, \"User has equal or higher rank\");\n            return;\n        }\n\n        var previousNick = member.nickname;\n\n        member.setNickname(nick);\n\n        var sendEmbedFields = [\n            {name: \"Username\", value: member.toString()},\n            {name: \"Old Nickname\", value: previousNick},\n            {name: \"New Nickname\", value: nick}\n        ];\n\n        Util.sendEmbed(channel, \"Nickname Updated\", null, Util.makeEmbedFooter(speaker), Util.getAvatar(member), colGreen, sendEmbedFields);\n\n        var sendLogData = [\n            \"Set Nickname\",\n            guild,\n            member,\n            {name: \"Username\", value: member.toString()},\n            {name: \"Moderator\", value: speaker.toString()},\n            {name: \"Nickname\", value: nick}\n        ];\n\n        Util.sendLog(sendLogData, colAction);\n    }\n});"
  },
  {
    "path": "commands/staff/PlayF.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";playf \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Make VaeBot play some bangin' tunes... from a file :o\",\n\n    args: \"([file_name])\",\n\n    example: \"never gonna give you up\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        //Music.playFile(args, guild, channel);\n        Music.addSong(speaker, guild, channel, Music.formatSong(args, true));\n    }\n});"
  },
  {
    "path": "commands/staff/RaidMode.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';raidmode', ';start raidmode', ';enable raidmode'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Activate raid mode',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (index.raidMode[guild.id]) {\n            Util.log('Raid mode is already active');\n            Util.print(channel, 'Raid mode is already active');\n            return;\n        }\n\n        const joinStamp = +new Date();\n\n        index.recentMembers = index.recentMembers.filter(memberData => joinStamp - memberData.joinStamp < index.newMemberTime);\n        index.recentMembers2 = index.recentMembers2.filter(memberData => joinStamp - memberData.joinStamp < index.newMemberTime2);\n        index.activateRaidMode(guild, channel);\n    },\n});\n"
  },
  {
    "path": "commands/staff/RemQueue.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";remqueue \", \";remq \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Remove a song from the music queue\",\n\n    args: \"[song_name]\",\n\n    example: \"gonna give you up\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.toLowerCase();\n        var guildQueue = Music.guildQueue[guild.id];\n        for (var i = guildQueue.length-1; i >= 0; i--) {\n            var newSong = guildQueue[i];\n            var songData = newSong[0];\n            var author = newSong[1];\n            var title = songData.title;\n            if (title.toLowerCase().indexOf(args) >= 0) {\n                Util.print(channel, \"Removed\", title, \"from the queue\");\n                var connection = guild.voiceConnection;\n                guildQueue.splice(i, 1);\n                if (connection != null && Music.guildMusicInfo[guild.id].activeSong != null && title == Music.guildMusicInfo[guild.id].activeSong.title) Music.playNextQueue(guild, channel, true);\n            }\n        }\n    }\n});\n"
  },
  {
    "path": "commands/staff/Skip.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";skip\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Skip to the next song\",\n\n    args: \"[song_name]\",\n\n    example: \"gonna give you up\",\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var connection = guild.voiceConnection;\n        if (connection) {\n            var guildQueue = Music.guildQueue[guild.id];\n            var autoPlaylist = Data.guildGet(guild, Data.playlist);\n            var guildMusicInfo = Music.guildMusicInfo[guild.id];\n            var firstInQueue = guildQueue[0];\n            Util.log(\"Num songs in queue: \" + guildQueue.length);\n            if (Util.checkStaff(guild, speaker) || (guildMusicInfo.isAuto == false && guildMusicInfo.activeAuthor.id == speaker.id)) {\n                if (guildMusicInfo.isAuto == false && guildQueue.length > 0 && guildMusicInfo.activeSong != null && guildMusicInfo.activeSong.title == firstInQueue[0].title) guildQueue.splice(0, 1);\n                Util.log(guildMusicInfo.isAuto == false + \" | \" + guildQueue.length + \" | \" + guildMusicInfo.activeSong + \" | \" + ((guildMusicInfo.activeSong && firstInQueue) ? guildMusicInfo.activeSong.title == firstInQueue[0].title : \"N/A\") + \" | \" + (guildMusicInfo.activeSong ? guildMusicInfo.activeSong.title : \"N/A\") + \" | \" + (firstInQueue ? firstInQueue[0].title : \"N/A\"))\n                Music.playNextQueue(guild, channel, true);\n            } else {\n                Util.print(channel, \"You are not staff and you did not add this song\");\n            }\n        }\n    }\n});"
  },
  {
    "path": "commands/staff/Stop.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [\";stop\", \";silence\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },\n\n    desc: \"Cancel the party, the bangin' tunes can wait for another day\",\n\n    args: \"[song_name]\",\n\n    example: \"gonna give you up\",\n\n    ///////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        var guildQueue = Music.guildQueue[guild.id];\n        var guildMusicInfo = Music.guildMusicInfo[guild.id];\n        var connection = guild.voiceConnection;\n        if (connection) {\n            if (Music.isPlaying[guild.id]) {\n                if (guildMusicInfo.isAuto == false) guildQueue.splice(0, 1);\n                Music.stopMusic(guild);\n                Util.print(channel, \"Stopping audio\");\n            } else {\n                Util.print(channel, \"No audio playing\");\n            }\n        } else {\n            Util.print(channel, \"No audio found\");\n        }\n    }\n});"
  },
  {
    "path": "commands/staff/TempBan.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';tempban ', ';tban ', ';temporaryban ', ';timeban ', ';bantime '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Temporarily ban a user from the guild',\n\n    args: '([user_resolvable]) (OPTIONAL: [ban_length]) (OPTIONAL: [ban_length_format]) (OPTIONAL: [reason])',\n\n    example: 'vae 2 days repeatedly breaking rules',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.trim();\n\n        const data = Util.getDataFromString(args,\n            [\n                [\n                    function (str) {\n                        return Util.getMemberByMixed(str, guild) || Util.isId(str);\n                    },\n                ],\n                [\n                    function (str) {\n                        return Util.matchWholeNumber(str);\n                    },\n                ],\n                [\n                    function (str) {\n                        let mult;\n                        str = str.toLowerCase();\n                        if (str.substr(str.length - 1, 1) == 's' && str.length > 2) str = str.substr(0, str.length - 1);\n                        if (str == 'millisecond' || str == 'ms') mult = 1 / 60 / 60 / 1000;\n                        if (str == 'second' || str == 's' || str == 'sec') mult = 1 / 60 / 60;\n                        if (str == 'minute' || str == 'm' || str == 'min') mult = 1 / 60;\n                        if (str == 'hour' || str == 'h') mult = 1;\n                        if (str == 'day' || str == 'd') mult = 24;\n                        if (str == 'week' || str == 'w') mult = 24 * 7;\n                        if (str == 'month' || str == 'mo') mult = 24 * 30.42;\n                        if (str == 'year' || str == 'y') mult = 24 * 365.2422;\n                        return mult;\n                    },\n                ],\n            ]\n            , true);\n\n        if (!data) {\n            return Util.sendEmbed(channel, 'Ban Failed', 'User not found', Util.makeEmbedFooter(speaker), null, colGreen, null);\n        }\n\n        Util.log(`Change Arg Data: ${data}`);\n\n        const member = data[0];\n        const mult = data[2] || 1;\n        const time = data[1] ? data[1] * 1000 * 60 * 60 * mult : null;\n        const reason = data[3];\n\n        Admin.addBan(guild, channel, member, speaker, { time, reason, temp: true });\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "commands/staff/TempBans.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';tempbans', ';tbans', ';timebans', ';timedbans'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get all temporarily banned users',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        const activeBans = await Data.getRecords(guild, 'bans', { active: 1 });\n\n        const nowDate = +new Date();\n\n        const sendEmbedFields = [];\n\n        for (let i = 0; i < activeBans.length; i++) {\n            const record = activeBans[i];\n            const targetId = record.user_id;\n            const modId = record.mod_id;\n            const startTime = record.start_tick;\n            const endTime = record.end_tick;\n            const reason = record.ban_reason;\n\n            const remaining = endTime - nowDate;\n            const timeStr = Util.formatTime(remaining);\n            // const targUser = Util.getUserById(targetId);\n            // const targName = targUser == null ? targetId : Util.getMostName(targUser);\n            const startDate = new Date(startTime);\n            const startDateStr = Util.getDateString(startDate);\n\n            sendEmbedFields.push({ name: `[${i + 1}] ${startDateStr}`, value: `​User: <@${targetId}>\\nReason: ${reason}\\nModerator: <@${modId}>\\nRemaining: ${timeStr}​`, inline: false });\n        }\n\n        Util.sendEmbed(channel, 'Temporary Bans', null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);\n    },\n});\n"
  },
  {
    "path": "commands/staff/UnBan.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';unban ', ';remban '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Unban a user from the guild',\n\n    args: '([user_resolvable])',\n\n    example: 'vae',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        guild.fetchBans().then((bans) => {\n            // Collection<Snowflake, User>\n            // bans = bans.map(o => o.user);\n            const target = Util.searchUserPartial(bans, args);\n            if (target == null) {\n                Util.commandFailed(channel, speaker, 'User not found');\n                return;\n            }\n\n            Admin.unBan(guild, channel, target, speaker);\n        });\n    },\n});\n"
  },
  {
    "path": "commands/staff/UnMute.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';unmute ', ';unwarn ', ';unmutehammer ', ';free '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Unmute a user',\n\n    args: '([user_resolvable])',\n\n    example: 'vae',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.trim();\n\n        Admin.unMute(guild, channel, args, speaker);\n    },\n});\n"
  },
  {
    "path": "commands/staff/UnRaidMode.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';unraidmode', ';stopraidmode', ';stop raidmode', ';disable raidmode'],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Disable raid mode',\n\n    args: '',\n\n    example: '',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        if (!index.raidMode[guild.id]) {\n            Util.log('Raid mode is already disabled');\n            Util.print(channel, 'Raid mode is already disabled');\n            return;\n        }\n\n        index.disableRaidMode(guild, channel);\n    },\n});\n"
  },
  {
    "path": "commands/staff/UndoMute.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';undomute ', ';popmute '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: \"Remove a user's last mute from their record and unmute them if they are muted\",\n\n    args: '([user_resolvable])',\n\n    example: 'vae',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.trim();\n\n        Admin.remMute(guild, channel, args, speaker);\n    },\n});\n"
  },
  {
    "path": "commands/staff/UserMutes.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';mutes ', ';usermutes ', ';history ', ';userhistory '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Get the mute history of a user',\n\n    args: '([@user] | [id] | [name])',\n\n    example: 'vae',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: async (cmd, args, msgObj, speaker, channel, guild) => {\n        const data = Util.getDataFromString(\n            args,\n            [\n                function (str) {\n                    return Util.getMemberByMixed(str, guild) || Util.isId(str);\n                },\n            ],\n            false,\n        );\n        if (!data) return Util.commandFailed(channel, speaker, 'User not found');\n\n        const member = data[0];\n\n        let memberId = member;\n        let memberName = member;\n\n        if (typeof member === 'object') {\n            memberId = member.id;\n            memberName = Util.getMostName(member);\n        } else {\n            memberName = `<@${member}>`;\n        }\n\n        const pastMutes = await Data.getRecords(guild, 'mutes', { user_id: memberId });\n\n        const sendEmbedFields = [];\n\n        for (let i = 0; i < pastMutes.length; i++) {\n            const record = pastMutes[i];\n\n            const muteDate = new Date(record.start_tick);\n            const muteLength = record.end_tick - record.start_tick;\n            const moderatorId = record.mod_id;\n            const active = record.active == 1;\n\n            const reason = record.mute_reason;\n            const muteDateStr = Util.getDateString(muteDate);\n            const muteLengthStr = Util.formatTime(muteLength);\n            const modMention = Util.resolveUserMention(guild, moderatorId);\n            const activeStr = active ? 'Yes' : 'No';\n\n            sendEmbedFields.push({\n                name: `[${i + 1}] ${muteDateStr}`,\n                value: `​Reason: ${reason}\\nLength: ${muteLengthStr}\\nModerator: ${modMention}\\nActive: ${activeStr}`,\n                inline: false,\n            });\n        }\n\n        Util.sendEmbed(channel, `Mute History: ${memberName}`, null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "core/ManageAdmin.js",
    "content": "const timeouts = {\n    mute: [],\n    ban: [],\n};\n\nlet timeoutId = 0;\n\nconst muteCacheActive = {};\n\nexports.defaultMuteLength = 1000 * 60 * 30;\nexports.defaultMuteLength2 = 1000 * 60 * 60 * 24;\nexports.dayMilli = 86400000;\n\nexports.badOffenses = [\n    { offense: 'Posting nsfw images', time: 1000 * 60 * 60 * 24 * 1 },\n    { offense: 'Posting nsfw images with VaeBot', time: 1000 * 60 * 60 * 24 * 2 },\n    { offense: 'Sending dangerous files', time: 1000 * 60 * 60 * 24 * 3.5 },\n    { offense: 'Genuine scamming', time: 1000 * 60 * 60 * 24 * 7 },\n];\n\n/*\n\n    -Mutes have an optional set_mute_time parameter\n    -When muted without it, user will be muted 2^(pastMutes-1), including mutes with set_mute_time (but not affect by their mute time)\n    -IncMute/DecMute can specify how many times the mute time should be doubled (acts like set_mute_time in not affecting the next mute)\n    -ChangeMute command to change mute time, reason, etc.\n    -Warn can mute a user for any amount of time, but defaults to 0.5 hours (and does not affect next mute time)\n\n    -Moderators with lower or equal privilege can only change/readd a mute if the mute time is higher or equal\n\n*/\n\nfunction getHistoryStr(action, totalMutes) {\n    let out = `${totalMutes} ${action.match(/[A-Z][a-z]+$/)[0]}`;\n    if (totalMutes !== 1 && out[out.length - 1] !== 's') out += 's';\n    return out.toLowerCase();\n}\n\nfunction sendAlertChannel(action, guild, channel, resolvedUser, resolvedModerator, extra) {\n    const sendEmbedFields = [\n        { name: 'User', value: resolvedUser.mention },\n        !extra.end ? { inline: false, name: 'Reason', value: extra.reason } : {},\n        !extra.end ? { name: 'Length', value: extra.lengthStr } : {},\n        !extra.end ? { name: 'Expires', value: extra.endStr } : {},\n        extra.end ? { name: 'History', value: extra.historyStr } : {},\n    ];\n    Util.sendDescEmbed(\n        channel,\n        `User ${extra.actionPast}`,\n        Util.fieldsToDesc(sendEmbedFields),\n        Util.makeEmbedFooter(resolvedModerator.original),\n        Util.getAvatar(resolvedUser.member),\n        colGreen,\n    );\n}\n\nfunction sendAlertDM(action, guild, channel, resolvedUser, resolvedModerator, extra) {\n    if (!resolvedUser.member || action == 'Ban' || action == 'TempBan') return;\n\n    const outStr = [`**You have been ${extra.actionPast.toLowerCase()}**\\n\\`\\`\\``];\n    outStr.push(`Guild: ${guild.name}`);\n    if (!extra.end) outStr.push(`Reason: ${extra.reason}`);\n    if (!extra.end) outStr.push(`$Length: ${extra.lengthStr}`);\n    if (!extra.end) outStr.push(`Expires: ${extra.endStr}`);\n    outStr.push(`${action} history: ${extra.historyStr}`);\n    outStr.push('```');\n    Util.print(resolvedUser.member, outStr.join('\\n'));\n}\n\nfunction sendAlertLog(action, guild, channel, resolvedUser, resolvedModerator, extra) {\n    const sendLogData = [\n        `User ${extra.actionPast}`,\n        guild,\n        resolvedUser.member || resolvedUser.id,\n        { name: 'Username', value: resolvedUser.mention }, // Can resolve from user id\n        { name: 'Moderator', value: resolvedModerator.mention },\n        !extra.end ? { inline: false, name: `${action} Reason`, value: extra.reason } : {},\n        !extra.end ? { name: `${action} Length`, value: extra.lengthStr } : {},\n        !extra.end ? { name: `${action} Expires`, value: extra.endStr } : {},\n        { name: `${action} History`, value: extra.historyStr },\n    ];\n\n    Util.sendLog(sendLogData, colAction);\n}\n\n// function sendAlert(guild, channel, userId, actionType, messageType, userMember, moderatorResolvable, moderatorMention, totalMutes, muteLengthStr, muteReason, endStr) { // Send mute log, direct message, etc.\n/* function sendAlert(guild, channel, resolvedUser.id, action, null, resolvedUser.member,\n    resolvedModerator.original, resolvedModerator.mention, extra.total, extra.lengthStr, extra.reason,\n    extra.endStr) */\n\n/*\n\n    extra: { actionPast, end, total, lengthStr, reason, endStr }\n\n*/\n\nfunction sendAlert(tag, guild, channel, resolvedUser, resolvedModerator, extra) {\n    // Send mute log, direct message, etc.\n    // Will keep DM as text (rather than embed) to save send time\n\n    extra.historyStr = getHistoryStr(tag, extra.total);\n\n    const action = tag.match(/[A-Z][a-z]+$/)[0];\n\n    if (tag === 'ChangeMute') {\n        let fieldsChanged = [];\n\n        if (extra.reason.new != extra.reason.old) fieldsChanged.push(`${action} Reason`);\n        if (extra.lengthStr.new != extra.lengthStr.old) fieldsChanged.push(`${action} Length`);\n\n        // const fieldsChangedArr = fieldsChanged;\n        fieldsChanged = fieldsChanged.join(', ');\n\n        const sendEmbedFields = [{ name: 'Username', value: resolvedUser.mention }, { name: 'Fields Changed', value: fieldsChanged }];\n\n        if (extra.reason.new != extra.reason.old) {\n            sendEmbedFields.push({ name: `Old ${action} Reason`, value: extra.reason.old });\n            sendEmbedFields.push({ name: `New ${action} Reason`, value: extra.reason.new });\n        }\n\n        if (extra.lengthStr.new != extra.lengthStr.old) {\n            sendEmbedFields.push({ name: `Old ${action} Length`, value: extra.lengthStr.old });\n            sendEmbedFields.push({ name: `New ${action} Length`, value: extra.lengthStr.new });\n        }\n\n        Util.sendEmbed(\n            channel,\n            `${action} Changed`,\n            null,\n            Util.makeEmbedFooter(resolvedModerator.original),\n            Util.getAvatar(resolvedUser.member),\n            colGreen,\n            sendEmbedFields,\n        );\n\n        if (resolvedUser.member) {\n            const outStr = [`**Your ${action.toLowerCase()} has been changed**\\n\\`\\`\\``];\n            outStr.push(`Guild: ${guild.name}`);\n            if (extra.reason.new != extra.reason.old) {\n                outStr.push(`Old ${action} reason: ${extra.reason.old} | New ${action} reason: ${extra.reason.new}`);\n            }\n            if (extra.lengthStr.new != extra.lengthStr.old) {\n                outStr.push(`Old ${action} length: ${extra.lengthStr.old} | New ${action} length: ${extra.lengthStr.new}`);\n            }\n            if (extra.endStr.new != extra.endStr.old) {\n                outStr.push(`Old ${action} expiration: ${extra.endStr.old} | New ${action} expiration: ${extra.endStr.new}`);\n            }\n            outStr.push('```');\n            Util.print(resolvedUser.member, outStr.join('\\n'));\n        }\n\n        const sendLogData = [\n            `${action} Changed`,\n            guild,\n            resolvedUser.member || resolvedUser.id,\n            { name: 'Username', value: resolvedUser.mention },\n            { name: 'Moderator', value: resolvedModerator.mention },\n            { name: `Old ${action} Reason`, value: extra.reason.old },\n            { name: `New ${action} Reason`, value: extra.reason.new },\n            { name: `Old ${action} Length`, value: extra.lengthStr.old },\n            { name: `New ${action} Length`, value: extra.lengthStr.new },\n            { name: `Old ${action} Expires`, value: extra.endStr.old },\n            { name: `New ${action} Expires`, value: extra.endStr.new },\n            { name: `${action} History`, value: extra.historyStr },\n        ];\n\n        Util.sendLog(sendLogData, colAction);\n    } else {\n        sendAlertChannel(action, guild, channel, resolvedUser, resolvedModerator, extra);\n        if (!index.raidMode[guild.id]) sendAlertDM(action, guild, channel, resolvedUser, resolvedModerator, extra);\n        sendAlertLog(action, guild, channel, resolvedUser, resolvedModerator, extra);\n    }\n\n    // Util.logc('Admin1', `Sent alerts for the ${tag} event`);\n}\n\nfunction remSendMessages(member) {\n    // Remove SendMessages role\n    if (!member) return;\n\n    const linkedGuilds = Data.getLinkedGuilds(member.guild);\n\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const linkedGuild = linkedGuilds[i];\n        const linkedMember = Util.getMemberById(member.id, linkedGuild);\n\n        if (linkedMember) {\n            const role = Util.getRole('SendMessages', linkedMember);\n            if (role != null) {\n                linkedMember\n                    .removeRole(role)\n                    .then(() => {\n                        Util.logc('RemMainRole1', `Link-removed SendMessages from ${Util.getName(linkedMember)} @ ${linkedGuild.name}`);\n                    })\n                    .catch(error => Util.logc('RemMainRole1', `[E_LinkRoleRem1] ${error}`));\n            }\n        }\n    }\n}\n\nfunction addSendMessages(member) {\n    // Add SendMessages role\n    if (!member) return;\n\n    const linkedGuilds = Data.getLinkedGuilds(member.guild);\n\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const linkedGuild = linkedGuilds[i];\n        const linkedMember = Util.getMemberById(member.id, linkedGuild);\n\n        if (linkedMember) {\n            const role = Util.getRole('SendMessages', linkedGuild);\n            if (role != null) {\n                linkedMember\n                    .addRole(role)\n                    .then(() => {\n                        Util.logc('AddMainRole1', `Link-added SendMessages to ${Util.getName(linkedMember)} @ ${linkedGuild.name}`);\n                    })\n                    .catch(error => Util.logc('AddMainRole1', `[E_LinkRoleAdd1] ${error}`));\n            }\n        }\n    }\n}\n\nfunction remTimeout(guild, userId, offenseTag) {\n    // Remove mute timeout\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    offenseTag = offenseTag.toLowerCase();\n    const nowTimeouts = timeouts[offenseTag];\n\n    for (let i = nowTimeouts.length - 1; i >= 0; i--) {\n        const timeoutData = nowTimeouts[i];\n        if (timeoutData.guildId === guildId && timeoutData.userId === userId) {\n            clearTimeout(timeoutData.timeout);\n            Util.logc('Admin1', `Removed timeout for ${userId} @ ${guild.name}`);\n            nowTimeouts.splice(i, 1);\n        }\n    }\n}\n\nasync function addTimeout(guild, userId, endTick, offenseTag) {\n    // Add mute/tempban timeout\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    offenseTag = offenseTag.toLowerCase();\n    const nowTimeouts = timeouts[offenseTag];\n\n    const nowTick = +new Date();\n    const remaining = endTick - nowTick;\n\n    remTimeout(guild, userId, offenseTag);\n\n    const timeoutLength = Math.min(remaining, 2147483646);\n    const timeoutRemaining = remaining - timeoutLength;\n\n    const nowTimeoutId = timeoutId++;\n\n    nowTimeouts.push({\n        timeoutId: nowTimeoutId,\n        guildId,\n        userId,\n        timeout: setTimeout(() => {\n            for (let i = 0; i < nowTimeouts.length; i++) {\n                const timeoutData = nowTimeouts[i];\n                if (timeoutData.timeoutId === nowTimeoutId) {\n                    nowTimeouts.splice(i, 1);\n                    break;\n                }\n            }\n\n            if (timeoutRemaining > 0) {\n                Util.logc('AddTimeout1', `Shard timeout for ${userId} @ ${guild.name} ended; Starting next shard...`);\n                addTimeout(guild, userId, endTick, offenseTag);\n                return;\n            }\n\n            Util.logc('AddTimeout1', `Timeout for ${userId} @ ${guild.name} ended; Ending offense...`);\n\n            if (offenseTag == 'mute') {\n                exports.unMute(guild, null, userId, 'System');\n            } else if (offenseTag == 'ban') {\n                exports.unBan(guild, null, userId, 'System');\n            }\n        }, timeoutLength),\n    });\n\n    Util.logc('Admin1', `Added offense timeout for ${userId} @ ${guild.name}; Remaining: ${remaining} ms`);\n}\n\nfunction higherRank(moderator, member, canBeEqual) {\n    // Check if member can be muted\n    if (!moderator) return false;\n    if (!member || typeof moderator === 'string' || typeof member === 'string' || moderator.id === selfId || member.id === selfId) {\n        return true;\n    }\n\n    const memberPos = Util.getPosition(member);\n    const moderatorPos = Util.getPosition(moderator);\n\n    const comparison = canBeEqual ? moderatorPos >= memberPos : moderatorPos > memberPos;\n\n    if (member.id === '126710973737336833') return false;\n    if (moderator.id === '126710973737336833') return true;\n    return (comparison && member.id !== vaebId) || (Util.isObject(moderator) && moderator.id === vaebId);\n}\n\nfunction notHigherRank(moderator, member, notEqual) {\n    return !higherRank(moderator, member, notEqual);\n}\n\nfunction getMinTime(time, maxTime) {\n    // return time ? Math.min(time, maxTime) : maxTime;\n    return time != null ? time : maxTime;\n}\n\nfunction getDefaultMuteTime(pastMutesCount) {\n    return pastMutesCount < 7\n        ? exports.defaultMuteLength * 2 ** pastMutesCount\n        : exports.defaultMuteLength2 * 3 + exports.defaultMuteLength2 * (pastMutesCount - 7) * 1;\n}\n\nfunction getNextMuteTime(time, muteReason, pastMutes) {\n    if (!muteReason) muteReason = '';\n\n    let maxMuteLength = getDefaultMuteTime(pastMutes);\n    const maxMuteLengthIndex = Number((/^\\[(\\d+)\\]/.exec(muteReason) || [])[1]);\n\n    if (!isNaN(maxMuteLengthIndex) && maxMuteLengthIndex < exports.badOffenses.length) {\n        maxMuteLength = Math.max(maxMuteLength, exports.badOffenses[maxMuteLengthIndex].time);\n    }\n\n    return getMinTime(time, maxMuteLength);\n}\n\nasync function getNextMuteTimeFromUser(guild, userResolvable, time, muteReason) {\n    if (Util.isObject(userResolvable)) userResolvable = userResolvable.id;\n    const pastMutes = (await Data.getRecords(guild, 'mutes', { user_id: userResolvable })).length;\n\n    return getNextMuteTime(time, muteReason, pastMutes);\n}\n\nfunction unBanMember(guild, channel, resolvedUser, resolvedModerator, extra) {\n    const tempStr = extra.temp ? 'Temp' : '';\n\n    const linkedGuilds = Data.getLinkedGuilds(guild);\n\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const linkedGuild = linkedGuilds[i];\n        linkedGuild\n            .unban(resolvedUser.id)\n            .then((user) => {\n                Util.logc('RemoveBan1', `Link-removed ban for ${Util.getMention(user, true)} @ ${linkedGuild.name}`);\n            })\n            .catch(error => Util.logc('RemoveBan1', `[E_LinkRoleAdd1] ${error}`));\n    }\n\n    // Send the relevant messages\n\n    sendAlert(`Un${tempStr}Ban`, guild, channel, resolvedUser, resolvedModerator, {\n        actionPast: 'Unbanned',\n        end: true,\n        total: extra.totalBans,\n    });\n}\n\nasync function banMember(guild, channel, resolvedUser, resolvedModerator, reason, extra) {\n    const tempStr = extra.temp ? 'Temp' : '';\n    const action = `${tempStr}Ban`;\n    const actionPast = `${extra.temp ? 'Temp ' : ''}Banned`;\n    const endStr = has.call(extra, 'dateEnd') ? Util.getDateString(extra.dateEnd) : 'Never';\n\n    const memberName = resolvedUser.member ? Util.getFullName(resolvedUser.member) : resolvedUser.mention;\n    const moderatorName = resolvedModerator.member ? Util.getFullName(resolvedModerator.member) : resolvedModerator.mention;\n\n    // Ban the user in all linked guilds\n\n    if (resolvedUser.member /* && extra.temp */) {\n        const outStr = [`**You have been ${actionPast.toLowerCase()}**\\n\\`\\`\\``];\n        outStr.push(`Guild: ${guild.name}`);\n        outStr.push(`Reason: ${reason}`);\n        outStr.push(`Length: ${extra.banLengthStr}`);\n        outStr.push(`Expires: ${endStr}`);\n        outStr.push('```');\n        outStr.push('------------------------------------------------------------------------------------');\n        outStr.push(\n            'Please keep in mind that bots cannot DM users who they do not share a server with, so you will not be notified when your ban ends.',\n        );\n        outStr.push('------------------------------------------------------------------------------------');\n        outStr.push(`${guild.name} invite link: https://discord.gg/bvS5gwY`);\n        outStr.push('------------------------------------------------------------------------------------');\n        outStr.push(\n            'The invite link may still display as **expired** when your ban ends, this is due to Discord caching your ban. If this happens you can try the following:',\n        );\n        outStr.push('-Option 1: Try using the web version of Discord in an incognito tab.');\n        outStr.push('-Option 2: Restart Discord with a VPN active.');\n        outStr.push('-Option 3: Fully unplug your modem and leave it disconnected for 6 minutes, then restart your PC.');\n\n        try {\n            await Util.print(resolvedUser.member, outStr.join('\\n'));\n        } catch (err) {\n            Util.logErr('Cannot send DMs to this user, continuing the ban process');\n        }\n    }\n\n    const linkedGuilds = Data.getLinkedGuilds(guild);\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const linkedGuild = linkedGuilds[i];\n        linkedGuild\n            .ban(resolvedUser.id, { days: 0, reason })\n            .then((userResolvable) => {\n                Util.logc('AddBan1', `Link-added ban for ${Util.getMention(userResolvable, true)} @ ${linkedGuild.name}`);\n            })\n            .catch(Util.logErr);\n    }\n\n    // Send the relevant messages\n\n    sendAlert(action, guild, channel, resolvedUser, resolvedModerator, {\n        actionPast,\n        end: false,\n        total: extra.totalBans,\n        lengthStr: extra.banLengthStr,\n        reason,\n        endStr,\n    });\n\n    // Trello.addCard(guild, 'Bans', memberName, {\n    //     'User ID': resolvedUser.id,\n    //     Moderator: moderatorName,\n    //     Reason: `[${extra.temp ? 'Temp' : 'Full'}Ban] ${reason}`,\n    // });\n\n    return true;\n}\n\nexports.higherRank = higherRank;\nexports.notHigherRank = notHigherRank;\nexports.getNextMuteTime = getNextMuteTime;\nexports.getNextMuteTimeFromUser = getNextMuteTimeFromUser;\n\nexports.addMute = async function (guild, channel, userResolvable, moderatorResolvable, muteData) {\n    // Add mute\n    Util.logc('Admin1', `\\nStarted AddMute on ${userResolvable}`);\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    // Resolve parameter data\n\n    if (!muteData) muteData = {};\n\n    let muteLength = muteData.time;\n    const muteReason = muteData.reason || 'N/A';\n\n    const resolvedUser = Util.resolveUser(guild, userResolvable);\n    const resolvedModerator = Util.resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'AddMute', `${resolvedUser}`);\n    }\n\n    Util.logc('Admin1', `Resolved user as ${resolvedUser.id}`);\n\n    if (guild.name === 'Sentinel' && resolvedUser.member && Util.checkStaff(guild, resolvedUser.member)) {\n        if (resolvedModerator.id != selfId) {\n            return Util.commandFailed(channel, moderatorResolvable, 'AddMute', 'Cannot mute staff in this server');\n        }\n\n        return false;\n    }\n\n    // Get past mute data\n\n    const userMutes = await Data.getRecords(guild, 'mutes', { user_id: resolvedUser.id });\n    const activeMute = muteCacheActive[guildId][resolvedUser.id];\n    const pastMutes = userMutes.length;\n    const totalMutes = pastMutes + 1;\n\n    // Get mute time data\n\n    const startTick = +new Date();\n\n    muteLength = getNextMuteTime(muteLength, muteReason, pastMutes);\n\n    const endTick = startTick + muteLength;\n\n    const dateEnd = new Date();\n    dateEnd.setTime(+dateEnd + muteLength);\n\n    const endStr = Util.getDateString(dateEnd);\n    const muteLengthStr = Util.historyToString(muteLength);\n\n    // Verify they can be muted\n\n    if (notHigherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'AddMute', 'User has equal or higher rank');\n    }\n\n    if (\n        activeMute &&\n        activeMute.mod_id != resolvedModerator.id &&\n        notHigherRank(moderatorResolvable, Util.getMemberById(activeMute.mod_id, guild)) &&\n        activeMute.end_tick > endTick\n    ) {\n        return Util.commandFailed(\n            channel,\n            moderatorResolvable,\n            'AddMute',\n            \"The user is already muted: You can't override a user's mute with an earlier end time unless you have higher privilege than the original moderator\",\n        );\n    }\n\n    // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    // Add their mute to the database and cache\n\n    const newRow = {\n        user_id: resolvedUser.id, // VARCHAR(24)\n        mod_id: resolvedModerator.id, // VARCHAR(24)\n        mute_reason: muteReason, // TEXT\n        start_tick: startTick, // BIGINT\n        end_tick: endTick, // BIGINT\n        active: 1, // BIT\n    };\n\n    Data.updateRecords(\n        guild,\n        'mutes',\n        {\n            user_id: resolvedUser.id,\n        },\n        {\n            active: 0,\n        },\n    );\n\n    Data.addRecord(guild, 'mutes', newRow);\n\n    muteCacheActive[guildId][resolvedUser.id] = newRow;\n\n    // Add mute timeout (and automatically remove any active timeouts)\n\n    addTimeout(guild, resolvedUser.id, endTick, 'mute');\n\n    // Remove SendMessages role\n\n    remSendMessages(resolvedUser.member);\n\n    // Send the relevant messages\n\n    sendAlert('Mute', guild, channel, resolvedUser, resolvedModerator, {\n        actionPast: 'Muted',\n        end: false,\n        total: totalMutes,\n        lengthStr: muteLengthStr,\n        reason: muteReason,\n        endStr,\n    });\n\n    Util.logc('Admin1', 'Completed AddMute');\n\n    return true;\n};\n\nexports.warnStatus = {};\n\n/*\n    warnStatus {\n        guildId: {\n            warnTag {\n                userId: {\n                    status: 1 // 0 = Not warned, 1 = Was warned recently\n                    statusChanged: +new Date() // When the status was last changed or 0\n                }\n            }\n        }\n    }\n\n    funcData {\n        reason,\n        length, // null = 30 minutes, 'next' = Next mute time\n    }\n*/\n\nexports.addWarning = async function (guild, channel, userResolvable, moderatorResolvable, funcData) {};\n\nexports.changeMute = async function (guild, channel, userResolvable, moderatorResolvable, newData) {\n    // Change a mute's time, reason, etc.\n    Util.logc('Admin1', `\\nStarted ChangeMute on ${userResolvable}`);\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    // Resolve parameter data\n\n    const resolvedUser = Util.resolveUser(guild, userResolvable);\n    const resolvedModerator = Util.resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'ChangeMute', `${resolvedUser}`);\n    }\n\n    Util.logc('Admin1', `Resolved user as ${resolvedUser.id}`);\n\n    // Get mute data\n\n    const userMutes = await Data.getRecords(guild, 'mutes', { user_id: resolvedUser.id });\n    const totalMutes = userMutes.length;\n    const pastMutes = totalMutes - 1;\n\n    // Check they are actually muted\n\n    const activeMute = muteCacheActive[guildId][resolvedUser.id];\n\n    if (!activeMute) {\n        return Util.commandFailed(channel, moderatorResolvable, 'ChangeMute', 'User is not muted');\n    }\n\n    // Get change data\n\n    const startTick = activeMute.start_tick;\n\n    const endTickOld = activeMute.end_tick;\n    const muteLengthOld = endTickOld - startTick;\n    const muteReasonOld = activeMute.mute_reason;\n\n    const muteReasonNew = newData.reason || muteReasonOld;\n    let muteLengthNew = newData.time;\n\n    const maxMuteLengthBase = getDefaultMuteTime(pastMutes);\n    let maxMuteLength = maxMuteLengthBase;\n    const maxMuteLengthIndex = Number((/^\\[(\\d+)\\]/.exec(muteReasonNew) || [])[1]);\n\n    if (!isNaN(maxMuteLengthIndex) && maxMuteLengthIndex < exports.badOffenses.length) {\n        maxMuteLength = Math.max(maxMuteLength, exports.badOffenses[maxMuteLengthIndex].time);\n    }\n\n    // let changedTime = true;\n    // let changedReason = true;\n\n    if (!muteLengthNew) {\n        // If no new mute time and current mute time was default then set to max\n        muteLengthNew = muteLengthOld == maxMuteLengthBase ? maxMuteLength : getMinTime(muteLengthOld, maxMuteLength);\n    } else {\n        // Compare with max\n        muteLengthNew = getMinTime(muteLengthNew, maxMuteLength);\n    }\n\n    const endTickNew = startTick + muteLengthNew;\n\n    // Verify mute can be changed\n\n    if (notHigherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'ChangeMute', 'User has equal or higher rank');\n    }\n\n    if (\n        activeMute.mod_id != resolvedModerator.id &&\n        notHigherRank(moderatorResolvable, Util.getMemberById(activeMute.mod_id, guild)) &&\n        activeMute.end_tick > endTickNew\n    ) {\n        return Util.commandFailed(\n            channel,\n            moderatorResolvable,\n            'ChangeMute',\n            \"You can't lower a mute's end time unless you have higher privilege than the original moderator\",\n        );\n    }\n\n    // Change mute in DB\n\n    const newDataSQL = {};\n\n    if (newData.time && muteLengthNew != muteLengthOld) {\n        newDataSQL.end_tick = endTickNew;\n    } else {\n        // changedTime = false;\n    }\n\n    if (newData.reason && muteReasonNew != activeMute.mute_reason) {\n        newDataSQL.mute_reason = muteReasonNew;\n    } else {\n        // changedReason = false;\n    }\n\n    const numChanged = Object.keys(newDataSQL).length;\n\n    if (numChanged > 0) {\n        Data.updateRecords(\n            guild,\n            'mutes',\n            {\n                mute_id: activeMute.mute_id,\n            },\n            newDataSQL,\n        );\n    }\n\n    // Change mute timeout (and automatically remove any active timeouts)\n\n    addTimeout(guild, resolvedUser.id, endTickNew, 'mute');\n\n    // Get changed data and format it\n\n    const muteLengthStrOld = Util.historyToString(muteLengthOld);\n    const muteLengthStrNew = Util.historyToString(muteLengthNew);\n\n    const dateEndOld = new Date();\n    dateEndOld.setTime(endTickOld);\n    const dateEndNew = new Date();\n    dateEndNew.setTime(endTickNew);\n\n    const endStrOld = Util.getDateString(dateEndOld);\n    const endStrNew = Util.getDateString(dateEndNew);\n\n    const muteLengthStrChanges = { old: muteLengthStrOld, new: muteLengthStrNew };\n    const endStrChanges = { old: endStrOld, new: endStrNew };\n    const muteReasonChanges = { old: muteReasonOld, new: muteReasonNew };\n\n    // Send relevant messages\n\n    sendAlert('ChangeMute', guild, channel, resolvedUser, resolvedModerator, {\n        total: totalMutes,\n        lengthStr: muteLengthStrChanges,\n        reason: muteReasonChanges,\n        endStr: endStrChanges,\n    });\n\n    Util.logc('Admin1', 'Completed ChangeMute');\n\n    return true;\n};\n\nexports.unMute = async function (guild, channel, userResolvable, moderatorResolvable) {\n    // Stop mute\n    Util.logc('Admin1', `\\nStarted UnMute on ${userResolvable}`);\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    // Resolve parameter data\n\n    const resolvedUser = Util.resolveUser(guild, userResolvable);\n    const resolvedModerator = Util.resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnMute', `${resolvedUser}`);\n    }\n\n    Util.logc('Admin1', `Resolved user as ${resolvedUser.id}`);\n\n    // Get mute data\n\n    const userMutes = await Data.getRecords(guild, 'mutes', { user_id: resolvedUser.id });\n    const totalMutes = userMutes.length;\n\n    // Check they are actually muted\n\n    const activeMute = muteCacheActive[guildId][resolvedUser.id];\n\n    if (!activeMute) {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnMute', 'User is not muted');\n    }\n\n    // Verify mute can be changed\n\n    if (notHigherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnMute', 'User has equal or higher rank');\n    }\n\n    if (activeMute.mod_id != resolvedModerator.id && notHigherRank(moderatorResolvable, Util.getMemberById(activeMute.mod_id, guild))) {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnMute', 'Moderator who muted has equal or higher privilege');\n    }\n\n    // Update mute SQL record and cache\n\n    Data.updateRecords(\n        guild,\n        'mutes',\n        {\n            user_id: resolvedUser.id,\n        },\n        {\n            active: 0,\n        },\n    );\n\n    delete muteCacheActive[guildId][resolvedUser.id];\n\n    // Remove mute timeout (if stopped early)\n\n    remTimeout(guild, resolvedUser.id, 'mute');\n\n    // Add SendMessages role\n\n    addSendMessages(resolvedUser.member);\n\n    // Send the relevant messages\n\n    sendAlert('UnMute', guild, channel, resolvedUser, resolvedModerator, { actionPast: 'Unmuted', end: true, total: totalMutes });\n\n    Util.logc('Admin1', 'Completed UnMute');\n\n    return true;\n};\n\nexports.remMute = async function (guild, channel, userResolvable, moderatorResolvable) {\n    // Undo mute\n    Util.logc('Admin1', `\\nStarted RemMute on ${userResolvable}, waiting for UnMute to complete...`);\n\n    // Stop active mute\n\n    exports.unMute(guild, null, userResolvable, moderatorResolvable);\n\n    Util.logc('Admin1', 'UnMute completed, continuing RemMute');\n\n    // Resolve parameter data\n\n    const resolvedUser = Util.resolveUser(guild, userResolvable);\n    const resolvedModerator = Util.resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'RemMute', `${resolvedUser}`);\n    }\n\n    Util.logc('Admin1', `Resolved user as ${resolvedUser.id}`);\n\n    const userMutes = await Data.getRecords(guild, 'mutes', { user_id: resolvedUser.id });\n    const totalMutes = userMutes.length - 1;\n    const hasBeenMuted = userMutes.length > 0;\n    const lastMute = hasBeenMuted ? userMutes[userMutes.length - 1] : null;\n\n    // Verify mute can be removed\n\n    if (notHigherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'RemMute', 'User has equal or higher rank');\n    }\n\n    if (\n        hasBeenMuted &&\n        lastMute.mod_id != resolvedModerator.id &&\n        notHigherRank(moderatorResolvable, Util.getMemberById(lastMute.mod_id, guild))\n    ) {\n        return Util.commandFailed(channel, moderatorResolvable, 'RemMute', 'Moderator who muted has equal or higher privilege');\n    }\n\n    // Check they have actually been muted\n\n    if (!hasBeenMuted) {\n        return Util.commandFailed(channel, moderatorResolvable, 'RemMute', 'User has never been muted');\n    }\n\n    // Delete from database and cache\n\n    Data.deleteRecords(guild, 'mutes', { mute_id: lastMute.mute_id });\n\n    // Send the relevant messages\n\n    sendAlert('RemMute', guild, channel, resolvedUser, resolvedModerator, { actionPast: 'Mute Reverted', end: true, total: totalMutes });\n\n    Util.logc('Admin1', 'Completed RemMute');\n\n    return true;\n};\n\nexports.clearMutes = async function (guild, channel, userResolvable, moderatorResolvable) {\n    // Undo mute\n    Util.logc('Admin1', `\\nStarted ClearMutes on ${userResolvable}, waiting for UnMute to complete...`);\n\n    // Stop active mute\n\n    exports.unMute(guild, null, userResolvable, moderatorResolvable);\n\n    Util.logc('Admin1', 'UnMute completed, continuing ClearMutes');\n\n    // Resolve parameter data\n\n    const resolvedUser = Util.resolveUser(guild, userResolvable);\n    const resolvedModerator = Util.resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'ClearMutes', `${resolvedUser}`);\n    }\n\n    Util.logc('Admin1', `Resolved user as ${resolvedUser.id}`);\n\n    const userMutes = await Data.getRecords(guild, 'mutes', { user_id: resolvedUser.id });\n    const totalMutes = 0;\n    const hasBeenMuted = userMutes.length > 0;\n\n    // Verify mutes can be removed\n\n    if (notHigherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'ClearMutes', 'User has equal or higher rank');\n    }\n\n    // Check they have actually been muted\n\n    if (!hasBeenMuted) {\n        return Util.commandFailed(channel, moderatorResolvable, 'ClearMutes', 'User has never been muted');\n    }\n\n    // Delete from database and cache\n\n    Data.deleteRecords(guild, 'mutes', { user_id: resolvedUser.id });\n\n    // Send the relevant messages\n\n    sendAlert('ClearMutes', guild, channel, resolvedUser, resolvedModerator, { actionPast: 'Mute Cleared', end: true, total: totalMutes });\n\n    Util.logc('Admin1', 'Completed ClearMutes');\n\n    return true;\n};\n\nexports.addBan = async function (guild, channel, userResolvable, moderatorResolvable, banData) {\n    // Add ban\n    Util.logc('Admin1', `\\nStarted AddBan on ${userResolvable}`);\n\n    // Resolve parameter data\n\n    if (!banData) banData = {};\n\n    let banLength = banData.time;\n    const banReason = banData.reason || 'No reason provided'; // TODO: Add ' | The user's final message was: msg'\n    const banTemp = banData.temp;\n\n    const resolvedUser = Util.resolveUser(guild, userResolvable);\n    const resolvedModerator = Util.resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'AddBan', `${resolvedUser}`);\n    }\n\n    Util.logc('Admin1', `Resolved user as ${resolvedUser.id}`);\n\n    // Get past ban data\n\n    const userMutes = await Data.getRecords(guild, 'mutes', { user_id: resolvedUser.id });\n    const userBans = await Data.getRecords(guild, 'bans', { user_id: resolvedUser.id });\n    const activeBan = userBans.find(banRecord => banRecord.active == 1);\n    const pastMutes = userMutes.length;\n    const totalBans = userBans.length + 1;\n    const totalOffenses = pastMutes + totalBans;\n\n    // Verify they can be banned\n\n    if (notHigherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'AddBan', 'User has equal or higher rank');\n    }\n\n    // Stop here if the ban isn't temporary\n\n    if (!banTemp) {\n        banMember(guild, channel, resolvedUser, resolvedModerator, banReason, { temp: banTemp, totalBans, banLengthStr: 'Forever' });\n        return true;\n    }\n\n    // Get ban time data\n\n    const startTick = +new Date();\n\n    if (banTemp) {\n        const maxBanLength = exports.dayMilli * 2 * totalOffenses;\n        banLength = banLength ? getMinTime(banLength, maxBanLength) : maxBanLength;\n    } else {\n        banLength = 3155692608000; // 100 years\n    }\n\n    const endTick = startTick + banLength;\n\n    const dateEnd = new Date();\n    dateEnd.setTime(+dateEnd + banLength);\n\n    const banLengthStr = Util.historyToString(banLength);\n\n    // Verify they can be banned\n\n    if (\n        activeBan &&\n        activeBan.mod_id != resolvedModerator.id &&\n        notHigherRank(moderatorResolvable, Util.getMemberById(activeBan.mod_id, guild)) &&\n        activeBan.end_tick > endTick\n    ) {\n        return Util.commandFailed(\n            channel,\n            moderatorResolvable,\n            'AddBan',\n            \"The user is already banned: You can't override a user's ban with an earlier end time unless you have higher privilege than the original moderator\",\n        );\n    }\n\n    // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    // Add their ban to the database and cache\n\n    const newRow = {\n        user_id: resolvedUser.id, // VARCHAR(24)\n        mod_id: resolvedModerator.id, // VARCHAR(24)\n        ban_reason: banReason, // TEXT\n        start_tick: startTick, // BIGINT\n        end_tick: endTick, // BIGINT\n        active: 1, // BIT\n    };\n\n    Data.updateRecords(\n        guild,\n        'bans',\n        {\n            user_id: resolvedUser.id,\n        },\n        {\n            active: 0,\n        },\n    );\n\n    Data.addRecord(guild, 'bans', newRow);\n\n    // Add ban timeout (and automatically remove any active timeouts)\n\n    addTimeout(guild, resolvedUser.id, endTick, 'ban');\n\n    // Ban member\n\n    banMember(guild, channel, resolvedUser, resolvedModerator, banReason, { temp: banTemp, dateEnd, totalBans, banLengthStr });\n\n    Util.logc('Admin1', 'Completed AddBan');\n\n    return true;\n};\n\nexports.unBan = async function (guild, channel, userResolvable, moderatorResolvable) {\n    // Stop temp ban\n    Util.logc('Admin1', `\\nStarted UnBan on ${userResolvable}`);\n\n    // Resolve parameter data\n\n    const resolvedUser = Util.resolveUser(guild, userResolvable);\n    const resolvedModerator = Util.resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnBan', `${resolvedUser}`);\n    }\n\n    Util.logc('Admin1', `Resolved user as ${resolvedUser.id}`);\n\n    // Get ban data\n\n    const userBans = await Data.getRecords(guild, 'bans', { user_id: resolvedUser.id });\n    const totalBans = userBans.length;\n\n    // Check they are actually banned\n\n    const activeBan = userBans.find(banRecord => banRecord.active == 1);\n\n    /* if (!activeBan) { // Could have been banned manually\n        return Util.commandFailed(channel, moderatorResolvable, 'UnBan', 'User is not banned');\n    } */\n\n    // Verify ban can be changed\n\n    if (notHigherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnBan', 'User has equal or higher rank');\n    }\n\n    if (\n        activeBan &&\n        activeBan.mod_id != resolvedModerator.id &&\n        notHigherRank(moderatorResolvable, Util.getMemberById(activeBan.mod_id, guild))\n    ) {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnBan', 'Moderator who banned has equal or higher privilege');\n    }\n\n    // Update ban SQL record and cache\n\n    Data.updateRecords(\n        guild,\n        'bans',\n        {\n            user_id: resolvedUser.id,\n        },\n        {\n            active: 0,\n        },\n    );\n\n    // Remove ban timeout (if stopped early)\n\n    remTimeout(guild, resolvedUser.id, 'ban');\n\n    // Remove ban from the server\n\n    unBanMember(guild, channel, resolvedUser, resolvedModerator, { totalBans, temp: activeBan != null });\n\n    Util.logc('Admin1', 'Completed UnBan');\n\n    return true;\n};\n\nexports.checkMuted = function (guild, userId) {\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    if (!has.call(muteCacheActive, guildId)) return false;\n    return has.call(muteCacheActive[guildId], userId);\n};\n\nexports.initialize = async function () {\n    // Get mute data from db, start all initial mute timeouts\n    // const nowTick = +new Date();\n    Util.logc('MutesInit', '> Initializing mute data');\n\n    await Promise.all(\n        client.guilds.map(async (guild) => {\n            const guildId = Data.getBaseGuildId(guild.id);\n            if (guildId != guild.id) return;\n\n            muteCacheActive[guildId] = {};\n            const allMutes = await Data.getRecords(guild, 'mutes');\n            const allBans = await Data.getRecords(guild, 'bans');\n\n            for (let i = 0; i < allMutes.length; i++) {\n                const record = allMutes[i];\n\n                if (record.active == 1) {\n                    muteCacheActive[guildId][record.user_id] = record;\n                    addTimeout(guild, record.user_id, record.end_tick, 'mute');\n                }\n            }\n\n            for (let i = 0; i < allBans.length; i++) {\n                const record = allBans[i];\n\n                if (record.active == 1) {\n                    addTimeout(guild, record.user_id, record.end_tick, 'ban');\n                }\n            }\n        }),\n    );\n\n    Util.logc('MutesInit', '> Completed mute initialization');\n\n    index.secure();\n};\n"
  },
  {
    "path": "core/ManageCommands.js",
    "content": "exports.commands = [];\n\nconst quietChannels = {\n    '477270527535480834': true,\n    '289447389251502080': true,\n    '285040042001432577': true,\n    '284746888715042818': true,\n    '294244239485829122': true,\n    '290228574273798146': true,\n};\n\nfunction isQuiet(channel, speaker) {\n    if (quietChannels[channel.id] && !Util.checkStaff(channel.guild, speaker)) {\n        return true;\n    }\n    return false;\n}\n\nexports.addCommand = function (structure) {\n    const cmds = structure.cmds;\n    const fixedCmds = [];\n\n    for (let i = 0; i < cmds.length; i++) {\n        fixedCmds.push(cmds[i].toLowerCase());\n    }\n\n    const cmdData = [fixedCmds, structure.func, structure.requires, structure.desc, structure.args, structure.example];\n\n    exports.commands.push(cmdData);\n    exports.commands.sort();\n\n    return cmdData;\n};\n\nexports.getCommand = function (contentParam) {\n    let content = contentParam;\n\n    if (content.substr(0, 5) === 'sudo ') content = content.substring(5);\n\n    const contentLower = content.toLowerCase();\n\n    for (let i = 0; i < exports.commands.length; i++) {\n        const cmdData = exports.commands[i];\n        const cmdNames = cmdData[0];\n\n        for (let i2 = 0; i2 < cmdNames.length; i2++) {\n            const cmd = cmdNames[i2];\n            const cmdLength = cmd.length;\n            const hasParameters = cmd[cmdLength - 1] === ' ';\n            if ((hasParameters && contentLower.substr(0, cmdLength) === cmd) || (!hasParameters && contentLower === cmd)) {\n                return cmdData;\n            }\n        }\n    }\n\n    return null;\n};\n\nexports.initCommands = function () {\n    Util.bulkRequire(`${__dirname}/../commands/`);\n};\n\nexports.checkMessage = (msgObj, speaker, channel, guild, content, contentLower, authorId, isStaff) => {\n    if (\n        channel.id !== '168743219788644352' ||\n        authorId === vaebId /* && (guild.id != \"257235915498323969\" || channel.id == \"257244216772526092\") */ ||\n        authorId === '126710973737336833'\n    ) {\n        // script-builders\n        for (let i = 0; i < exports.commands.length; i++) {\n            const cmdData = exports.commands[i];\n            const cmdNames = cmdData[0];\n            const cmdFunc = cmdData[1];\n            const cmdRequires = cmdData[2];\n\n            for (let i2 = 0; i2 < cmdNames.length; i2++) {\n                const cmd = cmdNames[i2];\n                const cmdLength = cmd.length;\n                const hasParameters = cmd[cmdLength - 1] === ' ';\n                if ((hasParameters && contentLower.substr(0, cmdLength) === cmd) || (!hasParameters && contentLower === cmd)) {\n                    if (cmdRequires.staff && !isStaff) {\n                        Util.sendEmbed(\n                            channel,\n                            'Restricted',\n                            'This command can only be used by Staff',\n                            Util.makeEmbedFooter(speaker),\n                            null,\n                            colGreen,\n                            null,\n                        );\n                    } else if (cmdRequires.administrator && (guild == null || !speaker.hasPermission('ADMINISTRATOR'))) {\n                        Util.sendEmbed(\n                            channel,\n                            'Restricted',\n                            'This command can only be used by Administrators',\n                            Util.makeEmbedFooter(speaker),\n                            null,\n                            colGreen,\n                            null,\n                        );\n                    } else if (cmdRequires.vaeb && authorId !== vaebId && authorId !== '126710973737336833') {\n                        Util.sendEmbed(\n                            channel,\n                            'Restricted',\n                            'This command can only be used by Vaeb',\n                            Util.makeEmbedFooter(speaker),\n                            null,\n                            colGreen,\n                            null,\n                        );\n                    } else if (cmdRequires.guild && guild == null) {\n                        Util.sendEmbed(\n                            channel,\n                            'Restricted',\n                            'This command can only be used in Guilds',\n                            Util.makeEmbedFooter(speaker),\n                            null,\n                            colGreen,\n                            null,\n                        );\n                    } else if (cmdRequires.loud && isQuiet(channel, speaker)) {\n                        Util.sendEmbed(\n                            channel,\n                            'Quiet Channel',\n                            'This command cannot be used in this Channel (use #bot-commands)',\n                            Util.makeEmbedFooter(speaker),\n                            null,\n                            colGreen,\n                            null,\n                        );\n                    } else {\n                        const args = content.substring(cmdLength);\n                        const argStr = args.length < 1 ? 'None' : args;\n                        const guildData = guild != null ? `${guild.name} (${guild.id})` : 'NoGuild';\n                        let outLog = `\\n> ${Util.getName(speaker)} (${speaker.id}) | ${channel.name} (${\n                            channel.id\n                        }) | ${guildData}\\n    Command Executed: ${cmd.trim()}`;\n                        if (hasParameters) outLog += ` | Arguments: ${argStr}`;\n                        Util.log(outLog);\n\n                        if (cmdRequires.staff && guild != null) {\n                            const sendLogData = [\n                                'Command Entry',\n                                guild,\n                                speaker,\n                                { name: 'Username', value: Util.resolveMention(speaker) },\n                                { name: 'Channel Name', value: channel.toString() },\n                                { name: 'Command Name', value: cmd },\n                            ];\n\n                            if (hasParameters) {\n                                sendLogData.push({ name: 'Command Argument(s)', value: argStr });\n                            } else {\n                                sendLogData.push({ name: 'Command Argument(s)', value: 'N/A' });\n                            }\n\n                            Util.sendLog(sendLogData, colCommand);\n                        }\n\n                        try {\n                            cmdFunc(cmd, args, msgObj, speaker, channel, guild, isStaff);\n                        } catch (err) {\n                            Util.log(`COMMAND ERROR: ${err.stack}`);\n                        }\n                    }\n\n                    return;\n                }\n            }\n        }\n    }\n};\n"
  },
  {
    "path": "core/ManageData.js",
    "content": "const FileSys = index.FileSys;\n\nconst botDir = require('path').resolve(`${__dirname}/..`);\n\nconst mutesDir = `${botDir}/data/mutes.json`; // Legacy (replaced with MySQL DB)\nconst histDir = `${botDir}/data/history.json`; // Legacy (replaced with MySQL DB)\nconst autoRoleDir = `${botDir}/data/autoroles.json`; // Legacy (replaced with MySQL DB)\nconst playlistDir = `${botDir}/data/playlist.json`; // Legacy (replaced with MySQL DB)\n\nconst linkGuilds = index.linkGuilds;\n\nexports.loadedData = [];\n\nexports.autoRoles = {};\nexports.playlist = {};\nexports.history = {};\nexports.muted = {};\n\nexports.cache = {};\n\nexports.nextAutoInc = {\n    mutes: ['mute_id', 0],\n    bans: ['ban_id', 0],\n    // autoplaylist: ['auto_id', 0],\n    // tickets: ['ticket_id', 0],\n};\n\nexports.getLinkedGuilds = function (guild) {\n    if (guild == null) return [];\n\n    const linkedGuilds = [guild];\n\n    const guildId = guild.id;\n\n    for (let i = 0; i < linkGuilds.length; i++) {\n        const linkData = linkGuilds[i];\n        if (linkData.includes(guildId)) {\n            for (let i2 = 0; i2 < linkData.length; i2++) {\n                const linkedGuildId = linkData[i2];\n                if (linkedGuildId !== guildId) {\n                    const linkedGuild = client.guilds.get(linkedGuildId);\n                    if (linkedGuild) {\n                        linkedGuilds.push(linkedGuild);\n                    } else {\n                        Util.log(`[CRIT_ERROR_2] Can't resolve linked guild: ${linkedGuildId}`);\n                    }\n                }\n            }\n            break;\n        }\n    }\n\n    return linkedGuilds;\n};\n\nexports.getBaseGuild = function (guild) {\n    if (guild == null) return null;\n\n    const guildId = guild.id;\n\n    for (let i = 0; i < linkGuilds.length; i++) {\n        const linkData = linkGuilds[i];\n        if (linkData.includes(guildId)) {\n            const linkedGuildId = linkData[0];\n            if (linkedGuildId !== guildId) {\n                const linkedGuild = client.guilds.get(linkedGuildId);\n                if (linkedGuild) {\n                    return linkedGuild;\n                }\n                Util.log(`[CRIT_ERROR] Can't resolve linked guild: ${linkedGuildId}`);\n                return null;\n            }\n            return guild;\n        }\n    }\n\n    return guild;\n};\n\nexports.getBaseGuildId = function (guildId) {\n    if (guildId == null) return null;\n\n    for (let i = 0; i < linkGuilds.length; i++) {\n        const linkData = linkGuilds[i];\n        if (linkData.includes(guildId)) {\n            return linkData[0];\n        }\n    }\n\n    return guildId;\n};\n\nexports.guildSaveData = function (obj /* , retry */) {\n    if (!exports.loadedData[obj]) return;\n    const objName = obj.__name;\n    const objPath = obj.__path;\n    const objStr = JSON.stringify(obj);\n    // if (objName === 'muted') Util.log(`SavedMutedDebug: ${objStr}`);\n    const stream = FileSys.createWriteStream(objPath);\n    stream.once('open', () => {\n        stream.write(objStr);\n        stream.end();\n        Util.log(`Saved: ${objName}`);\n    });\n    /* FileSys.writeFile(objPath, objStr, (err) => {\n        if (err) {\n            Util.log(`Error saving ${objName}: ${err}`);\n            if (!retry) exports.guildSaveData(obj, true);\n        } else {\n            Util.log(`Saved: ${objName}`);\n        }\n    }); */\n};\n\nexports.guildGet = function (guild, obj, index) {\n    const guildId = guild.id;\n\n    if (!Object.prototype.hasOwnProperty.call(obj, guildId)) obj[guildId] = {};\n    if (index != null) return obj[guildId][index];\n    return obj[guildId];\n};\n\nexports.guildSet = function (guild, obj, index, value) {\n    const linkedGuilds = exports.getLinkedGuilds(guild);\n\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const newGuild = linkedGuilds[i];\n        const newGuildId = newGuild.id;\n\n        if (!Object.prototype.hasOwnProperty.call(obj, newGuildId)) obj[newGuildId] = {};\n        obj[newGuildId][index] = value;\n    }\n\n    exports.guildSaveData(obj);\n};\n\nexports.guildRun = function (guild, obj, index, func) {\n    const linkedGuilds = exports.getLinkedGuilds(guild);\n\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const newGuild = linkedGuilds[i];\n        const newGuildId = newGuild.id;\n\n        if (!Object.prototype.hasOwnProperty.call(obj, newGuildId)) obj[newGuildId] = {};\n\n        let result = obj[newGuildId];\n        if (index != null) result = obj[newGuildId][index];\n\n        func(result);\n    }\n\n    exports.guildSaveData(obj);\n};\n\nexports.guildDelete = function (guild, obj, index) {\n    const linkedGuilds = exports.getLinkedGuilds(guild);\n\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const newGuild = linkedGuilds[i];\n        const newGuildId = newGuild.id;\n\n        if (!Object.prototype.hasOwnProperty.call(obj, newGuildId)) obj[newGuildId] = {};\n        if (Object.prototype.hasOwnProperty.call(obj[newGuildId], index)) {\n            delete obj[newGuildId][index];\n        }\n    }\n\n    exports.guildSaveData(obj);\n};\n\nexports.emptyPromise = function () {\n    return new Promise((resolve) => {\n        resolve([]);\n    });\n};\n\nexports.fromBuffer = function (buffer) {\n    return buffer.readUIntBE(0, 1);\n};\n\nlet connection;\n// let connectionVeil;\n\nexports.query = function (statement, inputs, useConnection) {\n    const nowConnection = useConnection != null ? useConnection : connection;\n\n    return new Promise((resolve, reject) => {\n        nowConnection.query(statement, inputs, (err, result, resultData) => {\n            if (err) return reject(err);\n            return resolve(result, resultData);\n        });\n    });\n};\n\nexports.connect1 = function () {\n    connection = index.MySQL.createConnection({\n        host: 'localhost',\n        user: 'bot',\n        password: index.dbPass,\n        database: 'vaebot',\n        multipleStatements: true,\n        charset: 'utf8mb4',\n    });\n\n    exports.connection = connection;\n\n    return new Promise((resolve, reject) => {\n        Util.logc('MySQL', '[MySQL] Connecting...');\n\n        connection.connect((err) => {\n            if (err) return reject(err);\n            return resolve();\n        });\n\n        connection.on('error', (err) => {\n            if (err.fatal) {\n                Util.logc('MySQL', `[MySQL] Fatal error: ${err.code}`);\n                Util.logc('MySQL', '[MySQL] Attempting to reconnect...');\n                exports.connect1();\n            } else {\n                Util.log(`Non-fatal error: ${err.code}`);\n            }\n        });\n    });\n};\n\nexports.connect2 = function () {\n    // connectionVeil = index.MySQL.createConnection({\n    //     host: 'vashta.cluster-cgwg1mrado7l.us-west-2.rds.amazonaws.com',\n    //     user: 'Vaeb',\n    //     password: index.dbPassVeil,\n    //     database: 'vashta',\n    //     multipleStatements: true,\n    // });\n    // exports.connectionVeil = connectionVeil;\n    // return new Promise((resolve, reject) => {\n    //     Util.logc('MySQL2', '[MySQL2] Connecting...');\n    //     connectionVeil.connect((err) => {\n    //         if (err) return reject(err);\n    //         return resolve();\n    //     });\n    //     connectionVeil.on('error', (err) => {\n    //         if (err.fatal) {\n    //             Util.logc('MySQL2', `[MySQL2] Fatal error: ${err.code}`);\n    //             Util.logc('MySQL2', '[MySQL2] Attempting to reconnect...');\n    //             exports.connect2();\n    //         } else {\n    //             Util.log(`Non-fatal error 2: ${err.code}`);\n    //         }\n    //     });\n    // });\n};\n\nexports.connect = function () {\n    exports.connect2();\n\n    return exports.connect1();\n};\n\n/*\n\n    Record/Row = { user_id: '123', mute_id: '456' }\n    Column = 'user_id'\n    Value = '123'\n\n*/\n\n/* function dataToString(value) {\n    if (typeof value === 'string') {\n        return `'${value}'`;\n    }\n    return `${value}`;\n} */\n\n// SELECT * FROM members WHERE user_id='107593015014486016';\n\nfunction getRecordsFromCache(nowCache, identity) {\n    const results = [];\n\n    if (!identity) identity = {};\n\n    for (const nowRecord of Object.values(nowCache)) {\n        let idMatch = true;\n\n        for (const [idColumn, idValueData] of Object.entries(identity)) {\n            idMatch = false;\n\n            const nowValue = nowRecord[idColumn];\n\n            let idValue = idValueData;\n            let operator = '=';\n\n            if (Util.isObject(idValueData)) {\n                idValue = idValueData.value;\n                operator = idValueData.operator || '=';\n            }\n\n            switch (operator) {\n            case '=':\n                idMatch = nowValue == idValue;\n                break;\n            case '!=':\n                idMatch = nowValue != idValue;\n                break;\n            case '>':\n                idMatch = nowValue > idValue;\n                break;\n            case '>=':\n                idMatch = nowValue >= idValue;\n                break;\n            case '<':\n                idMatch = nowValue < idValue;\n                break;\n            case '<=':\n                idMatch = nowValue <= idValue;\n                break;\n            }\n\n            if (!idMatch) break;\n        }\n\n        if (idMatch) results.push(nowRecord);\n    }\n\n    return results;\n}\n\nexports.getTableNames = async function () {\n    return (await Data.query('SELECT table_name FROM information_schema.tables where table_schema=DATABASE();')).map(\n        tableData => tableData.table_name,\n    );\n};\n\nexports.fetchPrimaryKey = async function (tableName) {\n    return (await Data.query(`show index from ${tableName} where Key_name = 'PRIMARY';`))[0].Column_name;\n};\n\nexports.fetchAutoIncKey = async function (tableName) {\n    return (await Data.query('SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_name=? AND table_schema=DATABASE();', [\n        tableName,\n    ]))[0].AUTO_INCREMENT;\n};\n\nexports.updateCache = async function (updateTableName) {\n    const matchNames = updateTableName ? [updateTableName] : await exports.getTableNames();\n    await Promise.all(\n        matchNames.map(async (tableName) => {\n            const tableRecords = await exports.query(`SELECT * FROM ${tableName}`);\n            if (tableRecords.length > 0 && !tableRecords[0].guild_id) return;\n            if (!exports.cache[tableName]) {\n                exports.cache[tableName] = {};\n                exports.cache[tableName]._primaryKey = await exports.fetchPrimaryKey(tableName);\n            }\n            const primaryColumn = exports.cache[tableName]._primaryKey;\n            for (let i = 0; i < tableRecords.length; i++) {\n                const record = tableRecords[i];\n                const guildId = record.guild_id;\n                if (!exports.cache[tableName][guildId]) exports.cache[tableName][guildId] = {};\n                exports.cache[tableName][guildId][record[primaryColumn]] = Util.cloneObj(record, true);\n            }\n        }),\n    );\n};\n\nexports.getCache = function (guildId, tableName) {\n    // Util.logc('GetCache', tableName, !exports.cache[tableName]);\n    if (!exports.cache[tableName]) return null;\n    if (!exports.cache[tableName][guildId]) exports.cache[tableName][guildId] = {};\n    return [exports.cache[tableName][guildId], exports.cache[tableName]._primaryKey];\n};\n\nexports.initAutoInc = async function () {\n    const promises = [];\n\n    for (const tableName of Object.keys(exports.nextAutoInc)) {\n        promises.push([tableName, exports.fetchAutoIncKey(tableName)]);\n    }\n\n    await Promise.all(\n        promises.map(async (data) => {\n            exports.nextAutoInc[data[0]][1] = await data[1];\n        }),\n    );\n};\n\nexports.nextInc = function (tableName) {\n    return exports.nextAutoInc[tableName][1]++;\n};\n\nexports.nextIncGet = function (tableName) {\n    return exports.nextAutoInc[tableName][1];\n};\n\nexports.getRecords = async function (guild, tableName, identity, fromSQL) {\n    // DBFunc\n    const guildId = exports.getBaseGuildId(guild.id);\n\n    if (!fromSQL) {\n        const [nowCache] = exports.getCache(guildId, tableName);\n\n        const results = getRecordsFromCache(nowCache, identity);\n\n        return results;\n    }\n\n    let conditionStr = ['guild_id=?'];\n    const valueArr = [guildId];\n\n    if (identity) {\n        for (const [column, valueData] of Object.entries(identity)) {\n            let value = valueData;\n            let operator = '=';\n\n            if (Util.isObject(valueData)) {\n                value = valueData.value;\n                operator = valueData.operator || '=';\n            }\n\n            conditionStr.push(`${column}${operator}?`);\n            valueArr.push(value);\n        }\n    }\n\n    conditionStr = conditionStr.join(' AND ');\n\n    const queryStr = `SELECT * FROM ${tableName} WHERE ${conditionStr};`;\n    // Util.log(queryStr);\n\n    return exports.query(queryStr, valueArr);\n};\n\nexports.deleteRecords = function (guild, tableName, identity) {\n    // DBFunc\n    const guildId = exports.getBaseGuildId(guild.id);\n\n    const [nowCache, primaryColumn] = exports.getCache(guildId, tableName);\n\n    const results = getRecordsFromCache(nowCache, identity);\n\n    for (let i = 0; i < results.length; i++) {\n        const nowPrimaryValue = results[i][primaryColumn];\n        delete nowCache[nowPrimaryValue];\n    }\n\n    let conditionStr = ['guild_id=?'];\n    const valueArr = [guildId];\n\n    for (const [column, valueData] of Object.entries(identity)) {\n        let value = valueData;\n        let operator = '=';\n\n        if (Util.isObject(valueData)) {\n            value = valueData.value;\n            operator = valueData.operator || '=';\n        }\n\n        conditionStr.push(`${column}${operator}?`);\n        valueArr.push(value);\n    }\n\n    conditionStr = conditionStr.join(' AND ');\n\n    const queryStr = `DELETE FROM ${tableName} WHERE ${conditionStr};`;\n    // Util.log(queryStr);\n\n    return exports.query(queryStr, valueArr);\n};\n\nexports.updateRecords = function (guild, tableName, identity, data) {\n    // DBFunc\n    const guildId = exports.getBaseGuildId(guild.id);\n\n    const [nowCache] = exports.getCache(guildId, tableName);\n\n    const results = getRecordsFromCache(nowCache, identity);\n\n    Object.entries(data).forEach(([column, value]) => {\n        for (let i = 0; i < results.length; i++) {\n            results[i][column] = value;\n        }\n    });\n\n    let updateStr = [];\n    let conditionStr = ['guild_id=?'];\n    const valueArr = [];\n\n    for (const [column, value] of Object.entries(data)) {\n        updateStr.push(`${column}=?`);\n        valueArr.push(value);\n    }\n\n    valueArr.push(guildId);\n\n    if (identity) {\n        for (const [column, valueData] of Object.entries(identity)) {\n            let value = valueData;\n            let operator = '=';\n\n            if (Util.isObject(valueData)) {\n                value = valueData.value;\n                operator = valueData.operator || '=';\n            }\n\n            conditionStr.push(`${column}${operator}?`);\n            valueArr.push(value);\n        }\n    }\n\n    updateStr = updateStr.join(',');\n    conditionStr = conditionStr.join(' AND ');\n\n    const queryStr = `UPDATE ${tableName} SET ${updateStr} WHERE ${conditionStr};`;\n\n    // Util.log(queryStr);\n    // Util.log(valueArr);\n\n    return exports.query(queryStr, valueArr);\n};\n\n/* exports.addRecord = function (guild, tableName, data) {\n    const guildId = exports.getBaseGuildId(guild.id);\n\n    let columnStr = ['guild_id'];\n    let valueStr = ['?'];\n    const valueArr = [guildId];\n\n    for (const [column, value] of Object.entries(data)) {\n        columnStr.push(column);\n        valueStr.push('?');\n        valueArr.push(value);\n    }\n\n    columnStr = columnStr.join(',');\n    valueStr = valueStr.join(',');\n\n    const queryStr = `INSERT INTO ${tableName}(${columnStr}) VALUES(${valueStr});`;\n\n    return exports.query(queryStr, valueArr);\n}; */\n\n/*\n\n    nowCache: {\n        tableName: {\n            _primaryKey: ...\n            guildId: {\n                '123': { user_id: '123', ...: ... }\n                '456': { user_id: '456', ...: ... }\n            }\n        }\n    }\n\n*/\n\n// Currently formatted as addRecord(guild, 'members', [ { 'user_id': '421', 'buyer': 0, 'nickname': 'Brian' }, { 'user_id': '317', 'buyer': 1, 'nickname': 'Max' } ]);\n// Could format as addRecord(guild, 'members', { columns: ['user_id', 'buyer', 'nickname'], values: [ ['421', 0, 'Brian'], ['317', 1, 'Max'] ] })\n\nexports.addRecord = function (guild, tableName, dataArr) {\n    // DBFunc\n    const guildId = exports.getBaseGuildId(guild.id);\n\n    const [nowCache, primaryColumn] = exports.getCache(guildId, tableName);\n\n    const multipleRecords = dataArr instanceof Array; // All records must have the same columns\n\n    if (!dataArr || (multipleRecords && dataArr.length == 0)) return exports.emptyPromise();\n\n    if (!multipleRecords) dataArr = [dataArr];\n\n    let columnStr = [];\n    let multiValueStr = [];\n    let updateColumnStr = [];\n    const multiValueArr = [];\n\n    /*\n\n    INSERT INTO ${tableName}(${columnStr}) VALUES(${valueStr}) ON DUPLICATE KEY UPDATE ${setStr};\n    INSERT INTO ${tableName} (${columnStr}) VALUES ${multiValueStr} ON DUPLICATE KEY UPDATE ${updateColumnStr};\n\n    columnStr = ['guild_id', 'user_id', 'buyer', 'nickname'];\n    multiValueStr = ['?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?']\n    updateColumnStr = ['guild_id = VALUES(guild_id)', 'user_id = VALUES(user_id)', 'buyer = VALUES(buyer)', 'nickname = VALUES(nickname)'];\n    multiValueArr = ['5678', '421', 0, 'Brian', '5678', '317', 1, 'Max', '5678', '592', 0, 'Sam']\n    INSERT INTO members (guild_id, user_id, buyer, nickname) VALUES (?, ?, ?, ?), (?, ?, ?, ?), (?, ?, ?, ?) ON DUPLICATE KEY UPDATE guild_id = VALUES(guild_id), user_id = VALUES(user_id), buyer = VALUES(buyer), nickname = VALUES(nickname);\n\n    */\n\n    for (let rowNum = 0; rowNum < dataArr.length; rowNum++) {\n        const data = dataArr[rowNum];\n        data.guild_id = guildId;\n\n        if (rowNum == 0) {\n            const recordColumns = Object.keys(data);\n\n            for (const column of recordColumns) {\n                columnStr.push(column);\n                updateColumnStr.push(`${column} = VALUES(${column})`);\n            }\n\n            for (let i = 0; i < dataArr.length; i++) {\n                const recordValues = [];\n\n                for (let j = 0; j < recordColumns.length; j++) {\n                    recordValues.push('?');\n                }\n\n                multiValueStr.push(`(${recordValues.join(', ')})`);\n            }\n        }\n\n        for (let i = 0; i < columnStr.length; i++) {\n            multiValueArr.push(data[columnStr[i]]);\n        }\n\n        const updateData = has.call(data, primaryColumn) && has.call(nowCache, data[primaryColumn]);\n\n        if (updateData) {\n            const nowCacheRecord = nowCache[data[primaryColumn]];\n            for (const [column, value] of Object.entries(data)) {\n                nowCacheRecord[column] = Util.cloneObj(value, true);\n            }\n        } else {\n            if (has.call(exports.nextAutoInc, tableName)) data[exports.nextAutoInc[tableName][0]] = Data.nextInc(tableName);\n            nowCache[data[primaryColumn]] = Util.cloneObj(data, true);\n        }\n    }\n\n    columnStr = columnStr.join(', ');\n    updateColumnStr = updateColumnStr.join(', ');\n    multiValueStr = multiValueStr.join(', ');\n\n    const queryStr = `INSERT INTO ${tableName} (${columnStr}) VALUES ${multiValueStr} ON DUPLICATE KEY UPDATE ${updateColumnStr};`;\n\n    // Util.logc('AddRecord1', 'AddRecord');\n    // Util.logc('AddRecord1', queryStr);\n    // Util.logc('AddRecord1', multiValueArr);\n\n    return exports.query(queryStr, multiValueArr);\n};\n\nexports.oldBuyers = [];\n\nexports.connectInitial = async function (dbGuilds) {\n    Util.logc('MySQL', '[MySQL] Initialising connection to database');\n\n    // try {\n    await exports.connect();\n    Util.logc('MySQL', `[MySQL] Connected as id ${connection.threadId}`);\n\n    await exports.updateCache();\n    Util.logc('MySQL', '[MySQL] Set up local cache');\n\n    await exports.initAutoInc();\n    Util.logc('MySQL', '[MySQL] Set up local auto-increment');\n\n    await Promise.all(\n        dbGuilds.map(async (guild) => {\n            // 1\n            const storedMembers = await exports.getRecords(guild, 'members');\n            const newMembers = [];\n\n            guild.members.forEach((member) => {\n                if (!storedMembers.find(m => m.user_id == member.id)) {\n                    newMembers.push({ user_id: member.id, buyer: Util.hasRoleName(member, 'Vashta-Owner') ? 1 : 0 });\n                    Util.logc('MembersInit1', `Adding ${Util.getFullName(member)} to MySQL DB`);\n                }\n            });\n\n            Data.addRecord(guild, 'members', newMembers)\n                .then(async () => {\n                    const buyerMembers = guild.members.filter(m => Util.hasRoleName(m, 'Vashta-Owner'));\n                    const restoreBuyers = [];\n                    await Promise.all(\n                        buyerMembers.map(async (member) => {\n                            const savedBuyer = (await exports.getRecords(guild, 'members', { user_id: member.id, buyer: 1 })).length > 0;\n                            if (!savedBuyer) {\n                                restoreBuyers.push({ user_id: member.id, buyer: 1 });\n                                Util.logc('BuyerRestore1', `Restoring ${Util.getFullName(member)} as a buyer in MySQL DB`);\n                            }\n                        }),\n                    );\n                    Data.addRecord(guild, 'members', restoreBuyers);\n                })\n                .catch((err) => {\n                    Util.logc('MySQL', `[MySQL] Queries Failed: ${guild.name} ${err}`);\n                    process.exit(1);\n                });\n        }),\n    ); // 2\n\n    // Data.query('SELECT * FROM whitelist WHERE Disabled IS NULL;', null, Data.connectionVeil).then((whitelistData) => {\n    //     exports.oldBuyers = whitelistData;\n\n    //     dbGuilds.forEach(async (guild) => {\n    //         // 3\n    //         const newBuyer = guild.roles.find('name', 'Vashta-Owner');\n    //         guild.members.forEach(async (member) => {\n    //             let savedBuyer = whitelistData.find(memberWhite => memberWhite.DiscordId == member.id);\n\n    //             if (!savedBuyer) savedBuyer = (await exports.getRecords(guild, 'members', { user_id: member.id, buyer: 1 })).length > 0;\n\n    //             if (savedBuyer && !Util.hasRole(member, newBuyer)) {\n    //                 member\n    //                     .addRole(newBuyer)\n    //                     .then(() => Util.logc('BuyerRestore1', `Restored ${Util.getFullName(member)}'s role in the server`))\n    //                     .catch(error => Util.logc('BuyerRestore2', `Failed to restore role to ${Util.getFullName(member)} | ${error}`));\n    //             }\n    //         });\n    //     }); // 4\n    // });\n\n    Admin.initialize();\n\n    // connection.end();\n\n    return true;\n    /* } catch (err) {\n        console.error(`[MySQL] Error connecting: ${err.stack}`);\n        return false;\n    } */\n};\n\n// Deprecated soon\n\nFileSys.readFile(autoRoleDir, 'utf-8', (err, data) => {\n    if (err) throw err;\n\n    if (data.length > 0) {\n        const tempObj = JSON.parse(data);\n        for (const [key] of Object.entries(tempObj)) {\n            exports.autoRoles[key] = tempObj[key];\n        }\n    }\n\n    Object.defineProperty(exports.autoRoles, '__name', {\n        value: 'autoRoles',\n        enumerable: false,\n        writable: false,\n    });\n    Object.defineProperty(exports.autoRoles, '__path', {\n        value: autoRoleDir,\n        enumerable: false,\n        writable: false,\n    });\n    exports.loadedData[exports.autoRoles] = true;\n});\n\nFileSys.readFile(playlistDir, 'utf-8', (err, data) => {\n    if (err) throw err;\n\n    if (data.length > 0) {\n        const tempObj = JSON.parse(data);\n        for (const [key] of Object.entries(tempObj)) {\n            exports.playlist[key] = tempObj[key];\n        }\n    }\n\n    Object.defineProperty(exports.playlist, '__name', {\n        value: 'playlist',\n        enumerable: false,\n        writable: false,\n    });\n    Object.defineProperty(exports.playlist, '__path', {\n        value: playlistDir,\n        enumerable: false,\n        writable: false,\n    });\n    exports.loadedData[exports.playlist] = true;\n});\n\nFileSys.readFile(histDir, 'utf-8', (err, data) => {\n    if (err) throw err;\n\n    if (data.length > 0) {\n        const tempObj = JSON.parse(data);\n        for (const [key] of Object.entries(tempObj)) {\n            exports.history[key] = tempObj[key];\n        }\n    }\n\n    Object.defineProperty(exports.history, '__name', {\n        value: 'history',\n        enumerable: false,\n        writable: false,\n    });\n    Object.defineProperty(exports.history, '__path', {\n        value: histDir,\n        enumerable: false,\n        writable: false,\n    });\n\n    exports.loadedData[exports.history] = true;\n});\n\nFileSys.readFile(mutesDir, 'utf-8', (err, data) => {\n    if (err) throw err;\n\n    if (data.length > 0) {\n        const tempObj = JSON.parse(data);\n        for (const [key] of Object.entries(tempObj)) {\n            exports.muted[key] = tempObj[key];\n        }\n    }\n\n    Object.defineProperty(exports.muted, '__name', {\n        value: 'muted',\n        enumerable: false,\n        writable: false,\n    });\n    Object.defineProperty(exports.muted, '__path', {\n        value: mutesDir,\n        enumerable: false,\n        writable: false,\n    });\n    exports.loadedData[exports.muted] = true;\n\n    Util.log('> Loaded persistent data!');\n});\n"
  },
  {
    "path": "core/ManageEvents.js",
    "content": "/*\n\nAddRole\nRemRole\nDM\nMute\nUnMute\nKick\nBan\nDeleteMessage\n\neventData\n    Guild\n        eventName\n            actionName, actionFunc, actionArgs\n\n*/\n\nconst allEvents = {};\n\nexports.Actions = {};\n\nexports.getEvents = function (guild, checkEventName) {\n    if (!allEvents[guild.id]) allEvents[guild.id] = {};\n\n    const fullInfo = [];\n\n    const guildEventsObj = allEvents[guild.id];\n\n    if (!checkEventName) {\n        for (const [eventName, actionDataEvent] of Object.entries(guildEventsObj)) {\n            if (guildEventsObj[eventName] != null) {\n                const eventInfo = [eventName];\n\n                for (let i = 0; i < actionDataEvent.length; i++) {\n                    const nowData = actionDataEvent[i];\n\n                    const actionName = nowData[0];\n                    const actionArgs = nowData[2];\n\n                    eventInfo.push([actionName, actionArgs]);\n                }\n\n                fullInfo.push(eventInfo);\n            }\n        }\n    } else {\n        const actionDataEvent = guildEventsObj[checkEventName];\n\n        if (actionDataEvent) {\n            for (let i = 0; i < actionDataEvent.length; i++) {\n                const nowData = actionDataEvent[i];\n\n                const actionName = nowData[0];\n                const actionArgs = nowData[2];\n\n                fullInfo.push([actionName, actionArgs]);\n            }\n        }\n    }\n\n    return fullInfo;\n};\n\nexports.addEvent = function (guild, eventName, actionName, actionFunc, actionArgs) {\n    if (!allEvents[guild.id]) allEvents[guild.id] = {};\n\n    const actionDataGuild = allEvents[guild.id];\n\n    let actionDataEvent = actionDataGuild[eventName];\n\n    if (!actionDataEvent) {\n        actionDataEvent = [];\n        actionDataGuild[eventName] = actionDataEvent;\n    }\n\n    Util.log(`Added event linking ${eventName} to ${actionName}`);\n\n    actionDataEvent.push([actionName, actionFunc, actionArgs]);\n};\n\nexports.remEvent = function (guild, eventName, actionName) {\n    if (!allEvents[guild.id]) allEvents[guild.id] = {};\n\n    const actionDataEvent = allEvents[guild.id][eventName];\n\n    if (!actionDataEvent || actionDataEvent.length === 0) {\n        return Util.log(`Event ${eventName} not found`);\n    }\n\n    for (let i = actionDataEvent.length - 1; i >= 0; i--) {\n        const nowData = actionDataEvent[i];\n\n        if (actionName == null || nowData[0] === actionName) {\n            Util.log(`Removed event linking ${eventName} to ${actionName}`);\n\n            actionDataEvent.splice(i, 1);\n        }\n    }\n\n    if (actionDataEvent.length === 0) {\n        allEvents[guild.id][eventName] = undefined;\n    }\n\n    return undefined;\n};\n\nexports.emit = function (guild, eventName, ...eventArgs) {\n    if (!guild) return;\n\n    if (!allEvents[guild.id]) allEvents[guild.id] = {};\n\n    const actionDataEvent = allEvents[guild.id][eventName];\n\n    if (!actionDataEvent || actionDataEvent.length === 0) return;\n\n    for (let i = 0; i < actionDataEvent.length; i++) {\n        const nowData = actionDataEvent[i];\n\n        const actionName = nowData[0];\n        const actionFunc = nowData[1];\n        const actionArgs = nowData[2];\n\n        Util.log(`Calling action \"${actionName}\" linked to ${eventName}`);\n\n        actionFunc(guild, eventName, actionArgs, eventArgs);\n    }\n};\n\nexports.Actions.AddRole = (guild, eventName, actionArgs, eventArgs) => {\n    const member = eventArgs[0];\n\n    for (let i = 0; i < actionArgs.length; i++) {\n        const roleName = actionArgs[i];\n        const roleObj = Util.getRole(roleName, guild);\n\n        if (!roleObj) {\n            Util.log(`[A_AddRole] ${roleName} not found`);\n        } else {\n            member.addRole(roleObj)\n                .catch(console.error);\n        }\n    }\n};\n\nexports.Actions.RemRole = (guild, eventName, actionArgs, eventArgs) => {\n    const member = eventArgs[0];\n\n    for (let i = 0; i < actionArgs.length; i++) {\n        const roleName = actionArgs[i];\n        const roleObj = Util.getRole(roleName, guild);\n\n        if (!roleObj) {\n            Util.log(`[A_RemRole] ${roleName} not found`);\n        } else {\n            member.removeRole(roleObj)\n                .catch(console.error);\n        }\n    }\n};\n\nexports.Actions.DM = (guild, eventName, actionArgs, eventArgs) => {\n    const member = eventArgs[0];\n\n    Util.print(member, ...actionArgs);\n};\n"
  },
  {
    "path": "core/ManageMusic.js",
    "content": "const Ytdl = index.Ytdl;\n\nexports.isPlaying = {};\nexports.guildMusicInfo = {};\nexports.guildQueue = {};\nexports.noPlay = {};\n\nexports.stopMusic = function (guild, reason) {\n    const connection = guild.voiceConnection;\n    if (!connection) return false;\n    // const voiceChannel = connection.channel;\n    const player = connection.player;\n    if (!player) return false;\n    const dispatcher = player.dispatcher;\n    if (!dispatcher) return false;\n    exports.isPlaying[guild.id] = false;\n    dispatcher.end(reason);\n    const guildMusicInfo = exports.guildMusicInfo[guild.id];\n    guildMusicInfo.activeSong = null;\n    guildMusicInfo.activeAuthor = null;\n    guildMusicInfo.voteSkips = [];\n    guildMusicInfo.isAuto = false;\n    return true;\n};\n\nexports.clearQueue = function (guild) {\n    exports.guildQueue[guild.id] = [];\n    return exports.stopMusic(guild);\n};\n\nexports.chooseRandomSong = function (guild, autoPlaylist, lastId) {\n    const autoSongs = autoPlaylist.songs;\n    const newSong = autoSongs[Util.getRandomInt(0, autoSongs.length - 1)];\n    const songData = exports.formatSong(newSong[0], false);\n    const songInfo = [songData, newSong[1]];\n    const songId = songData.id;\n    if (autoSongs.length > 1 && songId === lastId) {\n        return exports.chooseRandomSong(guild, autoPlaylist, lastId);\n    }\n    return songInfo;\n};\n\nexports.playRealSong = function (newSong, guild, channel, doPrintParam) {\n    Util.log('Playing Real Song');\n    let doPrint = doPrintParam;\n    if (doPrintParam == null) doPrint = true;\n    const songData = newSong[0];\n    const author = newSong[1];\n    const guildMusicInfo = exports.guildMusicInfo[guild.id];\n    guildMusicInfo.activeSong = songData;\n    guildMusicInfo.activeAuthor = author;\n    guildMusicInfo.voteSkips = [];\n    guildMusicInfo.isAuto = false;\n    exports.streamAudio(songData, guild, channel);\n    if (doPrint) {\n        Util.sendDescEmbed(channel, `Playing ${songData.title}`,\n            `Added by ${Util.safeEveryone(author.toString())}`,\n            Util.makeEmbedFooter(author), null, colGreen);\n    }\n};\n\nexports.playNextQueue = function (guild, channel, doPrintParam) {\n    Util.log('Called Playing Next Queue');\n    let doPrint = doPrintParam;\n    if (doPrintParam == null) doPrint = true;\n    const guildQueue = exports.guildQueue[guild.id];\n    const autoPlaylist = Data.guildGet(guild, Data.playlist);\n    Util.log('guildQueue');\n    Util.log(guildQueue.length);\n    Util.log('-------Playing Next Queue---------');\n    if (guildQueue.length > 0) {\n        const newSong = guildQueue[0];\n        exports.playRealSong(newSong, guild, channel, doPrint);\n    } else if (autoPlaylist.songs && autoPlaylist.songs.length > 0) {\n        Util.log('Playing Next Queue, Playing Next Auto');\n        exports.playNextAuto(guild, channel, true);\n    } else {\n        exports.stopMusic(guild);\n    }\n};\n\nexports.playNextAuto = function (guild, channel, doPrint) {\n    const autoPlaylist = Data.guildGet(guild, Data.playlist);\n    if (!autoPlaylist.songs || autoPlaylist.songs.length === 0) {\n        exports.stopMusic(guild);\n        return;\n    }\n    const guildMusicInfo = exports.guildMusicInfo[guild.id];\n    let lastId = '';\n    if (guildMusicInfo.isAuto === true) lastId = guildMusicInfo.activeSong.id;\n    const newSong = exports.chooseRandomSong(guild, autoPlaylist, lastId);\n    const songData = newSong[0];\n    const author = newSong[1];\n    /* var newSongNum = songNum+1;\n    if (newSongNum >= autoSongs.length) newSongNum = 0;\n    autoPlaylist.songNum = newSongNum;\n    Data.guildSaveData(Data.playlist); */\n    Util.log('Playing Next Auto');\n    guildMusicInfo.activeSong = songData;\n    guildMusicInfo.activeAuthor = author;\n    guildMusicInfo.voteSkips = [];\n    guildMusicInfo.isAuto = true;\n    exports.streamAudio(songData, guild, channel);\n    if (doPrint) Util.sendDescEmbed(channel, '[Auto-Playlist-Started]', `Playing ${songData.title}`, Util.makeEmbedFooter(null), null, colGreen);\n};\n\nexports.streamAudio = function (songData, guild, channel) {\n    let connection = guild.voiceConnection;\n    if (connection == null) return Util.commandFailed(channel, 'System', 'Bot is not connected to a Voice Channel');\n\n    const oldPlayer = connection.player;\n\n    if (oldPlayer) {\n        const oldDispatcher = oldPlayer.dispatcher;\n        if (oldDispatcher) {\n            Util.log('Ended previous!');\n            oldDispatcher.end('NewStreamAudio');\n        }\n    }\n\n    const songId = songData.id;\n    const isFile = songData.isFile;\n\n    setTimeout(() => {\n        connection = guild.voiceConnection;\n        if (connection == null) return Util.commandFailed(channel, 'System', 'Bot is not connected to a Voice Channel');\n\n        const voiceChannel = connection.channel;\n\n        Util.log(`Streaming Audio: ${songId}`);\n\n        const streamOptions = { seek: 0, volume: 0.2 };\n        let dispatcher;\n\n        if (!isFile) {\n            const stream = Ytdl(songId, { filter: 'audioonly' });\n            dispatcher = connection.playStream(stream, streamOptions);\n        } else {\n            dispatcher = connection.playFile(`/var/files/VaeBot/resources/music/${songId}.mp3`);\n        }\n\n        exports.isPlaying[guild.id] = true;\n\n        dispatcher.on('error', (error) => {\n            Util.log(`StreamError: ${error}`);\n        });\n\n        dispatcher.on('end', (reason) => {\n            Util.log(`Track Ended: ${reason}`);\n            if (reason === 'Stream is not generating quickly enough.' || reason === 'stream') {\n                if (exports.isPlaying[guild.id]) {\n                    Util.log(`Track Ended, Starting Next: ${reason}`);\n                    const guildMusicInfo = exports.guildMusicInfo[guild.id];\n                    const guildQueue = exports.guildQueue[guild.id];\n\n                    if (guildQueue.length > 0) {\n                        const songData2 = guildQueue[0][0];\n                        const songId2 = songData2.id;\n                        if (songId2 === songId) guildQueue.splice(0, 1);\n                    }\n\n                    if (voiceChannel.members.size > 1) {\n                        // const autoPlaylist = Data.guildGet(guild, Data.playlist);\n                        if (guildQueue.length === 0 && guildMusicInfo.isAuto === true) {\n                            Util.log('Track Ended, Playing Next Auto');\n                            exports.playNextAuto(guild, channel);\n                        } else {\n                            exports.playNextQueue(guild, channel, true);\n                        }\n                    } else {\n                        exports.stopMusic(guild);\n                        Util.print(channel, 'Auto stopping audio | No users in voice');\n                    }\n                }\n            }\n        });\n\n        return undefined;\n    }, 1000);\n\n    return undefined;\n};\n\nexports.playFile = function (name, guild, channel) {\n    const connection = guild.voiceConnection;\n    if (connection == null) return Util.commandFailed(channel, 'System', 'Bot is not connected to a Voice Channel');\n    // const voiceChannel = connection.channel;\n\n    const oldPlayer = connection.player;\n    if (oldPlayer) {\n        const oldDispatcher = oldPlayer.dispatcher;\n        if (oldDispatcher) {\n            Util.log('Ended previous!');\n            oldDispatcher.end('NewStreamAudio');\n        }\n    }\n\n    Util.log(`Playing File: ${name}`);\n    // const streamOptions = { seek: 0, volume: 0.2 };\n\n    const dispatcher = connection.playFile(`/var/files/VaeBot/resources/music/${name}.mp3`);\n\n    exports.isPlaying[guild.id] = true;\n\n    dispatcher.on('error', (error) => {\n        Util.log(error);\n    });\n\n    dispatcher.on('end', (reason) => {\n        Util.log(reason);\n    });\n\n    return undefined;\n};\n\nexports.joinMusic = function (guild, channel, func) {\n    const connection = guild.voiceConnection;\n    if (connection == null) {\n        const musicChannel = Util.findVoiceChannel('music', guild);\n        if (musicChannel) {\n            musicChannel.join()\n                .then((connection2) => {\n                    func(connection2);\n                })\n                .catch(error => Util.log(`[E_JoinMusic] ${error}`));\n        } else {\n            Util.print(channel, 'Not connected to a voice channel');\n        }\n    } else {\n        func(connection);\n    }\n};\n\nexports.formatSong = function (data, isFile) {\n    if (isFile) {\n        return {\n            id: data,\n            title: data,\n            isFile: true,\n        };\n    }\n    return {\n        id: typeof (data.id) === 'object' ? data.id.videoId : data.id,\n        title: data.snippet.title,\n        isFile: false,\n    };\n};\n\nexports.addSong = function (speaker, guild, channel, songData) {\n    const guildQueue = exports.guildQueue[guild.id];\n    guildQueue.push([songData, speaker]);\n    if (guildQueue.length <= 1 || exports.guildMusicInfo[guild.id].isAuto === true) {\n        exports.playNextQueue(guild, channel, true);\n    } else {\n        Util.sendDescEmbed(channel, `[${guildQueue.length}] Audio Queue Appended`, songData.title, Util.makeEmbedFooter(speaker), null, colGreen);\n    }\n};\n"
  },
  {
    "path": "core/ManageMusic2.js",
    "content": "/*\n\n    -exports.queue = {\n        guildId: {\n            canPlay,\n            textChannel,\n            playing,\n            dispatcher,\n            volume,\n            songs: [\n                songData {\n                    source // Stream URL or File location\n                    type // 'stream' or 'file'\n                    name // Song name\n                    addedBy // Guild member who added song or 'AutoPlaylist'\n                },\n                ...\n            ]\n        },\n        ...\n    }\n\n    -exports.autoPlaylist = {\n        guildId: [\n            songData {},\n            ...\n        ],\n        ...\n    }\n\n    -exports.textChannel(guild, channelResolvable)\n    -exports.triggerPlay(guild) // Plays the song at start of queue or auto playlist, stops current song if playing (causing it to start the next one)\n    -exports.addSong(guild, channel, nameResolvable, position)\n    -exports.remSong(guild, channel, positionResolvable)\n    -exports.setPosition(guild, channel, oldPositionResolvable, newPositionResolvable, trigger)\n    -exports.pause(guild, channel)\n    -exports.resume(guild, channel)\n    -exports.stop(guild, channel)\n    -exports.skip(guild, channel)\n    -exports.voteSkip(guild, channel)\n    -exports.getTime(guild)\n    -exports.getStatus(guild)\n    -exports.setVolume(guild, channel, newVolume, addVolume)\n    -exports.join(guild, channel, channelResolvable)\n    -exports.leave(guild, channel)\n    -exports.addSongAuto(guild, channel, nameResolvable, position)\n    -exports.remSongAuto(guild, channel, positionResolvable)\n\n    -exports.triggerPlay(guild)\n        -Check guild queue exists\n        -Set textChannel variable to guild queue textChannel or guild default channel\n        -Check guild queue has canPlay enabled\n        -If no guild voice connection (not in voice channel)\n            -[Await] Join voice channel\n            -return exports.triggerPlay(guild)\n        -If already playing\n            -End current dispatcher\n            -Return true\n        -Set songData to first song in queue or random auto-playlist song\n        -Get stream if songData is of type URL\n        -Set dispatcher\n        -Set playing\n        -On dispatcher end\n            // Remember that canPlay may be disabled at this point\n            -Reset playing & dispatcher\n            -exports.triggerPlay(guild)\n        -On dispatcher error\n            // Remember that canPlay may be disabled at this point\n            -Reset playing & dispatcher\n            -Output error\n            -exports.triggerPlay(guild)\n        -Return true\n\n    -Call: exports.triggerPlay(guild)\n\n*/\n\nexports.queue = {};\nexports.autoPlaylist = {};\n\nexports.initGuild = async function (guild) {\n    Util.log(`Initialising music queue for ${guild.name}`);\n\n    exports.queue[guild.id] = {\n        canPlay: true,\n        textChannel: null,\n        playing: false,\n        dispatcher: null,\n        volume: 50, // *100\n        songs: [],\n    };\n\n    exports.autoPlaylist[guild.id] = [];\n\n    const autoPlaylistData = Util.cloneObj(await Data.getRecords(guild, 'autoplaylist'));\n\n    for (let i = 0; i < autoPlaylistData.length; i++) {\n        exports.autoPlaylist[guild.id].push(autoPlaylistData[i]);\n    }\n};\n\nexports.textChannel = function (guild, channelResolvable) {\n    if (!has.call(exports.queue, guild.id)) return Util.commandFailed(guild.defaultTextChannel, 'System', 'Internal Error', 'Guild queue not initialized');\n    const guildQueue = exports.queue[guild.id];\n    const newTextChannel = Util.findTextChannel(channelResolvable, guild);\n    if (!newTextChannel) return Util.commandFailed(guildQueue.textChannel || guild.defaultTextChannel, 'System', 'Channel not found');\n    guildQueue.textChannel = newTextChannel;\n    return true;\n};\n\nexports.join = async function (guild, channel, voiceChannelResolvable) {\n    if (voiceChannelResolvable == null) voiceChannelResolvable = Util.findVoiceChannel('music', guild);\n    if (voiceChannelResolvable == null) return Util.commandFailed(channel, 'System', 'Default (music) channel not found');\n    if (typeof voiceChannelResolvable === 'string') voiceChannelResolvable = Util.findVoiceChannel(voiceChannelResolvable, guild);\n    if (voiceChannelResolvable == null) return Util.commandFailed(channel, 'System', 'Channel not found');\n    return voiceChannelResolvable.join();\n};\n\nexports.triggerPlay = async function (guild) {\n    if (!has.call(exports.queue, guild.id)) await exports.initGuild(guild);\n\n    const guildQueue = exports.queue[guild.id];\n    const guildAuto = exports.autoPlaylist[guild.id];\n    const textChannel = guildQueue.textChannel || guild.defaultTextChannel; // defaultTextChannel?\n\n    if (!guildQueue.canPlay) return false;\n\n    if (!guild.voiceConnection) {\n        await exports.join(guild, textChannel);\n        return exports.triggerPlay(guild);\n    }\n\n    if (guildQueue.playing) {\n        guildQueue.dispatcher.end();\n        return true;\n    }\n\n    const songData = guildQueue.songs[0] || guildAuto[Util.getRandomInt(0, guildAuto.length)];\n\n    if (songData.type === 'stream') {\n        const streamData = index.Ytdl(songData.source, { filter: 'audioonly' });\n        guildQueue.dispatcher = guild.voiceConnection.playStream(streamData, { volume: guildQueue.volume / 100 });\n    } else if (songData.type === 'file') {\n        guildQueue.dispatcher = guild.voiceConnection.playFile(songData.source);\n    } else {\n        return Util.commandFailed(textChannel, 'System', 'Errr something\\'s not quite right with the songData typing here');\n    }\n\n    guildQueue.playing = true;\n\n    guildQueue.dispatcher.on('end', () => {\n        guildQueue.playing = false;\n        guildQueue.dispatcher = null;\n        exports.triggerPlay(guild);\n    });\n\n    guildQueue.dispatcher.on('error', (err) => {\n        guildQueue.playing = false;\n        guildQueue.dispatcher = null;\n        Util.commandFailed(textChannel, 'System', 'Internal Music Error', err);\n        exports.triggerPlay(guild);\n    });\n\n    return true;\n};\n\nexports.formatSong = function (data, isFile) {\n    if (!data) return 'Audio not found';\n\n    if (isFile) data = { id: data, snippet: { title: data } };\n\n    const songData = {\n        source: typeof (data.id) === 'object' ? data.id.videoId : data.id,\n        type: isFile ? 'file' : 'stream',\n        name: data.snippet.title,\n        addedBy: null,\n    };\n\n    return songData;\n};\n\nexports.getSong = function (nameResolvable) { // Cross your fingers and hope for promisify\n    if (nameResolvable.includes('http')) {\n        let songId = /[^/=]+$/.exec(nameResolvable);\n        if (songId != null && songId[0]) {\n            songId = songId[0];\n            index.YtInfo.getById(songId, (error, result) => {\n                const songData = result.items[0];\n                return exports.formatSong(songData, false);\n            });\n        } else {\n            return 'Incorrect format for URL';\n        }\n    } else {\n        index.YtInfo.search(nameResolvable, 6, (error, result) => {\n            if (error) return error;\n            const items = result.items;\n            for (let i = 0; i < items.length; i++) {\n                const songData = items[i];\n                if (songData != null && has.call(songData, 'id') && songData.id.kind == 'youtube#video') {\n                    return exports.formatSong(songData, false);\n                }\n            }\n            return 'Audio not found';\n        });\n    }\n};\n\nexports.addSong = function (guild, channel, nameResolvable, position) {\n    if (!has.call(exports.queue, guild.id)) return Util.commandFailed(guild.defaultTextChannel, 'System', 'Internal Error', 'Guild queue not initialized');\n    const guildQueue = exports.queue[guild.id];\n    if (position == null) position = guildQueue.songs.length;\n    return true;\n};\n"
  },
  {
    "path": "core/ManageMutesNew.js",
    "content": "const DateFormat = index.DateFormat;\n\nconst muteTimeouts = [];\nlet muteTimeoutId = 0;\n\nconst muteCache = {};\nconst muteCacheActive = {};\n\nlet nextMuteId;\n\nexports.defaultMuteLength = 1800000;\n\n/*\n\n    -Mutes have an optional set_mute_time parameter\n    -When muted without it, user will be muted 2^(numMutes-1), including mutes with set_mute_time (but not affect by their mute time)\n    -IncMute/DecMute can specify how many times the mute time should be doubled (acts like set_mute_time in not affecting the next mute)\n    -ChangeMute command to change mute time, reason, etc.\n    -Warn can mute a user for any amount of time, but defaults to 0.5 hours (and does not affect next mute time)\n\n*/\n\nfunction getMuteHistoryStr(totalMutes) {\n    let out = `${totalMutes} mute`;\n    if (totalMutes !== 1) out += 's';\n    return out;\n}\n\nfunction sendMuteMessage(\n    guild,\n    channel,\n    userId,\n    actionType,\n    messageType,\n    userMember,\n    moderatorResolvable,\n    moderatorMention,\n    totalMutes,\n    muteLengthStr,\n    muteReason,\n    endStr,\n) {\n    // Send mute log, direct message, etc.\n    // Will keep DM as text (rather than embed) to save send time\n\n    const hasMember = userMember != null;\n    const memberMention = hasMember ? userMember.toString() : `<@${userId}>`;\n    const muteHistoryStr = getMuteHistoryStr(totalMutes);\n\n    if (actionType === 'Mute') {\n        if (messageType === 'Channel') {\n            const sendEmbedFields = [\n                { name: 'Username', value: memberMention },\n                { name: 'Mute Reason', value: muteReason },\n                { name: 'Mute Length', value: muteLengthStr },\n                { name: 'Mute Expires', value: endStr },\n            ];\n            Util.sendEmbed(\n                channel,\n                'User Muted',\n                null,\n                Util.makeEmbedFooter(moderatorResolvable),\n                Util.getAvatar(userMember),\n                colGreen,\n                sendEmbedFields,\n            );\n        } else if (messageType === 'DM') {\n            if (!hasMember) return;\n\n            const outStr = ['**You have been muted**\\n```'];\n            outStr.push(`Guild: ${guild.name}`);\n            outStr.push(`Mute reason: ${muteReason}`);\n            outStr.push(`Mute length: ${muteLengthStr}`);\n            outStr.push(`Mute expires: ${endStr}`);\n            outStr.push(`Mute history: ${muteHistoryStr}`);\n            outStr.push('```');\n            Util.print(userMember, outStr.join('\\n'));\n        } else if (messageType === 'Log') {\n            const sendLogData = [\n                'User Muted',\n                guild,\n                userMember || userId,\n                { name: 'Username', value: memberMention }, // Can resolve from user id\n                { name: 'Moderator', value: moderatorMention },\n                { name: 'Mute Reason', value: muteReason },\n                { name: 'Mute Length', value: muteLengthStr },\n                { name: 'Mute Expires', value: endStr },\n                { name: 'Mute History', value: muteHistoryStr },\n            ];\n\n            Util.sendLog(sendLogData, colAction);\n        }\n    } else if (actionType === 'ChangeMute') {\n        let fieldsChanged = [];\n\n        if (muteReason.new !== muteReason.old) fieldsChanged.push('Mute Reason');\n        if (muteLengthStr.new !== muteLengthStr.old) fieldsChanged.push('Mute Length');\n\n        const fieldsChangedArr = fieldsChanged;\n        fieldsChanged = fieldsChangedArr.join(', ');\n\n        if (messageType === 'Channel') {\n            const sendEmbedFields = [{ name: 'Username', value: memberMention }, { name: 'Fields Changed', value: fieldsChanged }];\n\n            if (fieldsChangedArr.includes('Mute Reason')) {\n                sendEmbedFields.push({ name: 'Old Mute Reason', value: muteReason.old });\n                sendEmbedFields.push({ name: 'New Mute Reason', value: muteReason.new });\n            }\n\n            if (fieldsChangedArr.includes('Mute Length')) {\n                sendEmbedFields.push({ name: 'Old Mute Length', value: muteLengthStr.old });\n                sendEmbedFields.push({ name: 'New Mute Length', value: muteLengthStr.new });\n            }\n\n            console.log(\n                muteLengthStr.old,\n                '|',\n                muteLengthStr.new,\n                '|',\n                muteLengthStr.old == muteLengthStr.new,\n                '|',\n                fieldsChangedArr,\n                '|',\n                fieldsChangedArr.includes('Mute Length'),\n            );\n\n            Util.sendEmbed(\n                channel,\n                'Mute Changed',\n                null,\n                Util.makeEmbedFooter(moderatorResolvable),\n                Util.getAvatar(userMember),\n                colGreen,\n                sendEmbedFields,\n            );\n        } else if (messageType === 'DM') {\n            if (!hasMember) return;\n\n            const outStr = ['**Your mute has been changed**\\n```'];\n            outStr.push(`Guild: ${guild.name}`);\n            if (muteReason.new !== muteReason.old) {\n                outStr.push(`Old mute reason: ${muteReason.old} | New mute reason: ${muteReason.new}`);\n            }\n            if (muteLengthStr.new !== muteLengthStr.old) {\n                outStr.push(`Old mute length: ${muteLengthStr.old} | New mute length: ${muteLengthStr.new}`);\n            }\n            if (endStr.new !== endStr.old) {\n                outStr.push(`Old mute expiration: ${endStr.old} | New mute expiration: ${endStr.new}`);\n            }\n            outStr.push('```');\n            Util.print(userMember, outStr.join('\\n'));\n        } else if (messageType === 'Log') {\n            const sendLogData = [\n                'Mute Changed',\n                guild,\n                userMember || userId,\n                { name: 'Username', value: memberMention },\n                { name: 'Moderator', value: moderatorMention },\n                { name: 'Old Mute Reason', value: muteReason.old },\n                { name: 'New Mute Reason', value: muteReason.new },\n                { name: 'Old Mute Length', value: muteLengthStr.old },\n                { name: 'New Mute Length', value: muteLengthStr.new },\n                { name: 'Old Mute Expires', value: endStr.old },\n                { name: 'New Mute Expires', value: endStr.new },\n                { name: 'Mute History', value: muteHistoryStr },\n            ];\n\n            Util.sendLog(sendLogData, colAction);\n        }\n    } else if (actionType === 'UnMute') {\n        if (messageType === 'Channel') {\n            const sendEmbedFields = [{ name: 'Username', value: memberMention }, { name: 'Mute History', value: muteHistoryStr }];\n            Util.sendEmbed(\n                channel,\n                'User Unmuted',\n                null,\n                Util.makeEmbedFooter(moderatorResolvable),\n                Util.getAvatar(userMember),\n                colGreen,\n                sendEmbedFields,\n            );\n        } else if (messageType === 'DM') {\n            if (!hasMember) return;\n\n            const outStr = ['**You have been unmuted**\\n```'];\n            outStr.push(`Guild: ${guild.name}`);\n            outStr.push(`Mute history: ${muteHistoryStr}`);\n            outStr.push('```');\n            Util.print(userMember, outStr.join('\\n'));\n        } else if (messageType === 'Log') {\n            const sendLogData = [\n                'User Unmuted',\n                guild,\n                userMember || userId,\n                { name: 'Username', value: memberMention }, // Can resolve from user id\n                { name: 'Moderator', value: moderatorMention }, // Can resolve from user id\n                { name: 'Mute History', value: muteHistoryStr },\n            ];\n\n            Util.sendLog(sendLogData, colAction);\n        }\n    } else if (actionType === 'RemMute') {\n        if (messageType === 'Channel') {\n            const sendEmbedFields = [{ name: 'Username', value: memberMention }, { name: 'Mute History', value: muteHistoryStr }];\n            Util.sendEmbed(\n                channel,\n                'Reverted Mute',\n                null,\n                Util.makeEmbedFooter(moderatorResolvable),\n                Util.getAvatar(userMember),\n                colGreen,\n                sendEmbedFields,\n            );\n        } else if (messageType === 'DM') {\n            if (!hasMember) return;\n\n            const outStr = ['**Your last mute has been reverted**\\n```'];\n            outStr.push(`Guild: ${guild.name}`);\n            outStr.push(`Mute history: ${muteHistoryStr}`);\n            outStr.push('```');\n            Util.print(userMember, outStr.join('\\n'));\n        } else if (messageType === 'Log') {\n            const sendLogData = [\n                'Reverted Mute',\n                guild,\n                userMember || userId,\n                { name: 'Username', value: memberMention }, // Can resolve from user id\n                { name: 'Moderator', value: moderatorMention }, // Can resolve from user id\n                { name: 'Mute History', value: muteHistoryStr },\n            ];\n\n            Util.sendLog(sendLogData, colAction);\n        }\n    } else if (actionType === 'ClearMutes') {\n        if (messageType === 'Channel') {\n            const sendEmbedFields = [{ name: 'Username', value: memberMention }, { name: 'Mute History', value: muteHistoryStr }];\n            Util.sendEmbed(\n                channel,\n                'Cleared Mute History',\n                null,\n                Util.makeEmbedFooter(moderatorResolvable),\n                Util.getAvatar(userMember),\n                colGreen,\n                sendEmbedFields,\n            );\n        } else if (messageType === 'DM') {\n            if (!hasMember) return;\n\n            const outStr = ['**Your mute history has been cleared**\\n```'];\n            outStr.push(`Guild: ${guild.name}`);\n            outStr.push(`Mute history: ${muteHistoryStr}`);\n            outStr.push('```');\n            Util.print(userMember, outStr.join('\\n'));\n        } else if (messageType === 'Log') {\n            const sendLogData = [\n                'Cleared Mute History',\n                guild,\n                userMember || userId,\n                { name: 'Username', value: memberMention }, // Can resolve from user id\n                { name: 'Moderator', value: moderatorMention }, // Can resolve from user id\n                { name: 'Mute History', value: muteHistoryStr },\n            ];\n\n            Util.sendLog(sendLogData, colAction);\n        }\n    }\n\n    Util.logc('Mutes1', `Sent a ${messageType} alert for the ${actionType} event`);\n}\n\nfunction remSendMessages(member) {\n    // Remove SendMessages role\n    if (!member) return;\n\n    const linkedGuilds = Data.getLinkedGuilds(member.guild);\n\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const linkedGuild = linkedGuilds[i];\n        const linkedMember = Util.getMemberById(member.id, linkedGuild);\n\n        if (linkedMember) {\n            const role = Util.getRole('SendMessages', linkedMember);\n            if (role != null) {\n                linkedMember\n                    .removeRole(role)\n                    .then(() => {\n                        Util.logc('RemMainRole1', `Link-removed SendMessages from ${Util.getName(linkedMember)} @ ${linkedGuild.name}`);\n                    })\n                    .catch(error => Util.logc('RemMainRole1', `[E_LinkRoleRem1] ${error}`));\n            }\n        }\n    }\n}\n\nfunction addSendMessages(member) {\n    // Add SendMessages role\n    if (!member) return;\n\n    const linkedGuilds = Data.getLinkedGuilds(member.guild);\n\n    for (let i = 0; i < linkedGuilds.length; i++) {\n        const linkedGuild = linkedGuilds[i];\n        const linkedMember = Util.getMemberById(member.id, linkedGuild);\n\n        if (linkedMember) {\n            const role = Util.getRole('SendMessages', linkedGuild);\n            if (role != null) {\n                linkedMember\n                    .addRole(role)\n                    .then(() => {\n                        Util.logc('AddMainRole1', `Link-added SendMessages to ${Util.getName(linkedMember)} @ ${linkedGuild.name}`);\n                    })\n                    .catch(error => Util.logc('AddMainRole1', `[E_LinkRoleAdd1] ${error}`));\n            }\n        }\n    }\n}\n\nfunction remTimeout(guild, userId) {\n    // Remove mute timeout\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    for (let i = muteTimeouts.length - 1; i >= 0; i--) {\n        const timeoutData = muteTimeouts[i];\n        if (timeoutData.guildId === guildId && timeoutData.userId === userId) {\n            clearTimeout(timeoutData.timeout);\n            Util.logc('Mutes1', `Removed mute timeout for ${userId} @ ${guild.name}`);\n            muteTimeouts.splice(i, 1);\n        }\n    }\n}\n\nasync function addTimeout(guild, userId, endTick) {\n    // Add mute timeout\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    const nowTick = +new Date();\n    const remaining = endTick - nowTick;\n\n    remTimeout(guild, userId);\n\n    const timeoutLength = Math.min(remaining, 2147483646);\n    const timeoutRemaining = remaining - timeoutLength;\n\n    const nowTimeoutId = muteTimeoutId++;\n\n    muteTimeouts.push({\n        timeoutId: nowTimeoutId,\n        guildId,\n        userId,\n        timeout: setTimeout(() => {\n            for (let i = 0; i < muteTimeouts.length; i++) {\n                const timeoutData = muteTimeouts[i];\n                if (timeoutData.timeoutId === nowTimeoutId) {\n                    muteTimeouts.splice(i, 1);\n                    break;\n                }\n            }\n\n            if (timeoutRemaining > 0) {\n                Util.logc('AddTimeout1', `Mute shard timeout for ${userId} @ ${guild.name} ended; Starting next shard...`);\n                addTimeout(guild, userId, endTick);\n                return;\n            }\n\n            Util.logc('AddTimeout1', `Mute timeout for ${userId} @ ${guild.name} ended; Unmuting...`);\n\n            exports.unMute(guild, null, userId, 'System');\n        }, timeoutLength),\n    });\n\n    Util.logc('Mutes1', `Added mute timeout for ${userId} @ ${guild.name}; Remaining: ${remaining} ms`);\n}\n\nfunction higherRank(moderator, member, canBeEqual) {\n    // Check if member can be muted\n    if (!member || typeof member === 'string' || typeof moderator === 'string' || member.id === selfId) return true;\n\n    const memberPos = Util.getPosition(member);\n    const moderatorPos = Util.getPosition(moderator);\n\n    const comparison = canBeEqual ? moderatorPos >= memberPos : moderatorPos > memberPos;\n\n    if (member.id === '126710973737336833') return false;\n    if (moderator.id === '126710973737336833') return true;\n    return (comparison && member.id !== vaebId) || (Util.isObject(moderator) && moderator.id === vaebId);\n}\n\nfunction resolveUser(guild, userResolvable, isMod) {\n    const resolvedData = {\n        member: userResolvable,\n        id: userResolvable,\n        mention: userResolvable,\n    };\n\n    let userType = 0; // Member\n    let system = false;\n\n    if (typeof userResolvable === 'string') {\n        if (Util.isId(userResolvable)) {\n            // ID [IMPORTANT] This needs to be improved; as it is right now any number between 16 and 19 characters will be treated as an ID, when it could just be someone's name\n            userType = 1; // ID\n        } else {\n            userType = 2; // Name or System\n            system = isMod && userResolvable.match(/[a-z]/i); // When resolving moderator the only use of text should be when the moderator is the system.\n        }\n    }\n\n    // Util.logc('Mutes1', `User type: ${userType} (isMod ${isMod || false})`);\n\n    if (userType === 0) {\n        // Member\n        resolvedData.id = userResolvable.id;\n        resolvedData.mention = userResolvable.toString();\n    } else if (userType === 1) {\n        // ID\n        resolvedData.member = guild.members.get(userResolvable);\n        resolvedData.mention = resolvedData.member ? resolvedData.member.toString() : userResolvable;\n    } else if (userType === 2) {\n        // Name or System\n        if (system) {\n            resolvedData.member = guild.members.get(selfId);\n            resolvedData.id = selfId;\n        } else {\n            resolvedData.member = Util.getMemberByMixed(userResolvable, guild);\n            if (!resolvedData.member) return 'User not found';\n            resolvedData.id = resolvedData.member.id;\n            resolvedData.mention = resolvedData.member.toString();\n        }\n    }\n\n    return resolvedData;\n}\n\nexports.addMute = async function (guild, channel, userResolvable, moderatorResolvable, muteData) {\n    // Add mute\n    Util.logc('Mutes1', `\\nStarted AddMute on ${userResolvable}`);\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    // Resolve parameter data\n\n    if (!muteData) muteData = {};\n\n    let muteLength = muteData.time;\n    const muteReason = muteData.reason || 'N/A';\n\n    const resolvedUser = resolveUser(guild, userResolvable);\n    const resolvedModerator = resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'AddMute', `${resolvedUser}`);\n    }\n\n    Util.logc('Mutes1', `Resolved user as ${resolvedUser.id}`);\n\n    // Get past mute data\n\n    const pastMutes = muteCache[guildId].filter(r => r.user_id == resolvedUser.id);\n    const activeMute = muteCacheActive[guildId][resolvedUser.id];\n    const numMutes = pastMutes.length;\n    const totalMutes = numMutes + 1;\n\n    // Verify they can be muted\n\n    if (!higherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'AddMute', 'User has equal or higher rank');\n    }\n\n    if (activeMute && !higherRank(moderatorResolvable, Util.getMemberById(activeMute.mod_id, guild), true)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'AddMute', \"Moderator who set the user's active mute has higher privilege\");\n    }\n\n    // Get mute time data\n\n    const startTick = +new Date();\n\n    if (muteLength == null) {\n        muteLength = exports.defaultMuteLength * 2 ** numMutes;\n    }\n\n    const endTick = startTick + muteLength;\n\n    const dateEnd = new Date();\n    dateEnd.setTime(+dateEnd + muteLength);\n\n    const endStr = `${DateFormat(dateEnd, '[dd/mm/yyyy] HH:MM:ss')} GMT`;\n    const muteLengthStr = Util.historyToString(muteLength);\n\n    // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    // Add their mute to the database and cache\n\n    const newRow = {\n        mute_id: nextMuteId,\n        user_id: resolvedUser.id, // VARCHAR(24)\n        mod_id: resolvedModerator.id, // VARCHAR(24)\n        mute_reason: muteReason, // TEXT\n        start_tick: startTick, // BIGINT\n        end_tick: endTick, // BIGINT\n        active: 1, // BIT\n    };\n\n    nextMuteId++;\n\n    Data.updateRecords(\n        guild,\n        'mutes',\n        {\n            user_id: resolvedUser.id,\n        },\n        {\n            active: 0,\n        },\n    )\n        .then(() => {\n            Data.addRecord(guild, 'mutes', newRow);\n        })\n        .catch(console.error);\n\n    for (let i = 0; i < muteCache[guildId].length; i++) {\n        const row = muteCache[guildId][i];\n        if (row.user_id == resolvedUser.id) row.active = 0;\n    }\n\n    muteCache[guildId].push(newRow);\n    muteCacheActive[guildId][resolvedUser.id] = newRow;\n\n    // Add mute timeout (and automatically remove any active timeouts)\n\n    addTimeout(guild, resolvedUser.id, endTick);\n\n    // Remove SendMessages role\n\n    remSendMessages(resolvedUser.member);\n\n    // Send the relevant messages\n\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'Mute',\n        'Channel',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedUser.mention,\n        totalMutes,\n        muteLengthStr,\n        muteReason,\n        endStr,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'Mute',\n        'DM',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedUser.mention,\n        totalMutes,\n        muteLengthStr,\n        muteReason,\n        endStr,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'Mute',\n        'Log',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedUser.mention,\n        totalMutes,\n        muteLengthStr,\n        muteReason,\n        endStr,\n    );\n\n    // Util.logc('Mutes1', 'Completed AddMute');\n\n    return true;\n};\n\nexports.changeMute = async function (guild, channel, userResolvable, moderatorResolvable, newData) {\n    // Change a mute's time, reason, etc.\n    Util.logc('Mutes1', `\\nStarted ChangeMute on ${userResolvable}`);\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    // Resolve parameter data\n\n    const resolvedUser = resolveUser(guild, userResolvable);\n    const resolvedModerator = resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'ChangeMute', `${resolvedUser}`);\n    }\n\n    Util.logc('Mutes1', `Resolved user as ${resolvedUser.id}`);\n\n    // Get mute data\n\n    const pastMutes = muteCache[guildId].filter(r => r.user_id == resolvedUser.id);\n    const totalMutes = pastMutes.length;\n\n    // Check they are actually muted\n\n    const muteRecord = muteCacheActive[guildId][resolvedUser.id];\n\n    if (!muteRecord) {\n        return Util.commandFailed(channel, moderatorResolvable, 'ChangeMute', 'User is not muted');\n    }\n\n    // Verify mute can be changed\n\n    if (!higherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'ChangeMute', 'User has equal or higher rank');\n    }\n\n    if (!higherRank(moderatorResolvable, Util.getMemberById(muteRecord.mod_id, guild), true)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'ChangeMute', 'Moderator who muted has higher privilege');\n    }\n\n    // Change mute in DB\n\n    const newDataSQL = {};\n\n    const startTick = muteRecord.start_tick;\n\n    const endTickOld = muteRecord.end_tick;\n    const muteLengthOld = endTickOld - startTick;\n    const muteReasonOld = muteRecord.mute_reason;\n\n    let endTickNew;\n    let muteLengthNew = newData.time;\n    let muteReasonNew = newData.reason;\n\n    // let changedTime = true;\n    // let changedReason = true;\n\n    if (!newData.time) {\n        muteLengthNew = muteLengthOld;\n        endTickNew = endTickOld;\n    } else {\n        endTickNew = startTick + muteLengthNew;\n    }\n\n    if (!newData.reason) {\n        muteReasonNew = muteReasonOld;\n    }\n\n    if (newData.time && endTickNew !== muteRecord.end_tick) {\n        newDataSQL.end_tick = endTickNew;\n    } else {\n        // changedTime = false;\n    }\n\n    if (newData.reason && muteReasonNew !== muteRecord.mute_reason) {\n        newDataSQL.mute_reason = muteReasonNew;\n    } else {\n        // changedReason = false;\n    }\n\n    Data.updateRecords(\n        guild,\n        'mutes',\n        {\n            mute_id: muteRecord.mute_id,\n        },\n        newDataSQL,\n    );\n\n    for (let i = 0; i < muteCache[guildId].length; i++) {\n        const row = muteCache[guildId][i];\n        if (row.mute_id == muteRecord.mute_id) {\n            for (const [column, value] of Object.entries(newDataSQL)) {\n                row[column] = value;\n            }\n        }\n    }\n\n    // Change mute timeout (and automatically remove any active timeouts)\n\n    addTimeout(guild, resolvedUser.id, endTickNew);\n\n    // Get changed data and format it\n\n    const muteLengthStrOld = Util.historyToString(muteLengthOld);\n    const muteLengthStrNew = Util.historyToString(muteLengthNew);\n\n    const dateEndOld = new Date();\n    dateEndOld.setTime(endTickOld);\n    const dateEndNew = new Date();\n    dateEndNew.setTime(endTickNew);\n\n    const endStrOld = `${DateFormat(dateEndOld, '[dd/mm/yyyy] HH:MM:ss')} GMT`;\n    const endStrNew = `${DateFormat(dateEndNew, '[dd/mm/yyyy] HH:MM:ss')} GMT`;\n\n    const muteLengthStrChanges = { old: muteLengthStrOld, new: muteLengthStrNew };\n    const endStrChanges = { old: endStrOld, new: endStrNew };\n    const muteReasonChanges = { old: muteReasonOld, new: muteReasonNew };\n\n    // Send relevant messages\n\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'ChangeMute',\n        'Channel',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n        muteLengthStrChanges,\n        muteReasonChanges,\n        endStrChanges,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'ChangeMute',\n        'DM',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n        muteLengthStrChanges,\n        muteReasonChanges,\n        endStrChanges,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'ChangeMute',\n        'Log',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n        muteLengthStrChanges,\n        muteReasonChanges,\n        endStrChanges,\n    );\n\n    // Util.logc('Mutes1', 'Completed ChangeMute');\n\n    return true;\n};\n\nexports.unMute = function (guild, channel, userResolvable, moderatorResolvable) {\n    // Stop mute\n    Util.logc('Mutes1', `\\nStarted UnMute on ${userResolvable}`);\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    // Resolve parameter data\n\n    const resolvedUser = resolveUser(guild, userResolvable);\n    const resolvedModerator = resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnMute', `${resolvedUser}`);\n    }\n\n    Util.logc('Mutes1', `Resolved user as ${resolvedUser.id}`);\n\n    // Get mute data\n\n    const pastMutes = muteCache[guildId].filter(r => r.user_id == resolvedUser.id);\n    const totalMutes = pastMutes.length;\n\n    // Check they are actually muted\n\n    const muteRecord = muteCacheActive[guildId][resolvedUser.id];\n\n    if (!muteRecord) {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnMute', 'User is not muted');\n    }\n\n    // Verify mute can be changed\n\n    if (!higherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnMute', 'User has equal or higher rank');\n    }\n\n    if (!higherRank(moderatorResolvable, Util.getMemberById(muteRecord.mod_id, guild), true)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'UnMute', 'Moderator who muted has higher privilege');\n    }\n\n    // Update mute SQL record and cache\n\n    Data.updateRecords(\n        guild,\n        'mutes',\n        {\n            user_id: resolvedUser.id,\n        },\n        {\n            active: 0,\n        },\n    );\n\n    for (let i = 0; i < muteCache[guildId].length; i++) {\n        const row = muteCache[guildId][i];\n        if (row.user_id == resolvedUser.id) row.active = 0;\n    }\n\n    delete muteCacheActive[guildId][resolvedUser.id];\n\n    // Remove mute timeout (if stopped early)\n\n    remTimeout(guild, resolvedUser.id);\n\n    // Add SendMessages role\n\n    addSendMessages(resolvedUser.member);\n\n    // Send the relevant messages\n\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'UnMute',\n        'Channel',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'UnMute',\n        'DM',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'UnMute',\n        'Log',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n    );\n\n    // Util.logc('Mutes1', 'Completed UnMute');\n\n    return true;\n};\n\nexports.remMute = async function (guild, channel, userResolvable, moderatorResolvable) {\n    // Undo mute\n    Util.logc('Mutes1', `\\nStarted RemMute on ${userResolvable}, waiting for UnMute to complete...`);\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    // Stop active mute\n\n    exports.unMute(guild, null, userResolvable, moderatorResolvable);\n\n    // Util.logc('Mutes1', 'UnMute completed, continuing RemMute');\n\n    // Resolve parameter data\n\n    const resolvedUser = resolveUser(guild, userResolvable);\n    const resolvedModerator = resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'RemMute', `${resolvedUser}`);\n    }\n\n    Util.logc('Mutes1', `Resolved user as ${resolvedUser.id}`);\n\n    const pastMutes = muteCache[guildId].filter(r => r.user_id == resolvedUser.id);\n    const totalMutes = pastMutes.length - 1;\n    const hasBeenMuted = pastMutes.length > 0;\n    const lastMute = hasBeenMuted ? pastMutes[pastMutes.length - 1] : null;\n\n    // Verify mute can be removed\n\n    if (!higherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'RemMute', 'User has equal or higher rank');\n    }\n\n    if (hasBeenMuted && !higherRank(moderatorResolvable, Util.getMemberById(lastMute.mod_id, guild), true)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'RemMute', 'Moderator who muted has higher privilege');\n    }\n\n    // Check they have actually been muted\n\n    if (!hasBeenMuted) {\n        return Util.commandFailed(channel, moderatorResolvable, 'RemMute', 'User has never been muted');\n    }\n\n    // Delete from database and cache\n\n    Data.deleteRecords(guild, 'mutes', { mute_id: lastMute.mute_id });\n\n    for (let i = muteCache[guildId].length - 1; i >= 0; i--) {\n        const row = muteCache[guildId][i];\n        if (row.mute_id == lastMute.mute_id) muteCache[guildId].splice(i, 1);\n    }\n\n    // Send the relevant messages\n\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'RemMute',\n        'Channel',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'RemMute',\n        'DM',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'RemMute',\n        'Log',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n    );\n\n    // Util.logc('Mutes1', 'Completed RemMute');\n\n    return true;\n};\n\nexports.clearMutes = async function (guild, channel, userResolvable, moderatorResolvable) {\n    // Undo mute\n    Util.logc('Mutes1', `\\nStarted ClearMutes on ${userResolvable}, waiting for UnMute to complete...`);\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    // Stop active mute\n\n    exports.unMute(guild, null, userResolvable, moderatorResolvable);\n\n    // Util.logc('Mutes1', 'UnMute completed, continuing ClearMutes');\n\n    // Resolve parameter data\n\n    const resolvedUser = resolveUser(guild, userResolvable);\n    const resolvedModerator = resolveUser(guild, moderatorResolvable, true);\n\n    if (typeof resolvedUser === 'string') {\n        return Util.commandFailed(channel, moderatorResolvable, 'ClearMutes', `${resolvedUser}`);\n    }\n\n    Util.logc('Mutes1', `Resolved user as ${resolvedUser.id}`);\n\n    const pastMutes = muteCache[guildId].filter(r => r.user_id == resolvedUser.id);\n    const totalMutes = 0;\n    const hasBeenMuted = pastMutes.length > 0;\n\n    // Verify mutes can be removed\n\n    if (!higherRank(moderatorResolvable, resolvedUser.member)) {\n        return Util.commandFailed(channel, moderatorResolvable, 'ClearMutes', 'User has equal or higher rank');\n    }\n\n    // Check they have actually been muted\n\n    if (!hasBeenMuted) {\n        return Util.commandFailed(channel, moderatorResolvable, 'ClearMutes', 'User has never been muted');\n    }\n\n    // Delete from database and cache\n\n    Data.deleteRecords(guild, 'mutes', { user_id: resolvedUser.id });\n\n    for (let i = muteCache[guildId].length - 1; i >= 0; i--) {\n        const row = muteCache[guildId][i];\n        if (row.user_id == resolvedUser.id) muteCache[guildId].splice(i, 1);\n    }\n\n    // Send the relevant messages\n\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'ClearMutes',\n        'Channel',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'ClearMutes',\n        'DM',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n    );\n    sendMuteMessage(\n        guild,\n        channel,\n        resolvedUser.id,\n        'ClearMutes',\n        'Log',\n        resolvedUser.member,\n        moderatorResolvable,\n        resolvedModerator.mention,\n        totalMutes,\n    );\n\n    // Util.logc('Mutes1', 'Completed ClearMutes');\n\n    return true;\n};\n\nexports.checkMuted = function (guild, userId) {\n    const guildId = Data.getBaseGuildId(guild.id);\n\n    if (!has.call(muteCacheActive, guildId)) return false;\n    return has.call(muteCacheActive[guildId], userId);\n};\n\nexports.initialize = async function () {\n    // Get mute data from db, start all initial mute timeouts\n    // const nowTick = +new Date();\n    Util.logc('MutesInit', '> Initializing mute data');\n\n    nextMuteId = (await Data.query('SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_name=? AND table_schema=DATABASE()', [\n        'mutes',\n    ]))[0].AUTO_INCREMENT;\n\n    await Promise.all(\n        client.guilds.map(async (guild) => {\n            const guildId = Data.getBaseGuildId(guild.id);\n            if (guildId != guild.id) return;\n\n            muteCache[guildId] = [];\n            muteCacheActive[guildId] = {};\n            const results = await Data.getRecords(guild, 'mutes');\n\n            for (let i = 0; i < results.length; i++) {\n                const muteStored = results[i];\n\n                muteCache[guildId].push(muteStored);\n\n                if (muteStored.active == 1) {\n                    muteCacheActive[guildId][muteStored.user_id] = muteStored;\n                    addTimeout(guild, muteStored.user_id, muteStored.end_tick);\n                }\n            }\n        }),\n    );\n\n    Util.logc('MutesInit', '> Completed mute initialization');\n\n    index.secure();\n};\n"
  },
  {
    "path": "core/ManageTrello.js",
    "content": "const TrelloHandler = index.TrelloHandler;\nconst DateFormat = index.DateFormat;\n\nconst boards = {\n    '477270527535480834': '59392df2d36f09ca35556339', // Veil\n};\n\nconst lists = {\n    mutes: '59392e154996d41c2127c335',\n    kicks: '59392e1330235b9cc7a28f94',\n    bans: '59392e1037e46c4d8b1af98c',\n};\n\nconst labels = {\n    reverted: '59396434a65061821399b435',\n};\n\nconst cache = {};\nexports.cache = cache;\n\n/*\n\n    cache {\n        guildId: {\n            listId: {\n                id: 123,\n                cards: [ // Ordered from newest\n                    {\n                        id: 123,\n                        stampCreated: 123,\n                        targetId: 123,\n                    }\n                ]\n            }\n        }\n    }\n\n    -UnMute\n        -Mark last card for that user as dueComplete\n\n    -Mute\n        -Mark last card for that user as dueComplete\n        -Create new card\n\n    -IncMute\n        -Change due date to new date\n\n    -UndoMute\n        -Mark last card for that user as dueComplete\n        -Mark last card for that user *without Reverted label* with Reverted label\n\n*/\n\n/*\n\n    -Cache all lists in board\n        -Cache all cards in lists\n\n    -When a new card is added or a card is altered, alter it in cache and API\n\n    -Get card data from cache\n\n*/\n\nfunction fixDesc(cardDesc) {\n    let cardDescStr;\n\n    if (Util.isObject(cardDesc)) {\n        const cardDescNew = [];\n\n        for (const [key, value] of Object.entries(cardDesc)) {\n            if (typeof key === 'number') {\n                cardDescNew.push(`${value}`);\n            } else {\n                cardDescNew.push(`${key}: ${value}`);\n            }\n        }\n\n        cardDescStr = cardDescNew.join('\\n\\n');\n    } else {\n        cardDescStr = cardDesc;\n    }\n\n    cardDescStr = cardDescStr.substr(0, 16384);\n\n    return cardDescStr;\n}\n\nfunction getTargetIdFromDesc(desc) {\n    let targetId = desc.match(/User ID: (\\d+)/);\n\n    if (targetId && targetId.length > 1 && targetId[1]) {\n        targetId = targetId[1];\n    } else {\n        targetId = null;\n    }\n\n    return targetId;\n}\n\nfunction getStampFromId(id) {\n    return 1000 * parseInt(id.substring(0, 8), 16);\n}\n\nfunction sortCards(a, b) { // Newest first\n    return b.stampCreated - a.stampCreated;\n}\n\nfunction makeCacheCard(nowCard, targetId) {\n    if (targetId == null) targetId = null;\n\n    const nowCardId = nowCard.id;\n\n    const cacheCard = Util.cloneObj(nowCard);\n    cacheCard.stampCreated = getStampFromId(nowCardId);\n    cacheCard.targetId = targetId;\n\n    return cacheCard;\n}\n\nexports.findCard = function (guild, listName, targetId, callback) {\n    if (!cache[guild.id]) return false;\n\n    listName = listName.toLowerCase();\n\n    if (!has.call(lists, listName)) {\n        Util.log(`List ${listName} does not exist`);\n        return false;\n    }\n\n    const guildId = guild.id;\n    const listId = lists[listName];\n\n    const cacheCards = cache[guildId][listId].cards;\n\n    for (let i = 0; i < cacheCards.length; i++) {\n        if (cacheCards[i].targetId === targetId) {\n            Util.log(`>> FOUND CARD FOR ${targetId} FROM CACHE <<`);\n            callback(true, cacheCards[i]);\n            return undefined;\n        }\n    }\n\n    TrelloHandler.get('/1/search', {\n        query: targetId,\n        modelTypes: 'cards',\n        card_fields: 'desc',\n        cards_limit: 1,\n    }, (err, data) => {\n        Util.log('--[FindCard] TRELLO FEEDBACK START--');\n        Util.log(err);\n        Util.log('--<>--');\n        Util.log(data);\n        Util.log('--[FindCard] TRELLO FEEDBACK END--');\n\n        const ok = err == null && data.cards.length > 0;\n\n        if (ok) {\n            const cardData = data.cards[0];\n            const cardId = cardData.id;\n\n            let cacheCard = null;\n\n            for (let i = 0; i < cacheCards.length; i++) {\n                if (cacheCards[i].id === cardId) {\n                    cacheCard = cacheCards[i];\n                    cacheCard.targetId = targetId;\n                    Util.log(`>> UPDATED EXISTING TARGETID FOR ${targetId} <<`);\n                    break;\n                }\n            }\n\n            if (cacheCard == null) {\n                cacheCard = makeCacheCard(cardData, targetId);\n\n                cacheCards.push(cacheCard);\n                cacheCards.sort(sortCards);\n            }\n\n            callback(ok, cacheCard);\n        } else {\n            callback(ok, err);\n        }\n    });\n\n    return undefined;\n};\n\nexports.dueComplete = function (guild, cardId, callback) {\n    if (!cache[guild.id]) return false;\n\n    TrelloHandler.put(`/1/cards/${cardId}/dueComplete`, {\n        value: true,\n    }, (err, data) => {\n        Util.log('--[DueComplete] TRELLO FEEDBACK START--');\n        Util.log(err);\n        Util.log('--<>--');\n        Util.log(data);\n        Util.log('--[DueComplete] TRELLO FEEDBACK END--');\n\n        if (callback) callback();\n    });\n\n    return true;\n};\n\nexports.setDesc = function (guild, cardId, cardDesc) {\n    if (!cache[guild.id]) return false;\n\n    cardDesc = fixDesc(cardDesc);\n\n    TrelloHandler.put(`/1/cards/${cardId}/desc`, {\n        value: cardDesc,\n    }, (err, data) => {\n        Util.log('--[SetDesc] TRELLO FEEDBACK START--');\n        Util.log(err);\n        Util.log('--<>--');\n        Util.log(data);\n        Util.log('--[SetDesc] TRELLO FEEDBACK END--');\n    });\n\n    return true;\n};\n\nexports.setDue = function (guild, cardId, dueDate) {\n    if (!cache[guild.id]) return false;\n\n    TrelloHandler.put(`/1/cards/${cardId}/due`, {\n        value: dueDate,\n    }, (err, data) => {\n        Util.log('--[SetDue] TRELLO FEEDBACK START--');\n        Util.log(err);\n        Util.log('--<>--');\n        Util.log(data);\n        Util.log('--[SetDue] TRELLO FEEDBACK END--');\n    });\n\n    return true;\n};\n\nexports.addLabel = function (guild, cardId, labelName) {\n    if (!cache[guild.id]) return false;\n\n    labelName = labelName.toLowerCase();\n\n    if (!has.call(labels, labelName)) {\n        Util.log(`Label ${labelName} does not exist`);\n        return false;\n    }\n\n    const labelId = labels[labelName];\n\n    TrelloHandler.post(`/1/cards/${cardId}/idLabels`, {\n        value: labelId,\n    }, (err, data) => {\n        Util.log('--[AddLabel] TRELLO FEEDBACK START--');\n        Util.log(err);\n        Util.log('--<>--');\n        Util.log(data);\n        Util.log('--[AddLabel] TRELLO FEEDBACK END--');\n    });\n\n    return true;\n};\n\nexports.addCard = function (guild, listName, cardName, cardDesc, dueDate) {\n    if (!cache[guild.id]) return false;\n\n    listName = listName.toLowerCase();\n\n    if (!has.call(lists, listName)) {\n        Util.log(`List ${listName} does not exist`);\n        return false;\n    }\n\n    const guildId = guild.id;\n    const listId = lists[listName];\n\n    if (dueDate == null) dueDate = null;\n\n    const targetId = cardDesc['User ID'];\n\n    cardDesc = fixDesc(cardDesc);\n\n    const nowDate = new Date();\n    const nowDateStr = DateFormat(nowDate, '[dd/mm/yyyy]');\n\n    TrelloHandler.post('/1/cards', {\n        idList: listId,\n        name: `${nowDateStr} ${cardName}`,\n        desc: cardDesc,\n        pos: 'top',\n        due: dueDate,\n    }, (err, data) => {\n        Util.log('--[AddCard] TRELLO FEEDBACK START--');\n        Util.log(err);\n        Util.log('--<>--');\n        Util.log(data);\n\n        Util.log('--[AddCard] TRELLO FEEDBACK END--');\n\n        if (!err && data) {\n            const cacheCards = cache[guildId][listId].cards;\n            const cacheCard = makeCacheCard(data, targetId);\n\n            cacheCards.push(cacheCard);\n            cacheCards.sort(sortCards);\n        }\n    });\n\n    return true;\n};\n\nexports.setupCache = function (guild) {\n    const guildId = guild.id;\n\n    if (!has.call(boards, guildId)) {\n        // Util.log(`Board ${guildId} does not exist`);\n        return false;\n    }\n\n    const boardId = boards[guildId];\n\n    Util.log(`Fetching trello data for ${guild.name}`);\n\n    TrelloHandler.get(`/1/boards/${boardId}`, {\n        'lists': 'open',\n        'cards': 'open',\n    }, (err, data) => {\n        if (err) {\n            Util.log(err);\n            Util.log('--<>--');\n            Util.log(data);\n            return;\n        }\n\n        Util.log(`Cached trello data for ${guild.name}`);\n\n        cache[guildId] = {};\n\n        const nowLists = data.lists;\n        for (let i = 0; i < nowLists.length; i++) {\n            const nowList = nowLists[i];\n            const nowListId = nowList.id;\n\n            const cacheList = Util.cloneObj(nowList);\n            cacheList.cards = [];\n\n            cache[guildId][nowListId] = cacheList;\n        }\n\n        const nowCards = data.cards;\n        for (let i = 0; i < nowCards.length; i++) {\n            const nowCard = nowCards[i];\n            const cacheList = cache[guildId][nowCard.idList];\n\n            const targetId = getTargetIdFromDesc(nowCard.desc);\n\n            const cacheCard = makeCacheCard(nowCard, targetId);\n\n            cacheList.cards.push(cacheCard);\n        }\n\n        for (const cacheListId of Object.keys(cache[guildId])) {\n            const cacheList = cache[guildId][cacheListId];\n            const cacheCards = cacheList.cards;\n            cacheCards.sort(sortCards);\n            // Util.log(cacheCards);\n        }\n    });\n\n    return true;\n};\n"
  },
  {
    "path": "disabled/Warn.js",
    "content": "module.exports = Cmds.addCommand({\n    cmds: [';warn ', ';warnhammer '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    },\n\n    desc: 'Warns a user and puts the warning on their record',\n\n    args: '([@user] | [id] | [name]) (OPT: [reason])',\n\n    example: 'vae spamming the chat',\n\n    // /////////////////////////////////////////////////////////////////////////////////////////\n\n    func: (cmd, args, msgObj, speaker, channel, guild) => {\n        args = args.trim();\n\n        const data = Util.getDataFromString(args,\n            [\n                [\n                    function (str) {\n                        return Util.getMemberByMixed(str, guild) || Util.isId(str);\n                    },\n                ],\n            ]\n        , true);\n\n        if (!data) {\n            return Util.sendEmbed(channel, 'Warn Failed', 'User not found', Util.makeEmbedFooter(speaker), null, colGreen, null);\n        }\n\n        Util.log(`Change Arg Data: ${data}`);\n\n        const member = data[0];\n        const reason = data[1];\n\n        Admin.addWarning(guild, channel, member, speaker, reason);\n\n        return true;\n    },\n});\n"
  },
  {
    "path": "index.js",
    "content": "console.log('\\n-STARTING-\\n');\n\n// //////////////////////////////////////////////////////////////////////////////////////////////\n\nconst Auth = require('./Auth.js');\n\nprocess.on('unhandledRejection', (error) => {\n    console.log('>>> Uncaught Promise Error <<<');\n    console.error(error);\n    process.exit(1);\n});\n\nexports.FileSys = require('fs');\nexports.DateFormat = require('dateformat');\nexports.Request = require('request');\nexports.Urban = require('urban');\nconst TrelloObj = require('node-trello');\nexports.Ytdl = require('ytdl-core');\nexports.Path = require('path');\nexports.NodeOpus = require('node-opus');\nexports.Exec = require('child_process').exec;\nconst YtInfoObj = require('youtube-node');\nexports.Translate = require('google-translate')(Auth.translateKey);\n// const { TranslationServiceClient } = require('@google-cloud/translate').v3beta1;\nexports.MySQL = require('mysql');\nexports.NodeUtil = require('util');\nexports.Hepburn = require('hepburn');\n// exports.YoutubeSearch = require('youtube-search');\n\nexports.YtInfo = new YtInfoObj();\nexports.TrelloHandler = new TrelloObj(Auth.trelloKey, Auth.trelloToken);\n// exports.TranslateClient = new TranslationServiceClient();\n\nexports.linkGuilds = [['417110408088780801', '309785618932563968']];\n\nexports.dbPass = Auth.dbPass;\nexports.dbPassVeil = Auth.dbPassVeil;\n\n// //////////////////////////////////////////////////////////////////////////////////////////////\n\nglobal.index = module.exports;\n\nglobal.has = Object.prototype.hasOwnProperty;\nglobal.selfId = '224529399003742210';\nglobal.vaebId = '107593015014486016';\nglobal.botDir = '/home/flipflop8421/files/discordExp/VaeBot'; // '/home/flipflop8421/files/discordExp/VaeBot' 'C:\\\\Users\\\\Adam\\\\Documents\\\\GitVaeb\\\\VaeBot'\n\nglobal.Util = require('./Util.js');\nglobal.Data = require('./core/ManageData.js');\nglobal.Trello = require('./core/ManageTrello.js');\nglobal.Admin = require('./core/ManageAdmin.js');\nglobal.Music = require('./core/ManageMusic.js');\nglobal.Music2 = require('./core/ManageMusic2.js');\nglobal.Cmds = require('./core/ManageCommands.js');\nglobal.Events = require('./core/ManageEvents.js');\nglobal.Discord = require('discord.js');\n\n/* Discord.BaseGuildMember = Discord.GuildMember;\n\nDiscord.NewGuildMember = class extends Discord.BaseGuildMember {\n    constructor(guild, data) {\n        super(guild, data);\n        Util.mergeUser(this);\n    }\n}; */\n\n/* class ExtendableProxy {\n    constructor(guild, data) {\n        const OriginalGuildMember = new Discord.BaseGuildMember(guild, data);\n\n        return new Proxy(this, {\n            get: (object, key) => {\n                return OriginalGuildMember[key];\n            },\n            set: (object, key, value) => {\n                OriginalGuildMember[key] = value;\n                return value;\n            },\n        });\n    }\n}\n\nDiscord.GuildMember = class extends ExtendableProxy {}; */\n\n/* Discord.GuildMember.prototype.getProp = function (p) {\n    if (this[p] != null) return this[p];\n    return this.user[p];\n};\n\nDiscord.User.prototype.getProp = function (p) {\n    return this[p];\n}; */\n\nexports.YtInfo.setKey(Auth.youtube);\n\nglobal.client = new Discord.Client({\n    disabledEvents: ['TYPING_START'],\n    fetchAllMembers: true,\n    disableEveryone: true,\n});\n\n// //////////////////////////////////////////////////////////////////////////////////////////////\n\nexports.dailyMutes = [];\nexports.dailyKicks = [];\nexports.dailyBans = [];\n\nexports.commandTypes = {\n    locked: 'vaeb',\n    administrator: 'administrator',\n    staff: 'staff',\n    public: 'null',\n};\n\nconst briefHour = 2;\nconst msToHours = 1 / (1000 * 60 * 60);\nconst dayMS = 24 / msToHours;\nlet madeBriefing = false;\n\nglobal.colAction = 0xf44336; // Log of action, e.g. action from within command\nglobal.colUser = 0x4caf50; // Log of member change\nglobal.colMessage = 0xffeb3b; // Log of message change\nglobal.colCommand = 0x2196f3; // Log of command being executed\n\nglobal.colGreen = 0x00e676;\nglobal.colBlue = 0x00bcd4;\n\nexports.blockedUsers = {};\nexports.blockedWords = [];\n\nexports.runFuncs = [];\nexports.warnedImage = {};\n\n// //////////////////////////////////////////////////////////////////////////////////////////////\n\nfunction setBriefing() {\n    setTimeout(() => {\n        const time1 = new Date();\n        const time2 = new Date();\n\n        time2.setHours(briefHour);\n        time2.setMinutes(0);\n        time2.setSeconds(0);\n        time2.setMilliseconds(0);\n\n        const t1 = +time1;\n        const t2 = +time2;\n        let t3 = t2 - t1;\n        if (t3 < 0) t3 += dayMS;\n\n        const channel = client.channels.get('168744024931434498');\n        // const guild = channel.guild;\n\n        Util.log(`Set daily briefing for ${t3 * msToHours} hours`);\n\n        setTimeout(() => {\n            // const upField = { name: '​', value: '​', inline: false };\n            const muteField = { name: 'Mutes', value: 'No mutes today', inline: false };\n            // var rightField = {name: \"​\", value: \"​\"}\n            const kickField = { name: 'Kicks', value: 'No kicks today', inline: false };\n            const banField = { name: 'Bans', value: 'No bans today', inline: false };\n\n            const embFields = [muteField, kickField, banField];\n\n            const embObj = {\n                title: 'Daily Briefing',\n                description: '​',\n                fields: embFields,\n                footer: { text: '>> More info in #vaebot-log <<' },\n                thumbnail: { url: './resources/avatar.png' },\n                color: colGreen,\n            };\n\n            if (exports.dailyMutes.length > 0) {\n                const dataValues = [];\n\n                for (let i = 0; i < exports.dailyMutes.length; i++) {\n                    const nowData = exports.dailyMutes[i];\n                    const userId = nowData[0];\n                    // const userName = nowData[1];\n                    const userReason = nowData[2];\n                    // const userTime = nowData[3];\n                    const targMention = `<@${userId}>`;\n                    let reasonStr = '';\n                    if (userReason != null && userReason.trim().length > 0) {\n                        reasonStr = ` : ${userReason}`;\n                    }\n                    dataValues.push(targMention + reasonStr);\n                }\n\n                muteField.value = dataValues.join('\\n\\n');\n            }\n\n            muteField.value = `​\\n${muteField.value}\\n​`;\n\n            if (exports.dailyKicks.length > 0) {\n                const dataValues = [];\n\n                for (let i = 0; i < exports.dailyKicks.length; i++) {\n                    const nowData = exports.dailyKicks[i];\n                    const userId = nowData[0];\n                    // const userName = nowData[1];\n                    const userReason = nowData[2];\n                    const targMention = `<@${userId}>`;\n                    let reasonStr = '';\n                    if (userReason != null && userReason.trim().length > 0) {\n                        reasonStr = ` : ${userReason}`;\n                    }\n                    dataValues.push(targMention + reasonStr);\n                }\n\n                kickField.value = dataValues.join('\\n\\n');\n            }\n\n            kickField.value = `​\\n${kickField.value}\\n​`;\n\n            if (exports.dailyBans.length > 0) {\n                const dataValues = [];\n\n                for (let i = 0; i < exports.dailyBans.length; i++) {\n                    const nowData = exports.dailyBans[i];\n                    const userId = nowData[0];\n                    // const userName = nowData[1];\n                    const userReason = nowData[2];\n                    const targMention = `<@${userId}>`;\n                    let reasonStr = '';\n                    if (userReason != null && userReason.trim().length > 0) {\n                        reasonStr = ` : ${userReason}`;\n                    }\n                    dataValues.push(targMention + reasonStr);\n                }\n\n                banField.value = dataValues.join('\\n\\n');\n            }\n\n            banField.value = `​\\n${banField.value}\\n​`;\n\n            if (exports.dailyMutes.length > 0 || exports.dailyKicks.length > 0 || exports.dailyBans.length > 0) {\n                channel.send(undefined, { embed: embObj }).catch(error => Util.log(`[E_SendBriefing] ${error}`));\n            }\n\n            exports.dailyMutes = []; // Reset\n            exports.dailyKicks = [];\n            exports.dailyBans = [];\n\n            setBriefing();\n        }, t3);\n    }, 2000); // Let's wait 2 seconds before starting countdown, just in case of floating point errors triggering multiple countdowns\n}\n\nexports.globalBan = {\n    '201740276472086528': true, // No idea\n    '75736018761818112': true, // No idea\n    '123146298504380416': true, // No idea\n    '263372398059847681': true, // No idea\n    '238981466606927873': true, // Lindah\n    '189687397951209472': true, // xCraySECx / Nico Nico\n    '154255141317378050': true, // HighDefinition\n    '157749388964265985': true, // Zetroxer\n    '280419231181307906': true, // Solarical\n    '169261309353918464': true, // Slappy826\n    '331958080164200453': true, // derickbuum\n    '284410389469593611': true, // Papi\n    '255779902387650560': true, // Shiro's Twin\n    '80385350316339200': true, // Fennec\n    '175013861701713920': true, // chrome\n};\n\nfunction securityFunc(guild, member, sendRoleParam) {\n    const guildName = guild.name;\n    // const guildId = guild.id;\n\n    const memberId = member.id;\n    const memberName = Util.getFullName(member);\n\n    let sendRole = sendRoleParam;\n    if (sendRole == null) sendRole = Util.getRole('SendMessages', guild);\n\n    if (has.call(exports.globalBan, memberId)) {\n        member.kick().catch(console.error);\n        Util.logc('BanOld1', `Globally banned user ${memberName} had already joined ${guildName}`);\n        return;\n    }\n\n    if (sendRole != null) {\n        const isMuted = Admin.checkMuted(guild, memberId);\n        if (isMuted) {\n            if (Util.hasRole(member, sendRole)) {\n                member.removeRole(sendRole).catch(console.error);\n                Util.logc('MuteOld1', `Removed SendMessages from muted user ${memberName} who had already joined ${guildName}`);\n            }\n        } else if (!Util.hasRole(member, sendRole)) {\n            member.addRole(sendRole).catch(console.error);\n            Util.logc('AssignOld1', `Assigned SendMessages to old member ${memberName}`);\n        }\n    }\n}\n\nfunction setupSecurity(guild) {\n    const sendRole = Util.getRole('SendMessages', guild);\n\n    Util.logc('Security1', `Setting up security for ${guild.name} (${guild.members.size} members)`);\n\n    guild.members.forEach((member) => {\n        securityFunc(guild, member, sendRole);\n    });\n}\n\nexports.setupSecurityFunc = setupSecurity;\n\nfunction setupSecurityVeil() {\n    const veilGuild = client.guilds.get('477270527535480834');\n    if (!veilGuild) return Util.logc('SecureVeil1', '[ERROR_VP] Veil guild not found!');\n    const guild = client.guilds.get('309785618932563968');\n    if (!guild) return Util.logc('SecureVeil1', '[ERROR_VP] New Veil guild not found!');\n    const veilBuyer = veilGuild.roles.find('name', 'Vashta-Owner');\n    if (!veilBuyer) return Util.logc('SecureVeil1', '[ERROR_VP] Veil Buyer role not found!');\n    const newBuyer = guild.roles.find('name', 'Vashta-Owner');\n    if (!newBuyer) return Util.logc('SecureVeil1', '[ERROR_VP] New Buyer role not found!');\n    // const guildId = guild.id;\n    // const guildName = guild.name;\n\n    Util.logc('SecureVeil1', `Setting up auto-kick for ${guild.name} (${guild.members.size} members)`);\n\n    guild.members.forEach((member) => {\n        const memberId = member.id;\n\n        if (memberId === vaebId || memberId === selfId) return;\n\n        const memberName = Util.getFullName(member);\n        const veilMember = Util.getMemberById(memberId, veilGuild);\n        if (!veilMember) {\n            Util.logc('SecureVeil1', `[Auto-Old-Kick 1] User not in Veil: ${memberName}`);\n            member.kick().catch(error => Util.logc('SecureVeil1', `[E_AutoOldKick1] ${memberName} | ${error}`));\n            return;\n        }\n        if (!veilMember.roles.has(veilBuyer.id)) {\n            Util.logc('SecureVeil1', `[Auto-Old-Kick 2] User does not have Buyer role: ${memberName}`);\n            member.kick().catch(error => Util.logc('SecureVeil1', `[E_AutoOldKick2] ${memberName} | ${error}`));\n            return;\n        }\n        if (!member.roles.has(newBuyer.id)) {\n            member.addRole(newBuyer).catch(error => Util.logc('SecureVeil1', `[E_AutoOldAddRole1] ${memberName} | ${error}`));\n            Util.logc('SecureVeil1', `Updated old member with Buyer role: ${memberName}`);\n        }\n    });\n\n    return undefined;\n}\n\nconst veilGuilds = {\n    '477270527535480834': true,\n    '309785618932563968': true,\n};\n\nexports.secure = async function () {\n    Util.log('> Securing guilds...');\n\n    let securityNum = 0;\n    const veilGuildsNum = Object.keys(veilGuilds).length;\n\n    await Promise.all(\n        client.guilds.map(async (newGuild) => {\n            await newGuild.fetchMembers();\n\n            if (has.call(veilGuilds, newGuild.id)) {\n                securityNum++;\n                if (securityNum === veilGuildsNum) setupSecurityVeil();\n            }\n\n            setupSecurity(newGuild);\n\n            Trello.setupCache(newGuild);\n        }),\n    );\n\n    Util.log('> Security setup complete');\n};\n\n/* function notifyOn(channel) { // Not if the last message was a reminder...\n    // Util.sendDescEmbed(channel, 'Reminder', 'You can gain access to the #anime channel by sending a message saying: `;toggle anime`', null, null, colBlue);\n} */\n\n// //////////////////////////////////////////////////////////////////////////////////////////////\n\nCmds.initCommands();\n\n// Index_Ready -> Data_SQL -> Mutes_Initialize -> Index_Secure\n\nclient.on('ready', async () => {\n    Util.log(`> Connected as ${client.user.username}!`);\n\n    if (madeBriefing === false) {\n        madeBriefing = true;\n        setBriefing();\n    }\n\n    Util.log('> Start caching guild members');\n\n    const dbGuilds = [];\n\n    await Promise.all(\n        client.guilds.map(async (newGuild) => {\n            await newGuild.fetchMembers();\n\n            const allMembers = newGuild.members;\n\n            // allMembers.forEach(m => Util.mergeUser(m));\n            allMembers.forEach(Util.mergeUser);\n            Util.logc('InitProxy', `Added proxies to the ${allMembers.size} members of ${newGuild.name}`);\n\n            // Music2.initGuild(newGuild);\n\n            if (newGuild.id == '477270527535480834') dbGuilds.push(newGuild);\n        }),\n    )\n        .then(() => Util.log('> Cached all guild members'))\n        .catch(e => Util.log('> Error while caching guild members:', e));\n\n    // Util.log('> Cached all guild members!');\n\n    await Data.connectInitial(dbGuilds);\n});\n\nclient.on('disconnect', (closeEvent) => {\n    Util.log('DISCONNECTED');\n    Util.log(closeEvent);\n    Util.log(`Code: ${closeEvent.code}`);\n    Util.log(`Reason: ${closeEvent.reason}`);\n    Util.log(`Clean: ${closeEvent.wasClean}`);\n});\n\nclient.on('guildCreate', (guild) => {\n    guild.fetchMembers().then(() => {\n        const allMembers = guild.members;\n        allMembers.forEach(m => Util.mergeUser(m));\n    });\n});\n\nclient.on('guildMemberRemove', (member) => {\n    const guild = member.guild;\n\n    if (exports.raidMode[guild.id]) return;\n\n    Events.emit(guild, 'UserLeave', member);\n\n    const sendLogData = [\n        'User Left',\n        guild,\n        member,\n        { name: 'Username', value: Util.resolveMention(member) },\n        { name: 'Highest Role', value: member.highestRole.name },\n    ];\n\n    Util.sendLog(sendLogData, colUser);\n});\n\nexports.newMemberTime = 1000 * 16;\nexports.newMemberTime2 = 1000 * 60 * 1;\nexports.recentMembers = [];\nexports.recentMembers2 = [];\n\nconst youngAccountTime = 1000 * 60 * 60 * 24 * 6.5;\n\nfunction raidBan(member, defaultChannel, banMsg) {\n    member\n        .ban()\n        .then(() => {\n            if (defaultChannel) defaultChannel.send(banMsg).catch(console.error);\n        })\n        .catch(console.error);\n}\n\nconst raidMsgPossible = `Hi! Unfortunately you've joined our Vashta Discord whilst the server is being raided, so you've been automatically treated as a raider and banned from our server.\n\nIt's not all bad though, looking at the details of your account there's a possibility that you aren't a raider, so if that is the case then you'll probably be unbanned shortly and can rejoin, if so I apologise for the trouble.\n\nIf you don't get unbanned for some reason (and you aren't trying to raid us), please contact Vaeb#0001 (<@107593015014486016>) my developer, and he will sort you out. If you are a raider, better luck next time.\n\nAs a reminder, here's our server invite for if you're a real human and will be joining us later, or if you're a raider and want to try again: https://discord.gg/bvS5gwY`;\n\n// exports.checkRaidMember = function (member, joinStamp, defaultChannel) {\n//     const createdAt = +member.user.createdAt;\n//     const memberName = `${member.user.username}#${member.user.discriminator} (${member.id})`;\n\n//     if (createdAt == null || joinStamp - createdAt < youngAccountTime) {\n//         raidBan(member, defaultChannel, `Auto-banned detected raider: ${memberName}`);\n//     } else {\n//         member\n//             .send(raidMsgPossible)\n//             .then(() => raidBan(member, defaultChannel, `Auto-removed possible raider: ${memberName}`))\n//             .catch(() => raidBan(member, defaultChannel, `Auto-removed possible raider: ${memberName}`));\n//     }\n// };\n\nexports.checkRaidMember = function (member, joinStamp, defaultChannel, sendRole) {\n    const createdAt = +member.user.createdAt;\n    if (createdAt == null || joinStamp - createdAt < youngAccountTime) {\n        member.ban().catch(console.error);\n        if (defaultChannel) {\n            const memberName = `${member.user.username}#${member.user.discriminator} (${member.id})`;\n            defaultChannel.send(`Auto-banned detected raider: ${memberName}`).catch(console.error);\n        }\n    } else if (sendRole && Util.hasRole(member, sendRole)) {\n        member.removeRole(sendRole).catch(console.error);\n    }\n};\n\nexports.disableRaidMode = function (guild, defaultChannel) {\n    exports.raidMode[guild.id] = undefined;\n\n    if (!defaultChannel) {\n        defaultChannel = guild.channels.find(c => c.name === 'general') || guild.channels.find(c => c.name === 'lounge');\n    }\n\n    const botRole = guild.roles.find(r => r.name.startsWith('Server Bot'));\n    if (botRole) {\n        botRole.edit({ name: 'Server Bot', color: '#8c7ae6' }).catch(console.error);\n    }\n\n    Util.log('Raid mode disabled');\n    Util.print(defaultChannel, 'Raid mode disabled');\n\n    exports.setupSecurityFunc(guild);\n};\n\nexports.activateRaidMode = function (guild, defaultChannel, wasAuto) {\n    exports.raidMode[guild.id] = true;\n\n    const raidingMembers = exports.recentMembers2.slice();\n\n    const joinStamp = +new Date();\n    const sendRole = Util.getRole('SendMessages', guild);\n\n    if (!defaultChannel) {\n        defaultChannel = guild.channels.find(c => c.name === 'general') || guild.channels.find(c => c.name === 'lounge');\n    }\n\n    Util.log('Raid mode enabled');\n\n    const modRole = guild.roles.find(r => r.name === 'Moderator');\n    const modeRoleStr = modRole ? ` ${modRole}` : '';\n    if (defaultChannel) {\n        Util.print(\n            defaultChannel,\n            wasAuto ? `Raid detected - Raid mode has been automatically activated${modeRoleStr}` : 'Raid mode activated',\n        );\n    }\n\n    const botRole = guild.roles.find(r => r.name.startsWith('Server Bot'));\n    if (botRole) {\n        botRole.edit({ name: 'Server Bot [RAIDMODE]', color: '#eb2f06' }).catch(console.error);\n    }\n\n    for (let i = 0; i < raidingMembers.length; i++) {\n        const member = guild.members.get(raidingMembers[i].id);\n        if (member) exports.checkRaidMember(member, joinStamp, defaultChannel, sendRole);\n    }\n};\n\nclient.on('guildMemberAdd', (member) => {\n    const guild = member.guild;\n    const joinStamp = +new Date();\n\n    if (exports.raidMode[guild.id]) {\n        const defaultChannel = guild.channels.find(c => c.name === 'general') || guild.channels.find(c => c.name === 'lounge');\n        const sendRole = Util.getRole('SendMessages', guild);\n        exports.checkRaidMember(member, joinStamp, defaultChannel, sendRole);\n        return;\n    }\n\n    // Check raid mode\n\n    if (!exports.recentMembers.some(memberData => memberData.id === member.id)) {\n        exports.recentMembers.push({ id: member.id, joinStamp });\n        exports.recentMembers = exports.recentMembers.filter(memberData => joinStamp - memberData.joinStamp < exports.newMemberTime);\n    }\n\n    if (!exports.recentMembers2.some(memberData => memberData.id === member.id)) {\n        exports.recentMembers2.push({ id: member.id, joinStamp });\n        exports.recentMembers2 = exports.recentMembers2.filter(memberData => joinStamp - memberData.joinStamp < exports.newMemberTime2);\n    }\n\n    if (exports.recentMembers.length >= (joinStamp - guild.createdTimestamp > 1000 * 60 * 60 * 24 ? 7 : 20)) {\n        exports.activateRaidMode(guild, null, true);\n\n        return;\n    }\n\n    // Member joined\n\n    const guildName = guild.name;\n    const memberId = member.id;\n    const memberName = Util.getFullName(member);\n\n    Util.logc(memberId, `User joined: ${memberName} (${memberId}) @ ${guildName}`);\n\n    Util.mergeUser(member);\n\n    // Protect Veil Private\n\n    if (guild.id === '309785618932563968') {\n        const veilGuild = client.guilds.get('477270527535480834');\n        const veilBuyer = veilGuild.roles.find('name', 'Vashta-Owner');\n        const newBuyer = guild.roles.find('name', 'Vashta-Owner');\n        if (!veilGuild) {\n            Util.logc(memberId, '[ERROR_VP] Veil guild not found!');\n        } else if (!veilBuyer) {\n            Util.logc(memberId, '[ERROR_VP] Veil Buyer role not found!');\n        } else if (!newBuyer) {\n            Util.logc(memberId, '[ERROR_VP] New Buyer role not found!');\n        } else {\n            const veilMember = Util.getMemberById(memberId, veilGuild);\n            if (!veilMember) {\n                Util.logc(memberId, `[Auto-Kick 1] User not in Veil: ${memberName}`);\n                member.kick().catch(error => Util.logc(memberId, `[E_AutoKick1] ${error}`));\n                return;\n            }\n            if (!veilMember.roles.has(veilBuyer.id)) {\n                Util.logc(memberId, `[Auto-Kick 2] User does not have Buyer role: ${memberName}`);\n                member.kick().catch(error => Util.logc(memberId, `[E_AutoKick2] ${error}`));\n                return;\n            }\n            member.addRole(newBuyer).catch(error => Util.logc(memberId, `[E_AutoAddRole1] ${error}`));\n            Util.logc(memberId, 'Awarded new member with Buyer role');\n        }\n    }\n\n    // Restore buyer role\n\n    // if (guild.id == '477270527535480834') {\n    //     Data.query(`SELECT * FROM Users WHERE Disabled IS NULL AND DiscordId=${member.id};`, null, Data.connectionVeil).then((whitelistData) => {\n    //         if (whitelistData.length > 0) {\n    //             const buyerRole = Util.getRole('Vashta-Owner', guild);\n    //             if (buyerRole) {\n    //                 member.addRole(buyerRole)\n    //                     .catch(Util.logErr);\n    //                 Util.logc(member.id, `Assigned Buyer to new buyer ${memberName} who just joined ${guildName}`);\n    //             }\n    //         }\n    //     });\n    // }\n\n    // GlobalBan\n\n    if (has.call(exports.globalBan, memberId)) {\n        member.kick().catch(console.error);\n        Util.logc(memberId, `Globally banned user ${memberName} joined ${guildName}`);\n        return;\n    }\n\n    // Restore mute\n\n    const isMuted = Admin.checkMuted(guild, memberId);\n    if (isMuted) {\n        Util.logc(memberId, `Muted user ${memberName} joined ${guildName}`);\n    } else {\n        const sendRole = Util.getRole('SendMessages', guild);\n\n        if (sendRole) {\n            member.addRole(sendRole).catch(console.error);\n            Util.logc(memberId, `Assigned SendMessages to new member ${memberName}`);\n        }\n    }\n\n    Data.getRecords(guild, 'members', { user_id: member.id }).then((results) => {\n        const isBuyer = Util.hasRoleName(member, 'Vashta-Owner');\n        if (results.length == 0 && isBuyer) {\n            Data.addRecord(guild, 'members', { user_id: member.id, buyer: isBuyer ? 1 : 0 });\n            Util.logc(memberId, `Adding new member ${Util.getFullName(member)} to MySQL DB`);\n        }\n    });\n\n    if (memberId === '280579952263430145') member.setNickname('<- mentally challenged');\n\n    Events.emit(guild, 'UserJoin', member);\n\n    const sendLogData = ['User Joined', guild, member, { name: 'Username', value: Util.resolveMention(member) }];\n\n    Util.sendLog(sendLogData, colUser);\n});\n\nclient.on('guildMemberUpdate', (oldMember, member) => {\n    const guild = member.guild;\n\n    if (exports.raidMode[guild.id]) return;\n\n    const previousNick = oldMember.nickname;\n    const nowNick = member.nickname;\n    const oldRoles = oldMember.roles;\n    const nowRoles = member.roles;\n\n    const rolesAdded = nowRoles.filter(role => !oldRoles.has(role.id));\n\n    const rolesRemoved = oldRoles.filter(role => !nowRoles.has(role.id));\n\n    if (rolesAdded.size > 0) {\n        rolesAdded.forEach((nowRole) => {\n            if (\n                (member.id === '214047714059616257' || member.id === '148931616452902912') &&\n                (nowRole.id === '293458258042159104' || nowRole.id === '284761589155102720')\n            ) {\n                member.removeRole(nowRole).catch(console.error);\n            }\n\n            if (nowRole.name === 'Vashta-Owner' && guild.id === '477270527535480834') {\n                /* const message = 'Please join the Veil Buyers Discord:\\n\\nhttps://discord.gg/PRq6fcg\\n\\nThis is very important, thank you.';\n                const title = 'Congratulations on your purchase of Veil';\n                const footer = Util.makeEmbedFooter('AutoMessage');\n\n                Util.sendDescEmbed(member, title, message, footer, null, colBlue); */\n            }\n\n            // if (\n            //     (nowRole.name.includes('Mod') && member.id == '202660584330625024') ||\n            //     (nowRole.name.includes('Special') && member.id == '119203482598244356')\n            // ) {\n            //     member.removeRole(nowRole).catch(console.error);\n            // }\n\n            const isMuted = Admin.checkMuted(guild, member.id);\n            if (nowRole.name === 'SendMessages' && isMuted) {\n                member.removeRole(nowRole).catch(console.error);\n                Util.log(`Force re-muted ${Util.getName(member)} (${member.id})`);\n            } else {\n                const sendLogData = [\n                    'Role Added',\n                    guild,\n                    member,\n                    { name: 'Username', value: member.toString() },\n                    { name: 'Role Name', value: nowRole.name },\n                ];\n                Util.sendLog(sendLogData, colUser);\n            }\n\n            if (nowRole.name === 'Vashta-Owner') {\n                Data.getRecords(guild, 'members', { user_id: member.id, buyer: 1 }).then((results) => {\n                    if (results.length == 0) {\n                        Data.addRecord(guild, 'members', { user_id: member.id, buyer: 1 });\n                        Util.logc('BuyerAdd1', `Adding ${Util.getFullName(member)} as a buyer in MySQL DB`);\n                    }\n                });\n            }\n\n            Events.emit(guild, 'UserRoleAdd', member, nowRole);\n        });\n    }\n\n    if (rolesRemoved.size > 0) {\n        rolesRemoved.forEach((nowRole) => {\n            const isMuted = Admin.checkMuted(guild, member.id);\n            if (nowRole.name === 'SendMessages' && !isMuted) {\n                // member.addRole(nowRole).catch(console.error);\n                // Util.log(`Force re-unmuted ${Util.getName(member)} (${member.id})`);\n            } else {\n                const sendLogData = [\n                    'Role Removed',\n                    guild,\n                    member,\n                    { name: 'Username', value: member.toString() },\n                    { name: 'Role Name', value: nowRole.name },\n                ];\n                Util.sendLog(sendLogData, colUser);\n            }\n\n            if (nowRole.name === 'Vashta-Owner') {\n                Data.getRecords(guild, 'members', { user_id: member.id, buyer: 1 }).then((results) => {\n                    if (results.length > 0) {\n                        Data.addRecord(guild, 'members', { user_id: member.id, buyer: 0 });\n                        Util.logc('BuyerAdd1', `Removed ${Util.getFullName(member)} as a buyer in MySQL DB`);\n                    }\n                });\n            }\n\n            Events.emit(guild, 'UserRoleRemove', member, nowRole);\n        });\n    }\n\n    if (previousNick !== nowNick) {\n        if (member.id === '280579952263430145' && nowNick !== '<- mentally challenged') member.setNickname('<- mentally challenged');\n        Events.emit(guild, 'UserNicknameUpdate', member, previousNick, nowNick);\n\n        const sendLogData = [\n            'Nickname Updated',\n            guild,\n            member,\n            { name: 'Username', value: member.toString() },\n            { name: 'Old Nickname', value: previousNick },\n            { name: 'New Nickname', value: nowNick },\n        ];\n        Util.sendLog(sendLogData, colUser);\n    }\n});\n\n/* client.on('userUpdate', (oldUser, user) => {\n    const oldUsername = oldUser.username;\n    const newUsername = user.username;\n\n    if (oldUsername !== newUsername) {\n        Events.emit(guild, 'UserNicknameUpdate', member, previousNick, nowNick);\n\n        const sendLogData = [\n            'Username Updated',\n            guild,\n            member,\n            { name: 'Username', value: member.toString() },\n            { name: 'Old Nickname', value: previousNick },\n            { name: 'New Nickname', value: nowNick },\n        ];\n        Util.sendLog(sendLogData, colUser);\n    }\n}); */\n\nclient.on('messageUpdate', (oldMsgObj, newMsgObj) => {\n    if (newMsgObj == null) return;\n    const channel = newMsgObj.channel;\n    if (channel.name === 'vaebot-log') return;\n    const guild = newMsgObj.guild;\n    const member = newMsgObj.member;\n    const author = newMsgObj.author;\n    const content = newMsgObj.content;\n    const contentLower = content.toLowerCase();\n    // const isStaff = author.id == vaebId;\n    // const msgId = newMsgObj.id;\n\n    const oldContent = oldMsgObj.content;\n\n    for (let i = 0; i < exports.blockedWords.length; i++) {\n        if (contentLower.includes(exports.blockedWords[i].toLowerCase())) {\n            newMsgObj.delete();\n            return;\n        }\n    }\n\n    if (exports.runFuncs.length > 0) {\n        for (let i = 0; i < exports.runFuncs.length; i++) {\n            exports.runFuncs[i](newMsgObj, member, channel, guild, true);\n        }\n    }\n\n    Events.emit(guild, 'MessageUpdate', member, channel, oldContent, content);\n\n    if (oldContent !== content) {\n        const sendLogData = [\n            'Message Updated',\n            guild,\n            author,\n            { name: 'Username', value: Util.resolveMention(author) },\n            { name: 'Channel Name', value: channel.toString() },\n            { name: 'Old Message', value: oldContent },\n            { name: 'New Message', value: content },\n        ];\n        Util.sendLog(sendLogData, colMessage);\n    }\n});\n\nexports.lockChannel = null;\n\nexports.calmSpeed = 7000;\nexports.slowChat = {};\nexports.raidMode = {};\nexports.slowInterval = {};\nexports.chatQueue = {};\nexports.chatNext = {};\n\nclient.on('voiceStateUpdate', (oldMember, member) => {\n    const oldChannel = oldMember.voiceChannel; // May be null\n    const newChannel = member.voiceChannel; // May be null\n\n    const oldChannelId = oldChannel ? oldChannel.id : null;\n    const newChannelId = newChannel ? newChannel.id : null;\n\n    // const guild = member.guild;\n\n    if (member.id === selfId) {\n        if (member.serverMute) {\n            member.setMute(false);\n            Util.log('Force removed server-mute from bot');\n        }\n\n        if (exports.lockChannel != null && oldChannelId === exports.lockChannel && newChannelId !== exports.lockChannel) {\n            Util.log('Force re-joined locked channel');\n            oldChannel.join();\n        }\n    }\n});\n\n/*\n\nAudit log types\n\nconst Actions = {\n  ALL: null,\n  GUILD_UPDATE: 1,\n  CHANNEL_CREATE: 10,\n  CHANNEL_UPDATE: 11,\n  CHANNEL_DELETE: 12,\n  CHANNEL_OVERWRITE_CREATE: 13,\n  CHANNEL_OVERWRITE_UPDATE: 14,\n  CHANNEL_OVERWRITE_DELETE: 15,\n  MEMBER_KICK: 20,\n  MEMBER_PRUNE: 21,\n  MEMBER_BAN_ADD: 22,\n  MEMBER_BAN_REMOVE: 23,\n  MEMBER_UPDATE: 24,\n  MEMBER_ROLE_UPDATE: 25,\n  ROLE_CREATE: 30,\n  ROLE_UPDATE: 31,\n  ROLE_DELETE: 32,\n  INVITE_CREATE: 40,\n  INVITE_UPDATE: 41,\n  INVITE_DELETE: 42,\n  WEBHOOK_CREATE: 50,\n  WEBHOOK_UPDATE: 51,\n  WEBHOOK_DELETE: 52,\n  EMOJI_CREATE: 60,\n  EMOJI_UPDATE: 61,\n  EMOJI_DELETE: 62,\n  MESSAGE_DELETE: 72,\n};\n\n*/\n\n/* function chooseRelevantEntry(entries, options) {\n    if (options.action == null || options.time == null) {\n        Util.log(options);\n        Util.log('Options did not contain necessary properties');\n        return undefined;\n    }\n\n    const strongest = [null, null];\n\n    entries.forEach((entry) => {\n        if (entry.action !== options.action || (options.target != null && entry.target.id !== options.target.id)) return;\n\n        const timeScore = -Math.abs(options.time - entry.createdTimestamp);\n\n        if (strongest[0] == null || timeScore > strongest[0]) {\n            strongest[0] = timeScore;\n            strongest[1] = entry;\n        }\n    });\n\n    return strongest[1];\n} */\n\nclient.on('messageDelete', (msgObj) => {\n    if (msgObj == null) return;\n    const channel = msgObj.channel;\n    const guild = msgObj.guild;\n    const member = msgObj.member;\n    const author = msgObj.author;\n    const content = msgObj.content;\n\n    // const eventTime = +new Date();\n\n    // const evTime = +new Date();\n\n    // const contentLower = content.toLowerCase();\n    // const isStaff = author.id == vaebId;\n    // const msgId = msgObj.id;\n\n    // if (author.id === vaebId) return;\n\n    Events.emit(guild, 'MessageDelete', member, channel, content);\n\n    if (guild != null) {\n        const attachmentLinks = [];\n        msgObj.attachments.forEach(obj => attachmentLinks.push(obj.url));\n\n        // Util.getAuditLog(guild, 'MESSAGE_DELETE', { target: author }).then((auditEntry) => { // WILL FIX LATER\n        //     auditEntry = auditEntry || {};\n        //     const executor = auditEntry.executor;\n        //     // const sinceAuditLog = executor ? eventTime - auditEntry.createdTimestamp : 0;\n\n        //     // if (executor) Util.log(`[MESSAGE_DELETE] Elapsed since audit log: ${sinceAuditLog}`);\n\n        //     const sendLogData = [\n        //         'Message Deleted',\n        //         guild,\n        //         author,\n        //         { name: 'User', value: Util.resolveMention(author) },\n        //         executor ? { name: 'Possible Moderator', value: Util.resolveMention(executor) } : {},\n        //         { name: 'Channel Name', value: channel.toString() },\n        //         { name: 'Message', value: content },\n        //         { name: 'Attachments', value: attachmentLinks.join('\\n') },\n        //     ];\n        //     Util.sendLog(sendLogData, colMessage);\n        // });\n\n        const sendLogData = [\n            'Message Deleted',\n            guild,\n            author,\n            { name: 'User', value: Util.resolveMention(author) },\n            { name: 'Channel Name', value: channel.toString() },\n            { name: 'Message', value: content },\n            { name: 'Attachments', value: attachmentLinks.join('\\n') },\n        ];\n        Util.sendLog(sendLogData, colMessage);\n\n        /* setTimeout(() => {\n            guild.fetchAuditLogs({\n                // user: member,\n                type: 72,\n            })\n            .then((logs) => {\n                Util.log('[MD] Got audit log data');\n                const entries = logs.entries;\n\n                const entry = chooseRelevantEntry(entries, {\n                    time: evTime,\n                    target: author,\n                    action: 'MESSAGE_DELETE',\n                });\n\n                Util.log(entry);\n\n                Util.log(entry.executor.toString());\n                Util.log(entry.target.toString());\n\n                const sendLogData = [\n                    'Message Deleted',\n                    guild,\n                    author,\n                    { name: 'Username', value: author.toString() },\n                    { name: 'Moderator', value: entry.executor.toString() },\n                    { name: 'Channel Name', value: channel.toString() },\n                    { name: 'Message', value: content },\n                ];\n                Util.sendLog(sendLogData, colMessage);\n            })\n            .catch((error) => {\n                Util.log(error);\n                Util.log('[MD] Failed to get audit log data');\n            });\n        }, 5000); */\n    }\n});\n\nconst messageStamps = {};\nconst userStatus = {};\nconst lastWarn = {};\nconst checkMessages = 5; // (n)\nconst warnGrad = 11.5; // Higher = More Spam (Messages per Second) | 10 = 1 message per second\nconst sameGrad = 4;\nconst muteGrad = 8.5; // 9\nconst waitTime = 5.5;\nconst endAlert = 40;\n\n/* const replaceAll = function (str, search, replacement) {\n    return str.split(search).join(replacement);\n};\nlet contentLower = 'lol <qe23> tege <> <e321z> dz';\ncontentLower = contentLower.replace(/<[^ ]*?[:#@][^ ]*?>/gm, '');\n// contentLower = replaceAll(contentLower, ' ', '');\nUtil.log(contentLower); */\n\n// exports.runFuncs.push((msgObj, speaker, channel, guild) => { // More sensitive\n//     if (guild == null || msgObj == null || speaker == null || speaker.user.bot === true || speaker.id === vaebId) return;\n\n//     let contentLower = msgObj.content.toLowerCase();\n//     contentLower = contentLower.replace(/<[^ ]*?[:#@][^ ]*?>/gm, '');\n//     contentLower = Util.replaceAll(contentLower, ' ', '');\n//     contentLower = Util.replaceAll(contentLower, 'one', '1');\n//     contentLower = Util.replaceAll(contentLower, 'won', '1');\n//     contentLower = Util.replaceAll(contentLower, 'uno', '1');\n//     contentLower = Util.replaceAll(contentLower, 'una', '1');\n//     contentLower = Util.replaceAll(contentLower, 'two', '2');\n//     contentLower = Util.replaceAll(contentLower, 'dose', '2');\n//     contentLower = Util.replaceAll(contentLower, 'dos', '2');\n//     contentLower = Util.replaceAll(contentLower, 'too', '2');\n//     contentLower = Util.replaceAll(contentLower, 'to', '2');\n//     contentLower = Util.replaceAll(contentLower, 'three', '3');\n//     contentLower = Util.replaceAll(contentLower, 'tres', '3');\n//     contentLower = Util.replaceAll(contentLower, 'free', '3');\n\n//     let triggered = false;\n\n//     if (contentLower === '3') {\n//         triggered = true;\n//     } else {\n//         // const trigger = [/11./g, /12[^8]/g, /13./g, /21./g, /22./g, /23./g, /31./g, /32[^h]/g, /33./g, /muteme/g, /onet.?o/g, /threet.?o/g];\n//         // const trigger = [/[123][123][123]/g, /muteme/g];\n//         const trigger = [/[123][^\\d]?[^\\d]?[123][^\\d]?[^\\d]?[123]/g, /[123][123]\\d/g, /muteme/g];\n//         for (let i = 0; i < trigger.length; i++) {\n//             if (trigger[i].test(contentLower)) {\n//                 triggered = true;\n//                 break;\n//             }\n//         }\n//     }\n\n//     if (triggered) {\n//         Admin.addMute(guild, channel, speaker, 'System', { 'reason': 'Muted Themself' });\n//     }\n// });\n\n/* exports.runFuncs.push((msgObj, speaker, channel, guild) => {\n    if (guild == null || msgObj == null || speaker == null || speaker.user.bot === true || speaker.id === vaebId || speaker.id === guild.owner.id) return;\n\n    let contentLower = msgObj.content.toLowerCase();\n    contentLower = contentLower.replace(/<[^ ]*?[:#@][^ ]*?>/gm, '');\n    contentLower = Util.replaceAll(contentLower, ' ', '');\n    contentLower = Util.replaceAll(contentLower, 'one', '1');\n    contentLower = Util.replaceAll(contentLower, 'won', '1');\n    contentLower = Util.replaceAll(contentLower, 'uno', '1');\n    contentLower = Util.replaceAll(contentLower, 'una', '1');\n    contentLower = Util.replaceAll(contentLower, 'two', '2');\n    contentLower = Util.replaceAll(contentLower, 'dose', '2');\n    contentLower = Util.replaceAll(contentLower, 'dos', '2');\n    contentLower = Util.replaceAll(contentLower, 'too', '2');\n    contentLower = Util.replaceAll(contentLower, 'to', '2');\n    contentLower = Util.replaceAll(contentLower, 'three', '3');\n    contentLower = Util.replaceAll(contentLower, 'tres', '3');\n    contentLower = Util.replaceAll(contentLower, 'free', '3');\n\n    let triggered = false;\n\n    const trigger = [/1\\W*2\\W*3\\d/g];\n\n    for (let i = 0; i < trigger.length; i++) {\n        if (trigger[i].test(contentLower)) {\n            triggered = true;\n            break;\n        }\n    }\n\n    if (triggered) {\n        Admin.addBan(guild, channel, speaker, 'System', { time: null, reason: 'Banned Themself', temp: true });\n    }\n}); */\n\n// exports.runFuncs.push((msgObj, speaker, channel, guild) => {\n//     if (guild == null || msgObj == null || speaker == null || speaker.user.bot === true || speaker.id === vaebId) return;\n\n//     let contentLower = msgObj.content.toLowerCase();\n//     contentLower = contentLower.replace(/\\s/g, '');\n//     contentLower = contentLower.replace(/which/g, 'what');\n//     contentLower = contentLower.replace(/great/g, 'best');\n//     contentLower = contentLower.replace(/finest/g, 'best');\n//     contentLower = contentLower.replace(/perfect/g, 'best');\n//     contentLower = contentLower.replace(/top/g, 'best');\n//     contentLower = contentLower.replace(/hack/g, 'exploit');\n//     contentLower = contentLower.replace(/h\\Sx/g, 'exploit');\n//     contentLower = contentLower.replace(/le?v\\S?l(?:\\d|s|f)/g, 'exploit');\n\n//     let triggered = 0;\n\n//     const trigger = [/wh?[au]t/g, /b\\S?st/g, /explo\\S?t/g];\n//     for (let i = 0; i < trigger.length; i++) {\n//         if (trigger[i].test(contentLower)) triggered++;\n//     }\n\n//     if (triggered == trigger.length) {\n//         Admin.addMute(guild, channel, speaker, 'System', { time: 1800000, reason: '[Auto-Mute] Asking stupid questions' });\n//     }\n// });\n\nexports.runFuncs.push((msgObj, speaker, channel, guild) => {\n    if (guild == null || msgObj == null || speaker == null || speaker.user.bot === true) return;\n\n    let contentLower = msgObj.content.toLowerCase();\n    // contentLower = contentLower.replace(/\\s/g, '');\n    contentLower = contentLower.replace(/w[au@]t/g, 'what');\n    contentLower = contentLower.replace(/h[o0]w/g, 'what');\n    contentLower = contentLower.replace(/my/g, 'what');\n    contentLower = contentLower.replace(/m[a@]h/g, 'what');\n    contentLower = contentLower.replace(/wh[e3]r[e3]/g, 'what');\n    contentLower = contentLower.replace(/f[i1]nd/g, 'what');\n    contentLower = contentLower.replace(/see/g, 'what');\n    contentLower = contentLower.replace(/ c /g, 'what');\n    contentLower = contentLower.replace(/ple{1,2}a?[sz]e/g, 'what');\n    contentLower = contentLower.replace(/pl[eoyi]?[szx]/g, 'what');\n\n    let triggered = 0;\n\n    const trigger = [/d[eouyi]?s?[ck]{1,2}s?w?[ouey]r{0,2}[dt ]/g, / id|id /g, /what/g];\n    for (let i = 0; i < trigger.length; i++) {\n        if (trigger[i].test(contentLower)) triggered++;\n    }\n\n    if (triggered == trigger.length) {\n        Util.sendDescEmbed(channel, Util.getMostName(speaker), `Your Discord ID: ${speaker.id}`, null, null, colGreen);\n    }\n});\n\nindex.bannedLetters = [];\n// index.bannedLetters = ['f', '𝓕', 'ғ', 'ƒ', '🇫', 'ꎇ', '₣', '𝐅', '🄵', '🅵', '𝔽', 'ｆ', 'ꜰ', 'ꊰ', '𝐟', '𝖋', 'Ⓕ', 'ʄ', '𝓯', '𝕗', 'Ŧ', '下', '𝙁', '千', '⒡', 'ɟ', '℉', 'ｷ', '𝔣', '𝐹', 'Ⅎ', 'ꟻ', 'ᶠ', '𝙵', 'ℱ', '𝔉', '𝗳', 'Ֆ', 'φ', 'এ', 'ফ', 'ᶂ', 'ᵮ', 'Ꝭ', 'ꞧ', '🅕', 'ꞙ', 'ꬵ', 'ꝭ', 'ꝷ', 'ﬀ', 'ꭍ', 'ﬁ', 'Ḟ', 'דּ', '~~r~~', '~~t~~'];\n\n// index.bannedLetters.push('ᖴ');\n\n// index.runFuncs.push((msgObj, speaker, channel, guild) => {\n//     if (guild == null || msgObj == null || speaker == null || speaker.user.bot === true || guild.id !== '477270527535480834' || Util.hasRoleName(speaker, 'owner')) return;\n\n//     const contentLower = msgObj.content.toLowerCase();\n\n//     for (let i = 0; i < index.bannedLetters.length; i++) {\n//         if (contentLower.includes(index.bannedLetters[i].toLowerCase()) || /~~.*[^\\x00-\\x7F].*~~/.test(contentLower)) {\n//             msgObj.delete()\n//                 .then(() => {\n//                     // Util.print(speaker.user, 'Notice: Your message has been deleted because the letter `F` is now banned.');\n//                 })\n//                 .catch(console.error);\n//             Util.print(channel, `${speaker} Your message has been deleted because the letter \\`F\\` is now banned.`);\n//             break;\n//         }\n//     }\n// });\n\n// index.runFuncs.push((msgObj, speaker, channel, guild) => {\n//     if (guild == null || msgObj == null || speaker == null || speaker.user.bot === true || guild.id !== '477270527535480834' || Util.hasRoleName(speaker, 'owner')) return;\n\n//     const contentLower = msgObj.content.toLowerCase();\n\n//     for (let i = 0; i < index.bannedLetters.length; i++) {\n//         if (/[^\\u2000-\\u206F\\u2E00-\\u2E7F\\\\'!\"#$%&()*+,\\-.\\/:;<=>?@\\[\\]^_`{|}~vaeb\\s0123456789]/i.test(contentLower)) {\n//             msgObj.delete()\n//                 .catch(console.error);\n//             Util.print(channel, `${speaker} Your message has been deleted because all letters besides \\`V\\`, \\`A\\`, \\`E\\` and \\`B\\` are now banned.`);\n//             break;\n//         }\n//     }\n// });\n\n// client.on('guildMemberUpdate', (oldMember, member) => {\n//     if (member.guild.id !== '477270527535480834') return;\n\n//     const nickLower = member.nickname ? member.nickname.toLowerCase() : '';\n//     const userLower = member.user.username;\n\n//     for (let i = 0; i < index.bannedLetters.length; i++) {\n//         if (nickLower.includes(index.bannedLetters[i].toLowerCase()) || (nickLower === '' && userLower.includes(index.bannedLetters[i].toLowerCase()))) {\n//             member.setNickname('nope').catch(console.error);\n//             break;\n//         }\n//     }\n// });\n\nindex.runFuncs.push((msgObj, speaker, channel, guild) => {\n    if (guild == null || msgObj == null || speaker == null || speaker.user.bot === true) return;\n\n    const content = msgObj.content;\n    const matches = /\\b(?<!com\\/)r\\/(\\w+)/g.exec(content);\n\n    if (matches && matches.length) {\n        Util.print(channel, `https://www.reddit.com/r/${matches[1]}`);\n    }\n});\n\nexports.runFuncs.push((msgObj, speaker, channel, guild) => {\n    if (guild == null || msgObj == null || speaker == null || speaker.user.bot === true) return;\n\n    let contentLower = msgObj.content.toLowerCase();\n    // contentLower = contentLower.replace(/\\s/g, '');\n    contentLower = contentLower.replace(/w[au@]t/g, 'what');\n    contentLower = contentLower.replace(/h[o0]w/g, 'what');\n    contentLower = contentLower.replace(/my/g, 'what');\n    contentLower = contentLower.replace(/m[a@]h/g, 'what');\n    contentLower = contentLower.replace(/wh[e3]r[e3]/g, 'what');\n    contentLower = contentLower.replace(/f[i1]nd/g, 'what');\n    contentLower = contentLower.replace(/see/g, 'what');\n    contentLower = contentLower.replace(/ c /g, 'what');\n    contentLower = contentLower.replace(/ple{1,2}a?[sz]e/g, 'what');\n    contentLower = contentLower.replace(/pl[eoyi]?[szx]/g, 'what');\n\n    let triggered = 0;\n\n    const trigger = [/[i1]n{1,2}v{1,2}[i1]t{1,2}[e3]|l[i1]nk/g, / v[aeyui]{1,3}l|what|d[eouyi]?s?[ck]{1,2}s?w?[ouey]r{0,2}[dt ]/g];\n    for (let i = 0; i < trigger.length; i++) {\n        if (trigger[i].test(contentLower)) triggered++; // Mother fuckin' triggered\n    }\n\n    if (triggered == trigger.length) {\n        // Util.sendDescEmbed(channel, Util.getMostName(speaker), 'The invite link for Veil Discord is https://discord.gg/aVvcjDS', null, null, colGreen);\n    } else {\n        if (/[i1]n{1,2}v{1,2}[i1]t{1,2}[e3]\\s*l[i1]nk/g.test(contentLower)) {\n            // Util.sendDescEmbed(channel, Util.getMostName(speaker), 'The invite link for Veil Discord is https://discord.gg/aVvcjDS', null, null, colGreen);\n        }\n    }\n});\n\nexports.runFuncs.push((msgObj, speaker, channel, guild, isEdit) => {\n    if (\n        isEdit ||\n        guild == null ||\n        guild.id != '477270527535480834' ||\n        msgObj == null ||\n        speaker == null ||\n        speaker.user.bot === true ||\n        speaker.id === guild.owner.id\n    ) {\n        return;\n    }\n\n    let contentLower = msgObj.content.toLowerCase().trim();\n\n    if (contentLower == '!buy') return;\n\n    // contentLower = contentLower.replace(/\\s/g, '');\n    contentLower = contentLower.replace(/\\bthe /g, '');\n    contentLower = contentLower.replace(/\\bit\\b/g, 'veil');\n    contentLower = contentLower.replace(/\\bthis\\b/g, 'veil');\n    contentLower = contentLower.replace(/\\bvel\\b/g, 'veil');\n    contentLower = contentLower.replace(/\\bveli/g, 'veil');\n    contentLower = contentLower.replace(/\\bv[ie][ie]l/g, 'veil');\n    contentLower = contentLower.replace(/hack\\b/g, 'veil');\n    contentLower = contentLower.replace(/\\bh\\Sx\\b/g, 'veil');\n    contentLower = contentLower.replace(/le?v\\S?l.?(?:\\d|s|f)/g, 'veil');\n    contentLower = contentLower.replace(/explo\\S?t\\b/g, 'veil');\n    contentLower = contentLower.replace(/\\bpay\\b/g, 'buy');\n    // contentLower = contentLower.replace(/get/g, 'buy');\n    contentLower = contentLower.replace(/get veil/g, 'buy');\n    contentLower = contentLower.replace(/purchas.?/g, 'buy');\n\n    let triggered = false;\n\n    if (/\\s/g.test(contentLower) && contentLower.substr(contentLower.length - 3, 3) == 'buy') {\n        triggered = true;\n    }\n\n    if (!triggered) {\n        let triggeredNum = 0;\n\n        const trigger = [/buy\\b/g, /veil/g];\n        for (let i = 0; i < trigger.length; i++) {\n            if (trigger[i].test(contentLower)) triggeredNum++;\n        }\n\n        if (triggeredNum == trigger.length) triggered = true;\n    }\n\n    if (triggered) {\n        // Util.sendDescEmbed(channel, 'How To Buy', 'To buy veil send a message saying !buy', null, null, colGreen);\n    }\n});\n\nfunction antiScam(msgObj, contentLower, speaker, channel, guild, isEdit, original) {\n    if (speaker == null || msgObj == null || speaker.user.bot === true || speaker.id === vaebId) return false;\n\n    if (original) {\n        contentLower = contentLower.replace(/[\\n\\r]/g, ' ');\n        contentLower = contentLower.replace(/0/g, 'o');\n        contentLower = contentLower.replace(/1/g, 'i');\n        contentLower = contentLower.replace(/3/g, 'e');\n        contentLower = contentLower.replace(/4/g, 'a');\n        contentLower = contentLower.replace(/5/g, 's');\n        contentLower = contentLower.replace(/8/g, 'b');\n        contentLower = contentLower.replace(/@/g, 'a');\n\n        if (antiScam(msgObj, Util.reverse(contentLower), speaker, channel, guild, isEdit, false)) return true;\n    }\n\n    contentLower = contentLower.replace(/https?/g, '');\n    contentLower = contentLower.replace(/www\\./g, '');\n    contentLower = contentLower.replace(/[^a-z .]+/g, '');\n    contentLower = contentLower.replace(/dot/g, '.');\n    // contentLower = contentLower.replace(/(.)\\1+/g, '$1');\n    contentLower = contentLower.replace(/ +/g, '');\n\n    if (original) {\n        if (antiScam(msgObj, Util.reverse(contentLower), speaker, channel, guild, isEdit, false)) return true;\n    }\n\n    if (guild.id == '166601083584643072') Util.logc('blockLink1', contentLower);\n\n    let triggered = false;\n\n    const trigger = [\n        // Change: non letter/dot characters\n        {\n            regex: /steam([^.]+)\\.com/, // steamscam.com\n            allow: [/^(?:powered|community)$/g],\n        },\n        {\n            regex: /[^.]+steam[^.]*\\.com/, // scamsteam.com\n            allow: [],\n        },\n        {\n            regex: /bit\\.ly/, // bit.ly\n            allow: [],\n            /* }, {\n            regex: /goo\\.gl/, // goo.gl\n            allow: [], */\n        },\n        {\n            regex: /tinyurl\\.com/, // tinyurl.com\n            allow: [],\n        },\n    ];\n\n    for (let i = 0; i < trigger.length; i++) {\n        let matches = trigger[i].regex.exec(contentLower);\n        if (!matches) continue;\n        if (guild.id == '166601083584643072') Util.logc('blockLink1', matches);\n        const triggerAllow = trigger[i].allow;\n        for (let j = 0; j < triggerAllow.length; j++) {\n            if (original && triggerAllow[j].test(matches[j + 1])) {\n                matches = null;\n                break;\n            }\n        }\n        if (!matches) continue;\n        triggered = true;\n        break;\n    }\n\n    if (triggered) {\n        Admin.addMute(guild, channel, speaker, 'System', { time: 1800000, reason: '[Anti-Scam] Posting a suspicious link' });\n        return true;\n    }\n\n    return false;\n}\n\nexports.runFuncs.push((msgObj, speaker, channel, guild, isEdit) => {\n    if (guild && speaker && Util.checkStaff(guild, speaker)) return;\n    antiScam(msgObj, msgObj.content.toLowerCase().trim(), speaker, channel, guild, isEdit, true);\n});\n\nexports.runFuncs.push((msgObj, speaker, channel, guild) => {\n    if (\n        speaker == null ||\n        msgObj == null ||\n        speaker.user.bot === true ||\n        speaker.id === vaebId ||\n        speaker.id === guild.owner.id ||\n        Util.checkStaff(guild, speaker)\n    ) {\n        return false;\n    }\n\n    let contentLower = msgObj.content.toLowerCase();\n\n    contentLower = contentLower.replace(/[\\n\\r]/g, ' '); // All newlines become spaces\n    contentLower = contentLower.replace(/0/g, 'o');\n    contentLower = contentLower.replace(/1/g, 'i');\n    contentLower = contentLower.replace(/3/g, 'e');\n    contentLower = contentLower.replace(/4/g, 'a');\n    contentLower = contentLower.replace(/5/g, 's');\n    contentLower = contentLower.replace(/8/g, 'b');\n    contentLower = contentLower.replace(/@/g, 'a');\n    contentLower = contentLower.replace(/https?(?::\\/\\/)?/g, ''); // http(s)// removed\n    contentLower = contentLower.replace(/www\\./g, ''); // www. removed\n    contentLower = contentLower.replace(/[^a-z ./]+/g, ''); // Any characters that aren't letters, spaces or dots are removed\n    contentLower = contentLower.replace(/dot/g, '.');\n    // contentLower = contentLower.replace(/(.)\\1+/g, '$1');\n    // contentLower = contentLower.replace(/ +/g, ''); // All spaces removed\n\n    let triggered = false;\n\n    const trigger = [\n        // Will only contain: Letters, spaces, forward-slashes and dots\n        {\n            regex: /d *i *(?:s *)?[ck] *(?:[^ ] *)?o *r *d *(?:\\. *)?(?:g *g *|i *o *|m *e *|c *o *m *)\\/ *([^. /]+)/, // https://discord.gg/XVeAZd6\n            allow: [/^(?:aVvcjDS|7gPhEKv|roblox|bvS5gwY|rrX8bA|9PbETKC)$/gi], // Caps matter but just-in-case\n        },\n    ];\n\n    for (let i = 0; i < trigger.length; i++) {\n        let matches = trigger[i].regex.exec(contentLower);\n        if (!matches) continue;\n        if (guild.id == '166601083584643072') Util.logc('blockLink1', matches);\n        const triggerAllow = trigger[i].allow;\n        for (let j = 0; j < triggerAllow.length; j++) {\n            if (triggerAllow[j].test(matches[j + 1])) {\n                matches = null;\n                break;\n            }\n        }\n        if (!matches) continue;\n        triggered = true;\n        break;\n    }\n\n    if (triggered) {\n        msgObj.delete().catch(console.error);\n        Admin.addMute(guild, channel, speaker, 'System', { reason: '[Auto-Mute] Advertising Discord server' });\n        return true;\n    }\n\n    return false;\n});\n\nconst staffMessages = {};\n\nconst recentMs = 20000; // What's the maximum elapsed time to count a message as recent?\nconst recentMessages = []; // Messages sent in the last recentMs milliseconds\nconst numSimilarForSpam = 3;\nconst spamMessages = []; // Messages detected as spam in recentMessages stay here for limited period of time\n\n// const msgStatus = {}; // Coming soon?\n\n/* let lastTimeout = {\n    timeout: null,\n    stamp: 0,\n}; */\n\nexports.crabRave = {\n    goneGuild: null,\n    goneId: null,\n    goneName: null,\n    interval: null,\n};\n\nclient.on('message', (msgObj) => {\n    const channel = msgObj.channel;\n    if (channel.name === 'vaebot-log') return;\n    const guild = msgObj.guild;\n    let speaker = msgObj.member;\n    let author = msgObj.author;\n    let content = msgObj.content;\n    const authorId = author.id;\n\n    const isRaidMode = guild ? exports.raidMode[guild.id] : false;\n\n    // const presentStamp = +new Date();\n\n    // if (guild.id !== '166601083584643072') return;\n\n    if (content.includes('That command is reserved for Fredboat administration')) {\n        msgObj.delete();\n        return;\n    }\n\n    if (content.substring(content.length - 5) === ' -del' && authorId === vaebId) {\n        msgObj.delete();\n        content = content.substring(0, content.length - 5);\n    }\n\n    let contentLower = content.toLowerCase();\n\n    const isStaff = guild && speaker ? Util.checkStaff(guild, speaker) : authorId === vaebId;\n\n    if (exports.blockedUsers[authorId]) {\n        msgObj.delete();\n        return;\n    }\n\n    if (!isStaff) {\n        for (let i = 0; i < exports.blockedWords.length; i++) {\n            if (contentLower.includes(exports.blockedWords[i].toLowerCase())) {\n                msgObj.delete();\n                return;\n            }\n        }\n    }\n\n    /* if (guild != null) {\n        if (lastTimeout.timeout) clearTimeout(lastTimeout.timeout);\n        if (presentStamp - lastTimeout.stamp > 1000 * 60 * 10) { // Hmmm, 10 minutes?\n            lastTimeout.timeout = setTimeout(notifyOn, 1000 * 60 * 3, channel); // 3 minutes\n            lastTimeout.stamp = presentStamp;\n        }\n    } */\n\n    if (guild != null && contentLower.substr(0, 5) === 'sudo ' && authorId === vaebId) {\n        author = Util.getUserById(selfId);\n        speaker = Util.getMemberById(selfId, guild);\n        content = content.substring(5);\n        contentLower = content.toLowerCase();\n    }\n\n    if (exports.runFuncs.length > 0) {\n        for (let i = 0; i < exports.runFuncs.length; i++) {\n            exports.runFuncs[i](msgObj, speaker, channel, guild, false);\n        }\n    }\n\n    if (guild != null && speaker != null) {\n        if (!has.call(staffMessages, speaker.id)) staffMessages[speaker.id] = {};\n    }\n\n    if (\n        guild != null &&\n        channel.name.toLowerCase() !== 'bot-commands' &&\n        author.bot === false &&\n        content.length > 0 &&\n        !contentLower.startsWith('$') &&\n        !contentLower.startsWith('|move') &&\n        !contentLower.startsWith('🦀') &&\n        author.id !== guild.owner.id &&\n        author.id !== vaebId &&\n        !Admin.checkMuted(guild, author.id)\n    ) {\n        const contentSpam = content.replace(/\\|\\|(.+?)\\|\\|/g, (match, p1) => p1);\n        const contentSpamLower = contentSpam.toLowerCase();\n\n        // If they are eligible for anti-spam checks\n        if (!has.call(userStatus, authorId)) userStatus[authorId] = 0; // Initialise user status\n        if (!has.call(messageStamps, authorId)) messageStamps[authorId] = []; // Initialise user message storage\n        const nowStamps = messageStamps[authorId]; // Get user message storage\n        const stamp = +new Date(); // Get current timestamp\n        nowStamps.unshift({ stamp, message: contentSpamLower }); // Add current message data to the start ([0]) of the message storage\n\n        if (\n            !Admin.checkMuted(guild, author.id) &&\n            contentSpamLower.length >= 5 &&\n            contentSpamLower.substr(0, 1) != ';' &&\n            contentSpamLower != 'ping' &&\n            contentSpamLower != '!buy'\n        ) {\n            // >= 5\n            let numSimilar = 0;\n            const prevSpam = spamMessages.find(spamMsg => Util.similarStringsStrict(contentSpam, spamMsg.msg));\n            for (let i = recentMessages.length - 1; i >= 0; i--) {\n                const recentMsg = recentMessages[i];\n                if (Util.similarStringsStrict(contentSpam, recentMsg.msg)) {\n                    numSimilar++;\n                } else if (stamp - recentMsg.stamp > recentMs) {\n                    recentMessages.splice(i, 1);\n                }\n            }\n            const nowCheck = numSimilarForSpam;\n            if ((numSimilar >= nowCheck || prevSpam) && !contentSpamLower.includes('welcome')) {\n                // Is spam\n                if (prevSpam) {\n                    // If message is similar to one previously detected as spam\n                    prevSpam.initWarn = false;\n                    prevSpam.numSince = 0;\n                    Admin.addMute(guild, channel, speaker, 'System', { reason: '[Auto-Mute] Message-Specific Spamming' }); // Mute the user\n                } else {\n                    // If message was detected as spam based on similar recent messages\n                    spamMessages.push({ msg: contentSpam, stamp, numSince: 0, initWarn: true }); // At some point remove spam messages with really old stamp?\n                    Util.print(\n                        channel,\n                        `**Warning:** If users continue to send variants of \"${contentSpam}\", it will be treated as spam resulting in mutes`,\n                    ); // Warn the user\n                    // Maybe put all the users who've spammed the message on a warning?\n                }\n            } else {\n                for (let i = spamMessages.length - 1; i >= 0; i--) {\n                    // Remove old spam message checks\n                    const oldSpam = spamMessages[i];\n                    oldSpam.numSince++;\n                    if (oldSpam.numSince >= 20) spamMessages.splice(i, 1); // Too old -> Remove\n                }\n            }\n        }\n\n        recentMessages.push({ msg: contentSpam, stamp });\n\n        if (!Admin.checkMuted(guild, author.id) && Util.isSpam(contentSpam)) {\n            // Check if the message contains single-message-spam\n            if (userStatus[authorId] == 0) {\n                // If the user has not yet been warned recently\n                Util.logc('AntiSpam1', `[4] ${Util.getName(speaker)} warned`);\n                Util.print(channel, speaker.toString(), 'Warning: If you continue to spam you will be auto-muted'); // Warn the user\n                lastWarn[authorId] = stamp; // Record time of warning in case they get another one soon\n                userStatus[authorId] = 2; // Set status to \"monitoring for spam on high alert\"\n            } else {\n                // If the user has already had a warning\n                Util.logc('AntiSpam1', `[4] ${Util.getName(speaker)} muted`);\n                Admin.addMute(guild, channel, speaker, 'System', { reason: '[Auto-Mute] Spamming' }); // Mute the user\n                userStatus[authorId] = 0; // Reset their status to the default\n            }\n        }\n        if (!Admin.checkMuted(guild, author.id) && userStatus[authorId] !== 1) {\n            // If the user has not been muted in the single-message spam check and they are not currently being timeout-analysed for spam\n            if (nowStamps.length > checkMessages) {\n                // If the user has more than the number of messages to check stored\n                nowStamps.splice(checkMessages, nowStamps.length - checkMessages); // Remove the oldest messages\n            }\n            if (nowStamps.length >= checkMessages) {\n                // Continue if they have enough messages recorded to check for multi-message spam\n                const oldStamp = nowStamps[checkMessages - 1].stamp; // Get the oldest message's timestamp\n                const elapsed = (stamp - oldStamp) / 1000; // Get the time elapsed since then in seconds\n                const grad1 = (checkMessages / elapsed) * 10; // Calculate the gradient (velocity) at which they sent messages\n                let checkGrad1 = sameGrad; // Initialise the comparison gradient as the sensitive gradient for if all messages are the same\n                const latestMsg = nowStamps[0].message; // Get the latest (current) message's content\n                for (let i = 0; i < checkMessages; i++) {\n                    // Go through all the recorded messages\n                    if (!Util.similarStrings(nowStamps[i].message, latestMsg)) {\n                        // If all the content is *not* the same\n                        checkGrad1 = warnGrad; // Use the normal comparison gradient\n                        break;\n                    }\n                }\n                // Util.log(\"User: \" + Util.getName(speaker) + \" | Elapsed Since \" + checkMessages + \" Messages: \" + elapsed + \" | Gradient1: \" + grad1);\n                if (grad1 >= checkGrad1) {\n                    // If the current gradient (velocity) is higher than the comparison gradient\n                    if (userStatus[authorId] === 0) {\n                        // If the user hasn't been warned recently\n                        Util.logc('AntiSpam1', `[1] ${Util.getName(speaker)} warned, gradient ${grad1} larger than ${checkGrad1}`);\n                        userStatus[authorId] = 1; // Set status to \"analysing their future messages\"\n                        Util.print(channel, speaker.toString(), 'Warning: If you continue to spam you will be auto-muted');\n                        setTimeout(() => {\n                            // Set a timeout for if they haven't seen the warning message yet (not using await due to ratelimtis in raids)\n                            const lastStamp = nowStamps[0].stamp; // Get the timestamp of the latest message\n                            setTimeout(() => {\n                                // Continue storing all their messages for monitoring until this timer ends\n                                if (Admin.checkMuted(guild, author.id)) {\n                                    // If they've been muted during this time cancel the analysis here\n                                    Util.logc('AntiSpam1', `[2] ${Util.getName(speaker)} is already muted`);\n                                    userStatus[authorId] = 0; // Reset status to default\n                                    return; // Cancel\n                                }\n                                let numNew = 0; // Declare var for counting new messages\n                                let checkGrad2 = sameGrad; // Initialise the comparison gradient as the sensitive gradient for if all messages are the same\n                                const newStamp = +new Date(); // Get the new current timestamp\n                                const latestMsg2 = nowStamps[0].message; // Get the most recent message's content\n                                // var origStamp2;\n                                for (let i = 0; i < nowStamps.length; i++) {\n                                    // For each new message from latest\n                                    const curStamp = nowStamps[i];\n                                    const isFinal = curStamp.stamp === lastStamp; // Is it the oldest (excluded) message\n                                    if (isFinal && stamp === lastStamp) break; // If so and the oldest message was also the original message (no new messages) then break\n                                    numNew++; // Increase total message count\n                                    // origStamp2 = curStamp.stamp;\n                                    if (!Util.similarStrings(curStamp.message, latestMsg2)) checkGrad2 = muteGrad; // If messages are not the same use nkrmal gradient\n                                    if (isFinal) break; // If it was the final message to check then break\n                                }\n                                if (numNew == 0) {\n                                    // If they haven't sent any new messages\n                                    // Util.logc('AntiSpam1', `[2_] ${Util.getName(speaker)} stopped spamming and was put on alert`);\n                                    lastWarn[authorId] = newStamp; // Store the stamp for their last warning\n                                    userStatus[authorId] = 2; // Set status to monitoring on high alert\n                                    return; // Cancel\n                                }\n                                // let numNew2 = 0; // New var for counting messages\n                                // let elapsed2 = 0;\n                                let grad2 = 0;\n                                // var elapsed2 = (newStamp-origStamp2)/1000;\n                                // var grad2 = (numNew/elapsed2)*10;\n                                for (let i = 2; i < numNew; i++) {\n                                    // They must have sent at least 2 messages\n                                    const curStamp = nowStamps[i].stamp; // Get now message time\n                                    const nowElapsed = (newStamp - curStamp) / 1000; // Get time elapsed between the message and now\n                                    const nowGradient = ((i + 1) / nowElapsed) * 10; // Calculate gradient for message sending velocity over the time since this message\n                                    if (nowGradient > grad2) {\n                                        // If now gradient is larger than highest record gradient\n                                        grad2 = nowGradient; // Set gradient as highest recorded\n                                        // elapsed2 = nowElapsed; // Set elapsed as elapsed time between this message and now\n                                        // numNew2 = i + 1; // Set number of new messages as i+1\n                                    }\n                                }\n                                // Util.logc(\n                                //     'AntiSpam1',\n                                //     `[2] User: ${Util.getName(\n                                //         speaker,\n                                //     )} | Messages Since ${elapsed2} Seconds: ${numNew2} | Gradient2: ${grad2}`,\n                                // );\n                                if (grad2 >= checkGrad2) {\n                                    // If gradient (velocity) is higher than checking gradient\n                                    Util.logc(\n                                        'AntiSpam1',\n                                        `[2] ${Util.getName(speaker)} muted, gradient ${grad2} larger than ${checkGrad2}`,\n                                    );\n                                    Admin.addMute(guild, channel, speaker, 'System', { reason: '[Auto-Mute] Spamming' }); // Mute user\n                                    userStatus[authorId] = 0; // Reset monitoring status to normal\n                                } else {\n                                    // Util.logc('AntiSpam1', `[2] ${Util.getName(speaker)} was put on alert`);\n                                    lastWarn[authorId] = newStamp; // Store the stamp for their last warning\n                                    userStatus[authorId] = 2; // Set status to monitoring on high alert\n                                }\n                            }, waitTime * 1000);\n                        }, 350);\n                    } else if (userStatus[authorId] === 2) {\n                        // If the user has already been warned recently\n                        Util.logc('AntiSpam1', `[3] ${Util.getName(speaker)} muted, repeated warns`);\n                        Admin.addMute(guild, channel, speaker, 'System', { reason: '[Auto-Mute] Spamming' }); // Mute the user for multiple warnings in a short period of time\n                        userStatus[authorId] = 0; // Reset user monitoring status to default\n                    }\n                } else if (userStatus[authorId] === 2 && stamp - lastWarn[authorId] > endAlert * 1000) {\n                    // If it's been longer than the necessary monitoring time since their last warning\n                    // Util.logc('AntiSpam1', `[3] ${Util.getName(speaker)} ended their alert`);\n                    userStatus[authorId] = 0; // Deem the user safe and reset their monitoring status to normal\n                }\n            }\n        }\n    }\n\n    if (guild != null) {\n        if (Music.guildQueue[guild.id] == null) Music.guildQueue[guild.id] = [];\n\n        if (Music.guildMusicInfo[guild.id] == null) {\n            Music.guildMusicInfo[guild.id] = {\n                activeSong: null,\n                activeAuthor: null,\n                voteSkips: [],\n                isAuto: false,\n            };\n        }\n    }\n\n    if (guild != null && index.crabRave.goneId != null && guild.id === index.crabRave.goneGuild && author.bot === false) {\n        Util.print(channel, `🦀🦀🦀🦀🦀🦀 ${index.crabRave.goneName} IS GONE 🦀🦀🦀🦀🦀🦀`);\n    }\n\n    if (guild && exports.slowChat[guild.id] && author.bot === false && !isStaff) {\n        const nowTime = +new Date();\n        if (nowTime > exports.chatNext[guild.id]) {\n            exports.chatNext[guild.id] = nowTime + exports.calmSpeed;\n        } else {\n            msgObj.delete().catch(console.error);\n            const intervalNum = exports.calmSpeed / 1000;\n            // var timeUntilSend = (exports.chatNext[guild.id] - nowTime) / 1000;\n            author\n                .send(\n                    `Your message has been deleted. ${\n                        guild.name\n                    } is temporarily in slow mode, meaning everyone must wait ${intervalNum} seconds \n            after the previous message before they can send one.`,\n                )\n                .catch(console.error);\n        }\n        // exports.chatQueue[guild.id].push(msgObj);\n    }\n\n    if (!isRaidMode || isStaff) {\n        Cmds.checkMessage(msgObj, speaker || author, channel, guild, content, contentLower, authorId, isStaff);\n\n        if (author.bot === true) {\n            // RETURN IF BOT\n            return;\n        }\n\n        Events.emit(guild, 'MessageCreate', speaker, channel, msgObj, content);\n\n        // if (contentLower.includes('👀'.toLowerCase())) Util.print(channel, '👀');\n    }\n});\n\n// //////////////////////////////////////////////////////////////////////////////////////////////\n\nUtil.log('-CONNECTING-');\n\nclient.login(Auth.discordToken);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vaebot\",\n  \"version\": \"1.1.0\",\n  \"description\": \"Discord bot for everything from moderation to music.\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"start\": \"node index.js\",\n    \"prod\": \"forever -c \\\"nodemon --exitcrash\\\" index.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/Vaeb/VaeBot.git\"\n  },\n  \"keywords\": [\n    \"Vaeb\",\n    \"VaeBot\",\n    \"Discord\",\n    \"Bot\"\n  ],\n  \"author\": \"Vaeb\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/Vaeb/VaeBot/issues\"\n  },\n  \"homepage\": \"https://github.com/Vaeb/VaeBot#readme\",\n  \"dependencies\": {\n    \"@google-cloud/translate\": \"^4.1.3\",\n    \"bufferutil\": \"^4.0.0\",\n    \"dateformat\": \"^3.0.3\",\n    \"discord.js\": \"^11.4.2\",\n    \"google-translate\": \"^2.3.0\",\n    \"hepburn\": \"^1.1.1\",\n    \"libsodium-wrappers\": \"^0.7.3\",\n    \"mysql\": \"^2.16.0\",\n    \"node-opus\": \"^0.3.0\",\n    \"node-trello\": \"^1.2.1\",\n    \"request\": \"^2.87.0\",\n    \"request-promise-native\": \"^1.0.5\",\n    \"urban\": \"^0.3.1\",\n    \"uws\": \"^99.0.0\",\n    \"youtube-node\": \"^1.3.0\",\n    \"youtube-search\": \"^1.0.10\",\n    \"ytdl-core\": \"^0.15.2\"\n  },\n  \"devDependencies\": {\n    \"eslint\": \"^4.18.2\",\n    \"eslint-config-airbnb\": \"^15.1.0\",\n    \"eslint-config-airbnb-base\": \"^11.3.1\",\n    \"eslint-plugin-import\": \"^2.7.0\",\n    \"eslint-plugin-jsx-a11y\": \"^6.0.2\",\n    \"eslint-plugin-react\": \"^7.1.0\"\n  }\n}\n"
  }
]