Full Code of Vaeb/VaeBot for AI

master 45e140430e41 cached
116 files
474.1 KB
119.3k tokens
54 symbols
1 requests
Download .txt
Showing preview only (505K chars total). Download the full file or copy to clipboard to get everything.
Repository: Vaeb/VaeBot
Branch: master
Commit: 45e140430e41
Files: 116
Total size: 474.1 KB

Directory structure:
gitextract_qb25w_km/

├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── Util.js
├── commands/
│   ├── administrator/
│   │   ├── Actions.js
│   │   ├── AddAction.js
│   │   ├── AddAuto.js
│   │   ├── AddAutoRole.js
│   │   ├── AddRole.js
│   │   ├── Alert.js
│   │   ├── AllInfo.js
│   │   ├── Ban.js
│   │   ├── Calm.js
│   │   ├── CheckPerms.js
│   │   ├── ClearMutes.js
│   │   ├── Events.js
│   │   ├── Fire.js
│   │   ├── FixRoles.js
│   │   ├── GetLinks.js
│   │   ├── HasPerm.js
│   │   ├── InitRoles.js
│   │   ├── Link.js
│   │   ├── Permissions.js
│   │   ├── RaveBan.js
│   │   ├── RemAuto.js
│   │   ├── RemAutoRole.js
│   │   ├── RemRole.js
│   │   ├── RolePerms.js
│   │   ├── Switch.js
│   │   ├── UnCalm.js
│   │   ├── UnLink.js
│   │   └── UnRaveBan.js
│   ├── locked/
│   │   ├── Bother.js
│   │   ├── Change.js
│   │   ├── Commit.js
│   │   ├── Create.js
│   │   ├── DisabR.js
│   │   ├── Echo.js
│   │   ├── Effect.js
│   │   ├── EnabR.js
│   │   ├── Eval.js
│   │   ├── Lua.js
│   │   ├── PSA.js
│   │   ├── Reminder.js
│   │   ├── Set.js
│   │   └── Username.js
│   ├── public/
│   │   ├── AutoPlaylist.js
│   │   ├── AutoRoles.js
│   │   ├── Channels.js
│   │   ├── CloseTicket.js
│   │   ├── Commands.js
│   │   ├── Decrypt.js
│   │   ├── Define.js
│   │   ├── Encrypt.js
│   │   ├── GetTickets.js
│   │   ├── GuildInfo.js
│   │   ├── Image.js
│   │   ├── Info.js
│   │   ├── JTranslate.js
│   │   ├── NewDiscord.js
│   │   ├── NowPlaying.js
│   │   ├── Offenses.js
│   │   ├── Ping.js
│   │   ├── PingPong.js
│   │   ├── Play.js
│   │   ├── PlayAuto.js
│   │   ├── Pop.js
│   │   ├── Power.js
│   │   ├── Queue.js
│   │   ├── Roles.js
│   │   ├── StartAuto.js
│   │   ├── StartQueue.js
│   │   ├── Syntax.js
│   │   ├── Text.js
│   │   ├── Ticket.js
│   │   ├── Toggle.js
│   │   ├── Translate.js
│   │   ├── UpdateOwner.js
│   │   └── VoteSkip.js
│   └── staff/
│       ├── ChangeMute.js
│       ├── Clear.js
│       ├── ClearQueue.js
│       ├── GetBans.js
│       ├── HardKick.js
│       ├── History.js
│       ├── Join.js
│       ├── Kick.js
│       ├── Leave.js
│       ├── Mute.js
│       ├── Mutes.js
│       ├── Nickname.js
│       ├── PlayF.js
│       ├── RaidMode.js
│       ├── RemQueue.js
│       ├── Skip.js
│       ├── Stop.js
│       ├── TempBan.js
│       ├── TempBans.js
│       ├── UnBan.js
│       ├── UnMute.js
│       ├── UnRaidMode.js
│       ├── UndoMute.js
│       └── UserMutes.js
├── core/
│   ├── ManageAdmin.js
│   ├── ManageCommands.js
│   ├── ManageData.js
│   ├── ManageEvents.js
│   ├── ManageMusic.js
│   ├── ManageMusic2.js
│   ├── ManageMutesNew.js
│   └── ManageTrello.js
├── disabled/
│   └── Warn.js
├── index.js
└── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.json
================================================
{
    "env": {
        "node": true
    },
    "extends": "airbnb",
    "globals": {
        "index": true,
	"has": true,
	"selfId": true,
	"vaebId": true,
	"botDir": true,
	"Util": true,
	"Data": true,
	"Trello": true,
	"Admin": true,
	"Music": true,
	"Music2": true,
	"Cmds": true,
	"Events": true,
	"Discord": true,
        "client": true,
        "colAction": true,
	"colUser": true,
	"colMessage": true,
	"colCommand": true,
	"colGreen": true,
        "colBlue": true,
        "cmd": true, "args": true, "msgObj": true, "speaker": true, "channel": true, "guild": true
    },
    "rules": {
        "default-case": "off",
        "eqeqeq": "off",
        "import/no-dynamic-require": "off",
        "indent": ["error", 4],
        "func-names": "off",
        "global-require": "off",
        "jsx-a11y/href-no-hash": "off",
        "max-len": "off",
        "no-bitwise": "off",
        "no-cond-assign": "off",
        "no-console": "off",
        "no-continue": "off",
        "no-control-regex": "off",
        "no-eval": "off",
        "no-irregular-whitespace": "off",
        "no-lonely-if": "off",
        "no-mixed-operators": "off",
        "no-multi-spaces": ["error", {"ignoreEOLComments": true}],
        "no-param-reassign": "off",
        "no-plusplus": "off",
        "no-restricted-syntax": "off",
        "no-underscore-dangle": "off",
        "quote-props": "off"
    }
}


================================================
FILE: .gitattributes
================================================
* text=auto eol=lf


================================================
FILE: .gitignore
================================================
# Packages
node_modules/
yarn.lock

# Log files
*.log

# Data Storage
data/autoroles.json
data/history.json
data/mutes.json
data/playlist.json

# Testing
Auth.js
Test.js

# =========================
# Everything below is .gitignore template
# =========================

# Miscellaneous
.tmp/
.vscode/

# Windows image file caches
Thumbs.db
ehthumbs.db

# Folder config file
Desktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msm
*.msp

# Windows shortcuts
*.lnk

# =========================
# Operating System Files
# =========================

# OSX
# =========================

.DS_Store
.AppleDouble
.LSOverride

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Adam

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# VaeBot - A Multi-Purpose Discord Bot

## Includes
- Full warn/mute based moderation system
- Powerful anti-raid and anti-spam system
- Hierarchy-based permission reasoning
- Complete music system
- A huge array of commands

## Staff-Only Commands
​
**;actions** OR **;guild** actions OR **;all** actions - Output all actions that can be used in **;link**

**;addauto** OR **;adda** OR **;addtoauto** - Adds a song to the music auto-playlist

**;addrole** - Add a role to a user

**;alert** OR **;dm** OR **;announce** - Sends a DM to everyone in the guild with a certain role

**;allinfo** - Get guild, role, channel and permission info in one huge set of messages

**;ban** OR **;banhammer** OR **;permaban** - Ban a user from the guild

**;bans** OR **;getbans** - Get all banned users

**;calm** OR **;calmchat** OR **;slow** OR **;slowchat** - Slows down chat speed

**;changemute** OR **;change** OR **;setmute** OR altermute - Change details of an active mute

**;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

**;clearqueue** - Clears VaeBot's queue of music

**;events** OR **;guild** events OR **;all** events - Output all events that can be used in **;link**

**;getlinks** OR **;links** OR **;triggers** - Output all created links

**;hardkick** OR **;hardeject** OR **;softban** - Kick a user from the guild (extra hard)

**;history** OR **;mutehistory** - Get all users with mute history

**;join** OR **;summon** - Make VaeBot join a voice channel

**;kick** OR **;eject** - Kick a user from the guild

**;leave** OR **;exit** - Make VaeBot leave it's voice channel

**;link** OR **;addlink** OR **;trigger** OR **;event** - Link an event to an action

**;mute** OR **;mutehammer** - Mute a user (in all guild channels) and add the mute to their record

**;mutes** OR **;usermutes** OR **;history** OR **;userhistory** - Get the mute history of a user

**;mutes** OR **;muted** - Get all currently muted users

**;nick** OR **;nickname** - Set a user's nickname

**;playf** - Make VaeBot play some bangin' tunes... from a file :o

**;remauto** OR **;rema** - Remove a song from the music auto-playlist

**;remautorole** OR **;delautorole** OR aroledel - Remove an autorole

**;remqueue** OR **;remq** - Remove a song from the music queue

**;remrole** OR **;removerole** OR **;delrole** - Remove a role from a user

**;setautorole** OR **;addautorole** OR **;arole** - Set a new autorole

**;skip** - Skip to the next song

**;stop** OR **;silence** - Cancel the party, the bangin' tunes can wait for another day

**;switch** - Specific command

**;tempban** OR **;tban** OR **;temporaryban** OR **;timeban** OR **;bantime** - Temporarily ban a user from the guild

**;tempbans** OR **;tbans** OR **;timebans** OR **;timedbans** - Get all temporarily banned users

**;unban** OR **;remban** - Unban a user from the guild

**;uncalm** OR **;uncalmchat** OR **;unslow** OR **;unslowchat** - Removes chat slowdown

**;undomute** OR **;popmute** - Remove a user's last mute from their record and unmute them if they are muted

**;unlink** OR **;remlink** OR **;dellink** OR **;untrigger** OR **;unevent** - UnLink an event from an action

**;unmute** OR **;unwarn** OR **;unmutehammer** - Unmute a user

**;warn** OR **;warnhammer** - Warns a user and puts the warning on their record


## Public Commands
​
**;autoplaylist** OR **;ap** - Output all the bangin' tunes in the auto-playlist

**;autoroles** - Get all autoroles (roles which users are allowed to assign to themselves)

**;channels** - Get all guild channels

**;closeticket** OR **;closesupport** OR **;stopticket** OR **;endticket** OR **;close** - Create a ticket to be viewed by Support

**;cmds** OR **;commands** OR **;help** - Output all commands

**;decrypt** - Decrypt text using One Time Pad

**;define** OR **;urban** - Output the definition for a word/phrase using Urban Dictionary

**;encrypt** - Encrypt text using One Time Pad

**;ginfo** OR **;guildinfo** - Get guild info

**;img** OR **;image** - Output an image for a word/phrase using Google

**;info** - Get info about a user

**;nowplaying** OR **;np** - Get info about the currently playing song

**;offenses** OR **;badoffenses** OR **;listoffenses** OR **;rules** - Output the list of offenses with defined mute times

**;ping** - Pings a user

**;play** OR **;add** OR **;addqueue** - Make VaeBot play some bangin' tunes (or add them to the queue if the party's already started)

**;playauto** OR **;playa** - Plays a tune already stored in the auto-playlist

**;power** OR **;rank** OR **;rate** - Are you over 9000?!

**;queue** - List all queued songs

**;roles** - Get all guild roles

**;startauto** OR **;startap** - Start playing the auto-playlist music

**;startqueue** - Start playing the queued music

**;syntax** OR **;help** OR **;cmd** - Display command information

**;ticket** OR **;support** OR **;ask** OR **;addticket** OR **;submitticket** OR **;sendticket** OR **;newticket** - Create a ticket to be viewed by Support

**;tickets** OR **;gettickets** OR **;showtickets** OR **;activetickets** OR **;displaytickets** OR **;supporttickets** - Display all open support tickets

**;toggle** - Toggle an autorole on the speaker

**;translate** - Translate a word/sentence into English

**;txt** OR **;text** OR **;type** - Echo your text with emojis

**;undo** OR **;pop** - Remove the last song from the queue which was added by the speaker

**;voteskip** - Vote to skip the current song (will skip when the vote reaches 50% of the users in the voice channel)


================================================
FILE: Util.js
================================================
/*

addCommand\((\[.+?\]), (\w+?), (\w+?), (\w+?), function\([\w, ]+?\) {.*\n\t([\s\S]+?)\n},\n\t(".*?"),\n\t(".*?"),\n\t(".*?")\n\)

module.exports = Cmds.addCommand({
    cmds: \1,

    requires: {
        guild: \4,
        loud: false
    },

    desc: \6,

    args: \7,

    example: \8,

    func: (cmd, args, msgObj, speaker, channel, guild) => {
    \5
    }
})

*/

const FileSys = index.FileSys;
const DateFormat = index.DateFormat;
const Exec = index.Exec;
const Path = index.Path;
const NodeUtil = index.NodeUtil;

exports.charLimit = 1999;

exports.regexURLPerfect = new RegExp(
    '^' +
        // protocol identifier
        '(?:(?:https?|ftp)://)' +
        // user:pass authentication
        '(?:\\S+(?::\\S*)?@)?' +
        '(?:' +
            // IP address exclusion
            // private & local networks
            '(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
            '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
            '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
            // IP address dotted notation octets
            // excludes loopback network 0.0.0.0
            // excludes reserved space >= 224.0.0.0
            // excludes network & broacast addresses
            // (first & last IP address of each class)
            '(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
            '(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
            '(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
        '|' +
            // host name
            '(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)' +
            // domain name
            '(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*' +
            // TLD identifier
            '(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))' +
            // TLD may end with dot
            '\\.?' +
        ')' +
        // port number
        '(?::\\d{2,5})?' +
        // resource path
        '(?:[/?#]\\S*)?' +
    '$', 'i');

exports.rolePermissions = [
    'CREATE_INSTANT_INVITE',
    'KICK_MEMBERS',
    'BAN_MEMBERS',
    'VIEW_AUDIT_LOG',
    'ADMINISTRATOR',
    'MANAGE_CHANNELS',
    'MANAGE_GUILD',
    'ADD_REACTIONS', // add reactions to messages
    'VIEW_CHANNEL',
    'SEND_MESSAGES',
    'SEND_TTS_MESSAGES',
    'MANAGE_MESSAGES',
    'EMBED_LINKS',
    'ATTACH_FILES',
    'READ_MESSAGE_HISTORY',
    'MENTION_EVERYONE',
    'USE_EXTERNAL_EMOJIS', // use external emojis
    'CONNECT', // connect to voice
    'SPEAK', // speak on voice
    'MUTE_MEMBERS', // globally mute members on voice
    'DEAFEN_MEMBERS', // globally deafen members on voice
    'MOVE_MEMBERS', // move member's voice channels
    'USE_VAD', // use voice activity detection
    'CHANGE_NICKNAME',
    'MANAGE_NICKNAMES', // change nicknames of others
    'MANAGE_ROLES',
    'MANAGE_WEBHOOKS',
    'MANAGE_EMOJIS',
];

exports.rolePermissionsObj = {
    CREATE_INSTANT_INVITE: true,
    KICK_MEMBERS: true,
    BAN_MEMBERS: true,
    VIEW_AUDIT_LOG: true,
    ADMINISTRATOR: true,
    MANAGE_CHANNELS: true,
    MANAGE_GUILD: true,
    ADD_REACTIONS: true, // add reactions to messages
    VIEW_CHANNEL: true,
    SEND_MESSAGES: true,
    SEND_TTS_MESSAGES: true,
    MANAGE_MESSAGES: true,
    EMBED_LINKS: true,
    ATTACH_FILES: true,
    READ_MESSAGE_HISTORY: true,
    MENTION_EVERYONE: true,
    USE_EXTERNAL_EMOJIS: true, // use external emojis
    CONNECT: true, // connect to voice
    SPEAK: true, // speak on voice
    MUTE_MEMBERS: true, // globally mute members on voice
    DEAFEN_MEMBERS: true, // globally deafen members on voice
    MOVE_MEMBERS: true, // move member's voice channels
    USE_VAD: true, // use voice activity detection
    CHANGE_NICKNAME: true,
    MANAGE_NICKNAMES: true, // change nicknames of others
    MANAGE_ROLES: true,
    MANAGE_WEBHOOKS: true,
    MANAGE_EMOJIS: true,
};

exports.textChannelPermissions = [
    'CREATE_INSTANT_INVITE',
    'MANAGE_CHANNEL',
    'ADD_REACTIONS', // add reactions to messages
    'VIEW_CHANNEL',
    'SEND_MESSAGES',
    'SEND_TTS_MESSAGES',
    'MANAGE_MESSAGES',
    'EMBED_LINKS',
    'ATTACH_FILES',
    'READ_MESSAGE_HISTORY',
    'MENTION_EVERYONE',
    'USE_EXTERNAL_EMOJIS', // use external emojis
    'MANAGE_PERMISSIONS',
    'MANAGE_WEBHOOKS',
];

exports.textChannelPermissionsObj = {
    ADD_REACTIONS: true, // add reactions to messages
    VIEW_CHANNEL: true,
    SEND_MESSAGES: true,
    SEND_TTS_MESSAGES: true,
    MANAGE_MESSAGES: true,
    EMBED_LINKS: true,
    ATTACH_FILES: true,
    READ_MESSAGE_HISTORY: true,
    MENTION_EVERYONE: true,
    USE_EXTERNAL_EMOJIS: true, // use external emojis
    CREATE_INSTANT_INVITE: true,
    MANAGE_CHANNEL: true,
    MANAGE_PERMISSIONS: true,
    MANAGE_WEBHOOKS: true,
};

exports.voiceChannelPermissions = [
    'CONNECT', // connect to voice
    'SPEAK', // speak on voice
    'MUTE_MEMBERS', // globally mute members on voice
    'DEAFEN_MEMBERS', // globally deafen members on voice
    'MOVE_MEMBERS', // move member's voice channels
    'USE_VAD', // use voice activity detection
    'CREATE_INSTANT_INVITE',
    'MANAGE_CHANNEL',
    'MANAGE_PERMISSIONS',
    'MANAGE_WEBHOOKS',
];

exports.voiceChannelPermissionsObj = {
    CONNECT: true, // connect to voice
    SPEAK: true, // speak on voice
    MUTE_MEMBERS: true, // globally mute members on voice
    DEAFEN_MEMBERS: true, // globally deafen members on voice
    MOVE_MEMBERS: true, // move member's voice channels
    USE_VAD: true, // use voice activity detection
    CREATE_INSTANT_INVITE: true,
    MANAGE_CHANNEL: true,
    MANAGE_PERMISSIONS: true,
    MANAGE_WEBHOOKS: true,
};

exports.permissionsOrder = {
    ADMINISTRATOR: 27,
    MANAGE_GUILD: 26,
    MANAGE_ROLES: 25,
    MANAGE_CHANNELS: 24,
    MANAGE_CHANNEL: 24, // Channel
    MANAGE_WEBHOOKS: 23,
    MANAGE_EMOJIS: 22,
    MANAGE_PERMISSIONS: 22, // Channel
    VIEW_AUDIT_LOG: 21,
    MENTION_EVERYONE: 20,
    BAN_MEMBERS: 19,
    KICK_MEMBERS: 18,
    MOVE_MEMBERS: 17,
    DEAFEN_MEMBERS: 16,
    MUTE_MEMBERS: 15,
    MANAGE_MESSAGES: 14,
    MANAGE_NICKNAMES: 13,
    USE_EXTERNAL_EMOJIS: 12,
    ATTACH_FILES: 11,
    SEND_TTS_MESSAGES: 10,
    ADD_REACTIONS: 9,
    EMBED_LINKS: 8,
    CHANGE_NICKNAME: 7,
    USE_VAD: 6,
    SPEAK: 5,
    CONNECT: 4,
    CREATE_INSTANT_INVITE: 3,
    SEND_MESSAGES: 2,
    READ_MESSAGE_HISTORY: 1,
    VIEW_CHANNEL: 0,
};

exports.permRating = [
    ['ADMINISTRATOR', 100],
    ['MANAGE_GUILD', 90],
    ['MANAGE_ROLES', 80],
    ['MANAGE_CHANNELS', 70],
    ['MANAGE_EMOJIS', 60],
    ['MENTION_EVERYONE', 50],
    ['VIEW_AUDIT_LOG', 50],
    ['BAN_MEMBERS', 40],
    ['KICK_MEMBERS', 30],
    ['MANAGE_MESSAGES', 20],
    ['MANAGE_NICKNAMES', 20],
    ['MOVE_MEMBERS', 20],
    ['ATTACH_FILES', 10],
    ['ADD_REACTIONS', 10],
    ['SEND_MESSAGES', 10],
];

exports.replaceAll = (str, search, replacement) => str.split(search).join(replacement);

function getURLChecker() {
    const SCHEME = '[a-z\\d.-]+://';
    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])';
    const HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+";
    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
    |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
    |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
    |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
    |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
    |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
    |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
    |xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)`;

    const HOST_OR_IP = `(?:${HOSTNAME}${TLD}|${IPV4})`;
    const PATH = '(?:[;/][^#?<>\\s]*)?';
    const QUERY_FRAG = '(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?';
    const URI1 = `\\b${SCHEME}[^<>\\s]+`;
    const URI2 = `\\b${HOST_OR_IP}${PATH}${QUERY_FRAG}(?!\\w)`;

    const MAILTO = 'mailto:';
    const EMAIL = `(?:${MAILTO})?[a-z0-9!#$%&'*+/=?^_\`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_\`{|}~-]+)*@${HOST_OR_IP}${QUERY_FRAG}(?!\\w)`;

    const URI_RE = new RegExp(`(?:${URI1}|${URI2}|${EMAIL})`, 'ig');
    const SCHEME_RE = new RegExp(`^${SCHEME}`, 'i');

    const quotes = {
        "'": '`',
        '>': '<',
        ')': '(',
        ']': '[',
        '}': '{',
        '»': '«',
        '›': '‹',
    };

    const defaultOptions = {
        callback(text, href) {
            return href || null;
        },
        punct_regexp: /(?:[!?.,:;'"]|(?:&|&amp;)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/,
    };

    function checkURLs(txtParam, optionsParam) {
        let txt = exports.replaceAll(txtParam, '\\', '');
        txt = exports.replaceAll(txt, '*', '');
        txt = exports.replaceAll(txt, '_', '');

        if (txt.includes('roblox')) Util.log(txt);

        const options = optionsParam || {};

        // Temp variables.
        let arr;
        let i;
        let link;
        let href;

        // Output HTML.
        // const html = '';

        // Store text / link parts, in order, for re-combination.
        const parts = [];

        // Used for keeping track of indices in the text.
        let idxPrev;
        let idxLast;
        let idx;
        let linkLast;

        // Used for trimming trailing punctuation and quotes from links.
        let matchesBegin;
        let matchesEnd;
        let quoteBegin;
        let quoteEnd;

        // Initialize options.
        for (i of Object.keys(defaultOptions)) {
            if (options[i] == null) {
                options[i] = defaultOptions[i];
            }
        }

        const inRep = (a) => {
            idxLast -= a.length;
            return '';
        };

        // Find links.
        while (arr = URI_RE.exec(txt)) {
            link = arr[0];
            idxLast = URI_RE.lastIndex;
            idx = idxLast - link.length;

            // Not a link if preceded by certain characters.
            if (/[/:]/.test(txt.charAt(idx - 1))) {
                continue;
            }

            // Trim trailing punctuation.
            do {
                // If no changes are made, we don't want to loop forever!
                linkLast = link;

                quoteEnd = link.substr(-1);
                quoteBegin = quotes[quoteEnd];

                // Ending quote character?
                if (quoteBegin) {
                    matchesBegin = link.match(new RegExp(`\\${quoteBegin}(?!$)`, 'g'));
                    matchesEnd = link.match(new RegExp(`\\${quoteEnd}`, 'g'));

                    // If quotes are unbalanced, remove trailing quote character.
                    if ((matchesBegin ? matchesBegin.length : 0) < (matchesEnd ? matchesEnd.length : 0)) {
                        link = link.substr(0, link.length - 1);
                        idxLast--;
                    }
                }

                // Ending non-quote punctuation character?
                if (options.punct_regexp) {
                    link = link.replace(options.punct_regexp, inRep);
                }
            } while (link.length && link !== linkLast);

            href = link;

            // Add appropriate protocol to naked links.
            if (!SCHEME_RE.test(href)) {
                const origHref = href;
                if (href.indexOf('@') != -1) {
                    if (!href.indexOf(MAILTO)) {
                        href = '';
                    } else {
                        href = MAILTO;
                    }
                } else if (!href.indexOf('irc.')) {
                    href = 'irc://';
                } else if (!href.indexOf('ftp.')) {
                    href = 'ftp://';
                } else {
                    href = 'http://';
                }
                href += origHref;
            }

            // Push preceding non-link text onto the array.
            if (idxPrev !== idx) {
                parts.push([txt.slice(idxPrev, idx)]);
                idxPrev = idxLast;
            }

            // Push massaged link onto the array
            parts.push([link, href]);
        }

        // Push remaining non-link text onto the array.
        parts.push([txt.substr(idxPrev)]);

        // Process the array items.
        const URLs = [];

        for (i = 0; i < parts.length; i++) {
            const result = options.callback.apply('nooone', parts[i]);
            if (result) {
                URLs.push(result);
            }
        }

        return URLs;
    }

    return checkURLs;
}

exports.checkURLs = getURLChecker();

function forceAddRolesInner(guild, sendRole, iterNum = 1) {
    let didError = false;

    guild.members.forEach((member) => {
        if (!exports.hasRole(member, sendRole)) {
            member.addRole(sendRole)
                .then(() => Util.log(`Assigned role to ${exports.getName(member)}`))
                .catch((error) => {
                    didError = true;
                    Util.log(`[E_InitRoles] addRole: ${error}`);
                });
        }
    });

    if (!didError || iterNum >= 10) return;

    setTimeout(() => {
        forceAddRolesInner(guild, sendRole, iterNum + 1);
    }, 1000 * 4);
}

function forceAddRoles(guild, sendRole) {
    forceAddRolesInner(guild, sendRole);
}

exports.initRoles = async function (sendRole, guild, guildChannel) {
    try {
        await Promise.all(guild.roles.map(async (role) => {
            if (role.name !== 'SendMessages' && role.hasPermission('SEND_MESSAGES', null, false)) {
                try {
                    await role.setPermissions(role.permissions & (~2048));
                } catch (err) {
                    console.log('[RolePermRem]', err);
                }
            }
        }));

        await Promise.all(guild.channels.map(async (channel) => {
            const deniesMessages = channel.permissionOverwrites.some(channelPerm => channelPerm.type === 'role' && channelPerm.denied.toArray(false).includes('SEND_MESSAGES'));

            if (deniesMessages) return;

            const newOverwrites = channel.permissionOverwrites.map((channelPerm) => {
                // const permObj = channelPerm.type === 'role' ? guild.roles.get(channelPerm.id) : guild.members.get(channelPerm.id);

                const allowed = channelPerm.allowed.toArray(false).filter(perm => perm !== 'SEND_MESSAGES');
                const denied = channelPerm.denied.toArray(false).filter(perm => perm !== 'SEND_MESSAGES');

                return {
                    allowed,
                    denied,
                    id: channelPerm.id,
                    type: channelPerm.type,
                };
            });

            channel.replacePermissionOverwrites({ overwrites: newOverwrites }).catch((err) => {
                console.log('[RepPermOverwrites]', err);
            });
        }));

        if (guildChannel) {
            Util.sendDescEmbed(guildChannel, 'Setup VaeBot', 'Server roles and channels have been setup appropriately', null, null, null);
        }
    } catch (err) {
        console.log('InitRolesInner Error:', err);
    }

    forceAddRoles(guild, sendRole);
};

exports.arrayToObj = function (arr) {
    const obj = {};
    for (let i = 0; i < arr.length; i++) {
        const val = arr[i];
        obj[val] = true;
    }
    return obj;
};

exports.capitalize = function (strParam) {
    let str = strParam;
    str = String(str);
    return str.charAt(0).toUpperCase() + str.slice(1);
};

exports.runLua = function (args, channel) {
    // args = "os=nil;io=nil;debug=nil;package=nil;require=nil;loadfile=nil;dofile=nil;collectgarbage=nil;" + args;
    const tagNum = Math.floor((new Date()).getTime());
    const fileDir = `/tmp/script_${tagNum}.lua`;
    FileSys.writeFile(fileDir, args, (err) => {
        if (err) {
            Util.log(`Script creation error: ${err}`);
            Util.print(channel, `Script creation error: ${err}`);
        }
        Exec(`lua ${fileDir}`, (error, stdoutParam, stderr) => {
            let stdout = stdoutParam;
            if (!stdout) stdout = '';
            const safeOut = Util.safe(stdout);
            // var safeErr = Util.safe(stderr);
            const outStr = [];
            if (error) {
                outStr.push('**Execution error:**');
                outStr.push('```');
                Util.log(`Execution Error: ${stderr}`);
                outStr.push(error);
                outStr.push('```');
            } else {
                if (safeOut.length <= 1980) {
                    outStr.push('**Output:**');
                    outStr.push('```');
                    outStr.push(safeOut);
                    outStr.push('```');
                } else {
                    const options = {
                        url: 'https://hastebin.com/documents',
                        method: 'POST',
                        headers: { 'Content-Type': 'text/plain' },
                        body: stdout,
                    };
                    index.Request(options, (error2, response, bodyParam) => {
                        const body = JSON.parse(bodyParam);
                        if (error2 || !body || !body.key) {
                            Util.print(channel, 'Hastebin upload error:', error2);
                        } else {
                            Util.print(channel, 'Output:', `https://hastebin.com/raw/${body.key}`);
                        }
                    });
                }
                if (stderr) {
                    outStr.push('**Lua Error:**');
                    outStr.push('```');
                    Util.log(`Lua Error: ${stderr}`);
                    outStr.push(stderr);
                    outStr.push('```');
                }
            }
            Util.print(channel, outStr.join('\n'));
            FileSys.unlink(fileDir);
        });
    });
};

exports.doXOR = function (a, b) {
    const result = ((a == 1 || b == 1) && !(a == 1 && b == 1)) ? 1 : 0;
    return result;
};

exports.capitalize2 = function (strParam, repUnder) {
    let str = String(strParam);
    if (repUnder) str = exports.replaceAll(str, '_', ' ');
    str = str.replace(/[0-9a-z]+/ig, (txt) => { Util.log(txt); return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });
    return str;
};

exports.boolToAns = function (bool) {
    const result = bool ? 'Yes' : 'No';
    return result;
};

exports.safe = function (str) {
    if (typeof (str) === 'string') return str.replace(/`/g, '\\`').replace(/@/g, '@­');
    return undefined;
};

exports.safe2 = function (str) {
    if (typeof (str) === 'string') return str.replace(/`/g, '\\`');
    return undefined;
};

exports.safeEveryone = function (str) {
    if (typeof (str) === 'string') {
        const newStr = str.replace(/@everyone/g, '@​everyone');
        return newStr.replace(/@here/g, '@​here');
    }
    return undefined;
};

exports.fix = str => (`\`${exports.safe(str)}\``);

exports.toFixedCut = (num, decimals) => Number(num.toFixed(decimals)).toString();

exports.grabFiles = function (filePath, filter = () => true) {
    const dirFiles = FileSys.readdirSync(filePath);
    let fullFiles = [];
    dirFiles.forEach((file) => {
        const fileData = FileSys.lstatSync(`${filePath}${file}`);
        if (fileData.isDirectory()) {
            const toAdd = exports.grabFiles(`${filePath}${file}/`, filter);
            fullFiles = fullFiles.concat(toAdd);
        } else if (filter(file)) {
            fullFiles.push(`${filePath}${file}`);
        }
    });
    return fullFiles;
};

exports.bulkRequire = function (filePath) {
    const bulkFiles = exports.grabFiles(filePath, file => file.endsWith('.js'));

    for (const data of Object.values(bulkFiles)) {
        exports.pathRequire(data);
    }
};

exports.pathRequire = function (filePath) {
    const file = Path.resolve(filePath);
    delete require.cache[require.resolve(file)];

    const fileData = require(filePath);

    const dirName = /(\w+)[/\\]\w+\.js$/.exec(file)[1];

    if (dirName && has.call(index.commandTypes, dirName)) {
        const cmdTypes = index.commandTypes;
        for (const [commandType, commandKey] of Object.entries(cmdTypes)) {
            if (commandKey !== 'null') {
                if (commandType === dirName) {
                    fileData[2][commandKey] = true;
                } else {
                    fileData[2][commandKey] = false;
                }
            }
        }
    }
};

exports.checkStaff = function (guild, member) {
    if (guild == null || member == null) {
        Util.log(`>>> CHECK STAFF ISSUE: ${guild} ${member} <<<`);
    }

    if (member.id === vaebId || member.id === selfId || member.id === guild.ownerID) return true;
    if (member.hasPermission('ADMINISTRATOR')) return true;
    if (member.id === '126710973737336833') return true;
    const speakerRoles = member.roles;
    if (!speakerRoles) return false;
    // if (exports.getPermRating(guild, member) >= 30) return true;
    return speakerRoles.some(role => /\bstaff\b/i.test(role.name) || role.name === 'Owner/Seller' || role.name === 'Bot Admin'
        || role.name === 'Moderator' || role.name.includes('Head Mod') || role.name === 'Trial Moderator' || /OP$/.test(role.name));
};

exports.commandFailed = function (channel, speaker, tag, message) {
    if (message == null) {
        message = tag;
        tag = null;
    }

    const tagMessage = tag ? `[${tag}] ` : '';

    if (channel != null) {
        exports.sendEmbed(channel, `${tagMessage}Command Failed`, message, exports.makeEmbedFooter(speaker), null, colGreen, null);
    } else {
        Util.log(`${tagMessage}[Command_Failed] ${speaker.id}: ${message}`);
    }

    return false;
};

exports.getRandomInt = function (minParam, maxParam) { // inclusive, exclusive
    maxParam++; // inclusive, inclusive
    const min = Math.ceil(minParam);
    const max = Math.floor(maxParam);
    return Math.floor(Math.random() * (max - min)) + min;
};

/* function chunkStringLine(str, size) {
    var numChunks = Math.ceil(str.length / size);
    var chunks = [];

    for (var i = 0, o = 0; i < numChunks; ++i, o += size) {
        chunks[i] = str.substr(o, size);
    }

    var chunkLength = chunks.length;

    if (numChunks > 1) {
        for (var i = 0; i < chunkLength; i++) {
            var nowChunk = chunks[i];
            var lastLine = nowChunk.lastIndexOf("\n");
            if (lastLine >= 0) {
                var nowChunkMsg = nowChunk.substring(0, lastLine);
                chunks[i] = nowChunkMsg;
                var nextChunkMsg = nowChunk.substring(lastLine+1);
                if (chunks[i+1] == null) {
                    if (nextChunkMsg == "" || nextChunkMsg == "\n" || nextChunkMsg == "```" || nextChunkMsg == "\n```") break;
                    chunks[i+1] = "";
                }
                chunks[i+1] = nextChunkMsg + chunks[i+1];
            }
        }
    }

    return chunks;
} */

/*

-Chunk string into sets of 2k chars
-For each chunk
    -If msg includes newline and first character of next message isn't newline
        -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
        -Copy everything after the newline to the start of the next chunk
        -Set msg to everything before the newline
    -If number of code blocks is odd and there are non-whitespace characters after the last codeblock
        -Add a codeblock to the end of the chunk
    -If number of characters is above 2000
        -Find last newline under (or equal) the 2001 character mark, where the character before it isn't a codeblock
        -If no newline
            -Append a newline as <= 2001st character (not between code blocks if possible)
        -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
        -Cut the chunk to everything before the newline

*/

exports.isObject = function (val) { // Or array
    if (val == null) return false;
    return (typeof (val) === 'object');
};

exports.cloneObj = function (obj, fixBuffer) {
    let copy;

    if (obj == null || typeof (obj) !== 'object') return obj;

    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    if (obj instanceof Array) {
        copy = [];
        const len = obj.length;
        for (let i = 0; i < len; i++) {
            copy[i] = exports.cloneObj(obj[i], fixBuffer);
        }
        return copy;
    }

    if (fixBuffer && obj instanceof Buffer) {
        return obj.readUIntBE(0, 1);
    }

    if (obj instanceof Object && !(obj instanceof Buffer)) {
        copy = {};
        for (const [attr, objAttr] of Object.entries(obj)) {
            copy[attr] = exports.cloneObj(objAttr, fixBuffer);
        }
        return copy;
    }

    console.log("Couldn't clone obj, returning real value");

    return obj;
};

exports.cloneObjDepth = function (obj, maxDepth = 1, nowDepth = 0) {
    let copy;

    if (obj == null || typeof (obj) !== 'object') return obj;

    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    if (obj instanceof Array) {
        const len = obj.length;

        if (nowDepth >= maxDepth && len > 0) return '[Array]';

        copy = [];
        for (let i = 0; i < len; i++) {
            copy[i] = exports.cloneObjDepth(obj[i], maxDepth, nowDepth + 1);
        }

        return copy;
    }

    if (obj instanceof Object && !(obj instanceof Buffer)) {
        const entries = Object.entries(obj);

        if (nowDepth >= maxDepth && entries.length > 0) return '[Object]';

        copy = {};
        for (const [attr, objAttr] of entries) {
            copy[attr] = exports.cloneObjDepth(objAttr, maxDepth, nowDepth + 1);
        }

        return copy;
    }

    console.log("Couldn't clone obj, returning real value");

    return obj;
};

const elapseTimeTags = {};

exports.throwErr = function () {
    setTimeout(() => {
        throw new Error('err');
    }, 1000);
};

exports.getElapsed = function (tag, remove) {
    let elapsed;

    if (elapseTimeTags[tag] != null) {
        const startTimeData = elapseTimeTags[tag];
        const elapsedTimeData = process.hrtime(startTimeData); // Seconds, Nanoseconds (Seconds * 1e9)
        elapsed = (elapsedTimeData[0] * 1e3) + Number((elapsedTimeData[1] / 1e6).toFixed(3));
    }

    if (remove) {
        elapseTimeTags[tag] = null;
        delete elapseTimeTags[tag]; // Remove time storage
    } else {
        elapseTimeTags[tag] = process.hrtime(); // Mark the start time
    }

    return elapsed;
};

exports.formatTime = function (time) {
    let timeStr;
    let formatStr;

    const numSeconds = exports.round(time / 1000, 0.1);
    const numMinutes = exports.round(time / (1000 * 60), 0.1);
    const numHours = exports.round(time / (1000 * 60 * 60), 0.1);
    const numDays = exports.round(time / (1000 * 60 * 60 * 24), 0.1);
    const numWeeks = exports.round(time / (1000 * 60 * 60 * 24 * 7), 0.1);
    const numMonths = exports.round(time / (1000 * 60 * 60 * 24 * 30.42), 0.1);
    const numYears = exports.round(time / (1000 * 60 * 60 * 24 * 365.2422), 0.1);

    if (numSeconds < 1) {
        timeStr = exports.toFixedCut(time, 0);
        formatStr = `${timeStr} millisecond`;
    } else if (numMinutes < 1) {
        timeStr = exports.toFixedCut(numSeconds, 1);
        formatStr = `${timeStr} second`;
    } else if (numHours < 1) {
        timeStr = exports.toFixedCut(numMinutes, 1);
        formatStr = `${timeStr} minute`;
    } else if (numDays < 1) {
        timeStr = exports.toFixedCut(numHours, 1);
        formatStr = `${timeStr} hour`;
    } else if (numWeeks < 1) {
        timeStr = exports.toFixedCut(numDays, 1);
        formatStr = `${timeStr} day`;
    } else if (numMonths < 1) {
        timeStr = exports.toFixedCut(numWeeks, 1);
        formatStr = `${timeStr} week`;
    } else if (numYears < 1) {
        timeStr = exports.toFixedCut(numMonths, 1);
        formatStr = `${timeStr} month`;
    } else {
        timeStr = exports.toFixedCut(numYears, 1);
        formatStr = `${timeStr} year`;
    }

    if (timeStr !== '1') formatStr += 's';

    return formatStr;
};

exports.chunkString = function (str, maxChars) {
    const iterations = Math.ceil(str.length / maxChars);
    const chunks = new Array(iterations);
    for (let i = 0, j = 0; i < iterations; ++i, j += maxChars) chunks[i] = str.substr(j, maxChars);
    return chunks;
};

exports.cutStringSafe = function (msg, postMsg, lastIsOpener) { // Tries to cut the string along a newline
    let lastIndex = msg.lastIndexOf('\n');
    if (lastIndex < 0) return [msg, postMsg];
    let preCut = msg.substring(0, lastIndex);
    let postCut = msg.substring(lastIndex + 1);
    const postHasBlock = postCut.includes('```');
    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)
        lastIndex = msg.lastIndexOf('```');
        preCut = msg.substring(0, lastIndex + 3);
        postCut = msg.substring(lastIndex + 3);
    } else {
        const strEnd1 = preCut.substr(Math.max(preCut.length - 3, 0), 3);
        const strEnd2 = preCut.substr(Math.max(preCut.length - 4, 0), 4);
        if (postHasBlock || (lastIsOpener && (strEnd1 === '```' || strEnd2 === '``` ' || strEnd2 === '```\n'))) { // If post is triyng to pass over opener or last section of preCut is an opener
            return [msg, postMsg];
        }
    }
    return [preCut, postCut + postMsg];
};

exports.fixMessageLengthNew = function (msgParam) {
    const argsFixed = exports.chunkString(msgParam, exports.charLimit); // Group string into sets of 2k chars
    const minusLimit = exports.charLimit - 4;
    // argsFixed.forEach(o => Util.log("---\n" + o));
    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)
    for (let i = 0; i < argsFixed.length; i++) {
        let passOver = ''; // String to pass over as the start of the next chunk
        let msg = argsFixed[i];
        const numBlock = (msg.match(/```/g) || []).length; // Number of user created code blocks in this chunk
        if (totalBlocks % 2 == 1) msg = `\`\`\`\n${msg}`; // If code block is currently open then this chunk needs to be formatted
        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)
        let lastIsOpener = totalBlocks % 2 == 1; // Checks whether the last code block is an opener or a closer
        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
            passOver = msg.substring(minusLimit);
            msg = msg.substr(0, minusLimit);
            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
            totalBlocks -= numPass;
            if (numPass % 2 == 1) lastIsOpener = false;
        }
        const nextMsg = passOver + (argsFixed[i + 1] != null ? argsFixed[i + 1] : ''); // Message for next chunk (or empty string if none)
        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)
            const cutData = exports.cutStringSafe(msg, '', lastIsOpener);
            msg = cutData[0];
            passOver = cutData[1] + passOver;
        }
        if (lastIsOpener) msg += '\n```'; // Close any left over code blocks (and re open on next chunk if they continue)
        argsFixed[i] = msg;
        if (passOver.length > 0) { // Whether any text actually needs to be passed
            if (argsFixed[i + 1] == null) argsFixed[i + 1] = ''; // Create new chunk if this is the last one
            argsFixed[i + 1] = passOver + argsFixed[i + 1];
        }
    }
    return argsFixed;
};

/* function fixMessageLength(msg) {
    var argsFixed = chunkStringLine(msg, 2000);
    var argsLength = argsFixed.length;
    for (var i = 0; i < argsFixed.length; i++) {
        var passOver = "";
        var msg = argsFixed[i];
        //Util.log("Original message length: " + msg.length);
        if (msg.length > 1996) {
            passOver = msg.substring(1996);
            msg = msg.substring(0, 1996);
            //Util.log("passStart orig: " + passOver.length);
            var lastLine = msg.lastIndexOf("\n");
            if (lastLine >= 5) {
                var msgEnd = lastLine;
                var passStart = msgEnd+1;
                passOver = msg.substring(passStart) + passOver;
                msg = msg.substring(0, msgEnd);
                //Util.log("passOver: " + passOver.length);
                //Util.log("msg: " + msg.length);
                //Util.log("lastLine: " + lastLine);
            }
        }
        var numBlock = (msg.match(/```/g) || []).length;
        if (numBlock % 2 == 1) {
            passOver = "```\n" + passOver;
            msg = msg + "\n```";
        }
        argsFixed[i] = msg;
        //Util.log("Message length: " + msg.length);
        //Util.log("Pass Over: " + passOver.length);
        if (passOver != "" && (argsFixed[i+1] != null || passOver != "```\n")) {
            if (argsFixed[i+1] == null) {
                //Util.log("Created new print block extender")
                argsFixed[i+1] = "";
            }
            argsFixed[i+1] = passOver + argsFixed[i+1];
        }
    }

    return argsFixed;
} */

exports.splitMessagesOld = function (messages) {
    const fixed = exports.fixMessageLengthNew(messages.join(' '));
    return fixed;
};

exports.escapeRegExp = function (str) {
    return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
};

function getMatchesWithBlock(str, matchChars, blockChars, useInside) { // Gets all matches of a substring that are in/out of a code block
    const pattern = new RegExp(exports.escapeRegExp(blockChars), 'g');
    let result;

    let numMatches = 0;
    let strPointer = 0;
    let newStr = '';

    while (result = pattern.exec(str)) {
        numMatches++;
        if (useInside) {
            if (numMatches % 2 == 1) { // Open block
                newStr += '.'.repeat(result.index - strPointer);
                strPointer = result.index;
            } else { // Close block (Store data)
                newStr += '.'.repeat(blockChars.length) + str.substring(strPointer + blockChars.length, result.index);
                strPointer = result.index;
            }
        } else {
            if (numMatches % 2 == 1) { // Open block (Store data)
                newStr += str.substring(strPointer, result.index);
                strPointer = result.index;
            } else { // Close block
                newStr += '.'.repeat(result.index - strPointer + blockChars.length);
                strPointer = result.index + blockChars.length;
            }
        }
    }

    if (useInside) {
        newStr += '.'.repeat(str.length - strPointer);
    } else {
        newStr += str.substring(strPointer);
    }

    if (newStr.length != str.length) throw new Error('[E_GetMatchesWithBlock] Failed because the output string didn\'t match input string length');

    return (newStr.match(new RegExp(exports.escapeRegExp(matchChars), 'g')) || []);
}

/*

    chunkMessage
        -Split messages into chunks <= exports.charLimit characters
        -Chunks retain format (`, ``, ```, *, **, ***, _, __, ___) and thus account for fitting in format characters
        -Message splitting focuses on retaining format over reducing number of chunks:
            End ```
            Newline + Newline
            Newline + Format character(s)
            Newline
            Space
            Any
        -Make it so that if last chunk continued string onto next chunk, next chunk cuts at end of that string
*/

const formatSets = [
    ['___', '__'],
    ['***', '**', '*'],
    ['```', '``', '`'],
];

const splitSets = [ // pivot: -1 = Split Start, 0 = Remove, 1 = Split End
    { chars: '```', pivot: 1 }, // Only applies to end ```
    { chars: '\n\n', pivot: 0 },
    { chars: '\n', pivot: 0 },
    { chars: ' ', pivot: 0 },
];

const leaveExtra = formatSets.reduce((a, b) => a.concat(b)).length * 2;

function chunkMessage(msg) {
    const origChunks = [msg];
    let content = msg;
    let appendBeginning = [];

    const baseChunkSize = exports.charLimit - leaveExtra;

    for (let i = 0; content; ++i, content = origChunks[i]) {
        for (let j = 0; j < appendBeginning.length; j++) {
            content = appendBeginning[j] + content;
        }

        if (content.length < exports.charLimit) {
            origChunks[i] = content;
            break;
        }

        let chunk = content.substr(0, baseChunkSize);
        let leftOver;

        appendBeginning = [];

        for (let j = 0; j < splitSets.length; j++) {
            const splitSet = splitSets[j];
            const splitChars = splitSet.chars;
            const splitType = splitSet.pivot;

            let pivotStart = chunk.lastIndexOf(splitChars); // exclusive
            let pivotEnd = pivotStart; // inclusive

            if (pivotStart == -1) continue;

            if (splitType == 1) { // Split End
                pivotStart += splitChars.length;
                pivotEnd = pivotStart;
            } else if (splitType == 0) { // Remove
                pivotEnd += splitChars.length;
            }

            let chunkTemp = chunk.substring(0, pivotStart);

            if (splitChars == '```') { // Has to be closing a block
                const numSets = (chunkTemp.match(new RegExp(exports.escapeRegExp(splitChars), 'g')) || []).length;
                if (numSets % 2 == 1) {
                    if (numSets == 1) continue;
                    pivotStart = chunk.substring(0, pivotStart - splitChars.length).lastIndexOf(splitChars);
                    if (pivotStart == -1) continue;
                    pivotStart += splitChars.length;
                    pivotEnd = pivotStart;
                    chunkTemp = chunk.substring(0, pivotStart);
                }
            }

            if (chunkTemp.length <= leaveExtra) continue;

            Util.log(`Split on ${splitChars} @ ${pivotStart} @ ${pivotEnd}`);

            chunk = chunkTemp;
            leftOver = content.substr(pivotEnd);

            /* if (i == 1) {
                Util.log(chunkTemp);
                Util.log('---');
                Util.log(leftOver);
            } */

            break;
        }

        if (leftOver == null) {
            Util.log('Split on last');
            leftOver = content.substr(baseChunkSize);
        }

        for (let j = 0; j < formatSets.length; j++) {
            const formatSet = formatSets[j];

            for (let k = 0; k < formatSet.length; k++) {
                const formatChars = formatSet[k];
                const numSets = getMatchesWithBlock(chunk, formatChars, '```', false).length; // Should really only be counting matches not inside code blocks

                if (numSets % 2 == 1) {
                    chunk += formatChars;
                    appendBeginning.push(formatChars);
                    break;
                }
            }
        }

        if (chunk.substr(chunk.length - 3, 3) == '```') appendBeginning.push('​\n');

        origChunks[i] = chunk;

        if (leftOver && leftOver.length > 0) origChunks.push(leftOver);
    }

    return origChunks;
}

exports.splitMessages = function (messages) {
    return chunkMessage(messages.join(' '));
};

exports.print = function (channel, ...args) {
    const messages = exports.splitMessages(args);
    const promises = [];
    for (let i = 0; i < messages.length; i++) {
        const msg = messages[i];
        // Util.log(`${channel.name}: ${msg.length}`);
        const msgPromise = channel.send(msg);
        msgPromise.catch((err) => {
            console.log('[PRINT_CATCH]', err);
        });
        promises.push(msgPromise);
    }
    return Promise.all(promises);
};

const printPromise = async (channel, msg, resolveData, resolveErr) => {
    try {
        const data = await channel.send(msg);
        resolveData.push(data);
    } catch (err) {
        resolveErr.push(err);
        console.log('[PRINT_CATCH]', err);
    }
};

exports.print = async function (channel, ...args) {
    const messages = Util.splitMessages(args);
    const resolveData = [];
    const resolveErr = [];
    const promises = [];
    for (let i = 0; i < messages.length; i++) {
        const msg = messages[i];
        promises.push(printPromise(channel, msg, resolveData, resolveErr));
    }
    try {
        await Promise.all(promises);
        if (resolveData.length > 0 && resolveErr.length === 0) return resolveData[0];
        return resolveErr[0];
    } catch (err) {
        throw new Error("SOMETHING WENT WRONG WITH PRINT'S CODE:", err);
    }
};

exports.sortPerms = function (permsArr) {
    permsArr.sort((a, b) => exports.permissionsOrder[b] - exports.permissionsOrder[a]);
};

exports.getGuildRoles = function (guild) {
    return Array.from(guild.roles.values()).sort((a, b) => b.position - a.position); // From highest to lowest
};

exports.getName = function (userResolvable) {
    if (userResolvable == null) return null;
    if (typeof userResolvable === 'string') return userResolvable;
    return Util.isMember(userResolvable) ? userResolvable.user.username : userResolvable.username;
};

exports.getMostName = function (userResolvable) {
    if (userResolvable == null) return null;
    if (typeof userResolvable === 'string') return userResolvable;
    const username = exports.getName(userResolvable);
    const discrim = Util.isMember(userResolvable) ? userResolvable.user.discriminator : userResolvable.discriminator;
    return `${username}#${discrim}`;
};

exports.getFullName = function (userResolvable, strict) {
    if (userResolvable == null) return strict ? null : 'null'; // TODO: Make strict default at some point
    if (typeof userResolvable === 'string') return userResolvable;
    const mostName = exports.getMostName(userResolvable);
    return `${mostName} (${userResolvable.id})`;
};

exports.getDisplayName = function (member) {
    const result = member.displayName || member.username;
    return result;
};

exports.getMention = function (userResolvable, full) {
    let out;

    if (userResolvable.user) { // Member
        out = userResolvable.toString();
    } else if (userResolvable.id) { // User
        out = full ? exports.getFullName(userResolvable) : exports.getMostName(userResolvable);
    } else { // Id
        out = `<@${userResolvable}>`;
    }

    return out;
};

exports.getAvatar = function (userResolvable, outStr) {
    if (userResolvable != null && exports.isObject(userResolvable)) {
        if (userResolvable.user) userResolvable = userResolvable.user;
        // return userResolvable.displayAvatarURL({ format: 'png' });
        return userResolvable.displayAvatarURL;
    }
    return (outStr === true ? 'null' : null);
};

exports.isLoud = function (channel) {
    const guild = channel.guild;

    const botRegex = /\bbot\b|commands/i;
    const botRegex2 = /\bbot\b/i;
    const botRegex3 = /commands/i;

    if (!botRegex.test(channel.name)) {
        let botChannel = guild.channels.find(c => botRegex2.test(c.name) && botRegex3.test(c.name));
        if (!botChannel) botChannel = guild.channels.find(c => botRegex.test(c.name));

        if (botChannel) {
            exports.print(channel, `Please use ${botChannel}`);
        } else {
            exports.print(
                channel,
                '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)',
            );
        }

        return true;
    }

    return false;
};

exports.getDateString = function (d) {
    if (d == null) d = new Date();
    const result = `${DateFormat(d, 'ddd, mmm dS yyyy @ h:MM TT')} GMT`;
    return result;
};

exports.hasRole = (member, role) => member.roles.has(role.id);

exports.hasRoleName = (member, name) => {
    name = name.toLowerCase();
    const hasRoleVal = member.roles.some(role => role.name.toLowerCase().includes(name));
    return hasRoleVal;
};

exports.makeEmbedFooter = function (user, dateParam) {
    const memberName = exports.isObject(user) ? exports.getDisplayName(user) : String(user);
    let date = dateParam;
    if (date == null) date = new Date();
    const dateStr = exports.getDateString(date);
    return { text: `${memberName} | ${dateStr}`, icon_url: exports.getAvatar(user) };
};

exports.getSuffix = function (n) {
    const j = n % 10;
    const k = n % 100;
    if (j == 1 && k != 11) {
        return `${n}st`;
    }
    if (j == 2 && k != 12) {
        return `${n}nd`;
    }
    if (j == 3 && k != 13) {
        return `${n}rd`;
    }
    return `${n}th`;
};

/*

    If nowString is less than or exactly 512 characters set as field value and return nowFieldNum
    Find last newline under 512 characters
    If none exists then trim, set and return nowFieldNum
    Set everything before newline as value for the field
    Create new field immediately after current field
    Set name as zero width character
    Return function on new field and string after newline

*/

exports.setFieldValue = function (embFields, nowFieldNum, nowString) {
    const nowField = embFields[nowFieldNum];
    if (nowString.length <= 512) {
        nowField.value = nowString;
        return nowFieldNum;
    }
    let subFirst = nowString.substr(0, 512);
    let subNext;
    const lastNewline = subFirst.lastIndexOf('\n');
    if (lastNewline < 0) {
        const lastSpace = subFirst.lastIndexOf(' ');
        if (lastSpace < 0) {
            subNext = nowString.substring(512);
        } else {
            subFirst = nowString.substring(0, lastSpace);
            subNext = nowString.substring(lastSpace + 1);
        }
    } else {
        subFirst = nowString.substring(0, lastNewline);
        subNext = nowString.substring(lastNewline + 1);
    }
    nowField.value = subFirst;
    const newFieldNum = nowFieldNum + 1;
    embFields.splice(newFieldNum, 0, { name: '​', value: '', inline: nowField.inline });
    return exports.setFieldValue(embFields, newFieldNum, subNext);
};

/*

Max characters:

Title: 256
Description: 2048
Footer: 2048
Field Name: 256
Field Value: 512 (maybe 1024?)

*/

exports.sendEmbed = function (embChannel, embTitle, embDesc, embFooterParam, embImage, embColor, embFieldsParam, isContinued) {
    if (embChannel == null) return;

    let embFooter = embFooterParam;
    let embFields = embFieldsParam;

    let manyFields = false;
    let extraFields;

    if (embFields == null) embFields = [];

    for (let i = embFields.length - 1; i >= 0; i--) {
        if (!embFields[i].name) embFields.splice(i, 1);
    }

    if (embFields.length > 25) {
        manyFields = true;
        extraFields = embFields.splice(25);
    }

    for (let i = 0; i < embFields.length; i++) {
        const nowField = embFields[i];

        if (!has.call(nowField, 'inline')) nowField.inline = true;

        let nowName = nowField.name;
        let nowValue = nowField.value;

        nowName = exports.safeEveryone(String(nowName == null ? 'N/A' : nowName));
        nowValue = exports.safeEveryone(String(nowValue == null ? 'N/A' : nowValue));

        nowField.name = nowName.trim().length < 1 ? 'N/A' : nowName.substr(0, 256);

        if (nowValue.trim().length < 1) {
            nowField.value = 'N/A';
        } else if (nowValue.length > 512) {
            i = exports.setFieldValue(embFields, i, nowValue);
        } else {
            nowField.value = nowValue;
        }
    }

    const embDescStr = String(embDesc);

    let newTitle;
    let newFooter;
    let newDesc = ((embDesc == null || embDescStr.trim().length < 1) ? '​' : embDescStr.substr(0, 2048));

    if (embTitle) newTitle = embTitle.substr(0, 256);
    if (embFooter) {
        if (!exports.isObject(embFooter)) {
            embFooter = { text: embFooter };
        }
        newFooter = exports.cloneObj(embFooter);
        newFooter.text = (newFooter.text).substr(0, 2048);
    }

    if (isContinued) {
        newTitle = null;
        if (newDesc.length < 1 || newDesc === '​') newDesc = null;
    }

    if (manyFields) {
        newFooter = null;
    }

    const embObj = {
        title: newTitle,
        description: newDesc,
        fields: embFields,
        footer: newFooter,
        thumbnail: { url: embImage },
        color: embColor,
    };

    // console.log(1111, embObj);

    embChannel.send(undefined, { embed: embObj })
        .then(() => {
            // console.log(2222);
        })
        .catch((error) => {
            // console.log(3333);
            Util.log(`[E_SendEmbed] ${error} ${embChannel}`);
            Util.log(embObj);
            Util.log(JSON.stringify(embFields));
        });

    if (manyFields) {
        exports.sendEmbed(embChannel, embTitle, embDesc, embFooter, embImage, embColor, extraFields, true);
    }
};

exports.sendDescEmbed = function (embChannel, embTitle, embDesc, embFooter, embImage, embColorParam) {
    if (embChannel == null) return;

    let embColor = embColorParam;

    if (embColor == null) embColor = colBlue;

    if (embDesc != null && embDesc.length > 2048) {
        let subFirst = embDesc.substr(0, 2048);
        let subNext;
        const lastNewline = subFirst.lastIndexOf('\n');
        if (lastNewline < 0) {
            const lastSpace = subFirst.lastIndexOf(' ');
            if (lastSpace < 0) {
                subNext = embDesc.substring(2048);
            } else {
                subFirst = embDesc.substring(0, lastSpace);
                subNext = embDesc.substring(lastSpace + 1);
            }
        } else {
            subFirst = embDesc.substring(0, lastNewline);
            subNext = embDesc.substring(lastNewline + 1);
        }
        exports.sendEmbed(embChannel, embTitle, subFirst, null, embImage, embColor, []);
        exports.sendDescEmbed(embChannel, null, subNext, embFooter, embImage, embColor);
    } else {
        exports.sendEmbed(embChannel, embTitle, embDesc, embFooter, embImage, embColor, []);
    }
};

exports.sendLog = function (embData, embColor) {
    const embTitle = embData[0];
    const embGuild = embData[1];
    const embAuthor = embData[2];
    const embFields = embData.splice(3);

    for (let i = embFields.length - 1; i >= 0; i--) {
        if (!embFields[i].name) embFields.splice(i, 1);
    }

    const embedTitleLower = embTitle.toLowerCase();

    const logChannel = exports.findChannel('vaebot-log', embGuild);
    if (logChannel) {
        const embFooter = exports.makeEmbedFooter(embAuthor);
        const embAvatar = exports.getAvatar(embAuthor);

        exports.sendEmbed(
            logChannel,
            exports.cloneObj(embTitle),
            null,
            exports.cloneObj(embFooter),
            exports.cloneObj(embAvatar),
            exports.cloneObj(embColor),
            exports.cloneObj(embFields));
    }

    const regex = /(\S*)(?:warn|mute|kick|ban)/i;
    const pre = (regex.exec(embedTitleLower) || [])[1];

    const modChannel = exports.findChannel('mod-logs', embGuild);
    if (modChannel && pre != null && pre != 'un' && !embedTitleLower.includes('revert') && !embedTitleLower.includes('cleared')) {
        const embFooter = exports.makeEmbedFooter(embAuthor);
        const embAvatar = exports.getAvatar(embAuthor);

        exports.sendEmbed(
            modChannel,
            exports.cloneObj(embTitle),
            null,
            exports.cloneObj(embFooter),
            exports.cloneObj(embAvatar),
            exports.cloneObj(embColor),
            exports.cloneObj(embFields));
    }
};

exports.getHourStr = function (d) {
    let valStr = (d.getHours()).toString();
    if (valStr.length < 2) valStr = `0${valStr}`;
    return valStr;
};

exports.getMinStr = function (d) {
    let valStr = (d.getMinutes()).toString();
    if (valStr.length < 2) valStr = `0${valStr}`;
    return valStr;
};

exports.getYearStr = function (d) {
    const valStr = (d.getFullYear()).toString();
    return valStr;
};

exports.getMonthStr = function (d) {
    let valStr = (d.getMonth() + 1).toString();
    if (valStr.length < 2) valStr = `0${valStr}`;
    return valStr;
};

exports.getDayStr = function (d) {
    let valStr = (d.getDate()).toString();
    if (valStr.length < 2) valStr = `0${valStr}`;
    return valStr;
};

/* function searchPartial(array, name, checkPartial) {
    if (checkPartial != false) {
        var firstChar = name.substr(0, 1);
        var endChar = name.substr(name.length-1, 1);
        if (firstChar == "\"" && endChar == "\"") {
            checkPartial = false;
            name = name.substring(1, name.length-1);
            if (name.length < 1) return;
        }
    }
    name = name.toLowerCase()
    var user = array.find(function(item) {
        var user = exports.getName(item);
        if (checkPartial != false ? exports.safe(user.toLowerCase()).includes(name) : exports.safe(user.toLowerCase()) == name) {
            return true;
        }
        return false;
    })
    return user;
} */

exports.searchUserPartial = function (container, name) {
    name = name.toLowerCase();
    return container.find((user) => {
        const username = exports.getName(user);
        if (user.id === name || exports.safe(username.toLowerCase()).includes(name)) {
            return true;
        }
        return false;
    });
};

exports.round = function (num, inc) {
    return inc == 0 ? num : Math.floor((num / inc) + 0.5) * inc;
};

exports.write = function (content, name) {
    FileSys.writeFile(name, content);
};

exports.remove = function (name) {
    FileSys.unlink(name);
};

exports.resolveUserMention = function (guild, id) {
    let resolvedUser;

    if (id == null) {
        id = guild;
        resolvedUser = client.users.get(id);
    } else {
        resolvedUser = exports.getMemberById(id, guild);
    }

    if (resolvedUser) return resolvedUser.toString();

    return `<@${id}>`;
};

exports.getNumMutes = async function (id, guild) {
    const pastMutes = await Data.getRecords(guild, 'mutes', { user_id: id });
    return pastMutes.length;
};

exports.historyToStringOld = function (num) {
    let timeHours = exports.round(num / 3600000, 0.1);
    timeHours = (timeHours >= 1 || timeHours == 0) ? timeHours.toFixed(0) : timeHours.toFixed(1);
    Util.log(`[RANDOM] timeHours: ${timeHours}`);
    return timeHours + (timeHours == 1 ? ' hour' : ' hours');
};

exports.historyToStringOld2 = function (num) {
    let timeHours = num / 3600000;
    Util.log(`[RANDOM] timeHours: ${timeHours}`);
    timeHours += (timeHours == 1 ? ' hour' : ' hours');
    return timeHours;
};

exports.historyToString = function (num) {
    const timeStr = exports.formatTime(num);
    return timeStr;
};

exports.matchWholeNumber = function (str) {
    let result = str.match(/^\d*(?:\.\d+)?$/);
    result = result ? result[0] : undefined;
    return result;
};

exports.getSafeId = function (id) {
    id = id.match(/\d+/);

    if (id == null) return undefined;

    return id[0];
};

exports.getMemberById = function (id, guild) {
    if (id == null || guild == null) return null;

    if (id.substr(0, 1) === '<' && id.substr(id.length - 1, 1) === '>') id = exports.getSafeId(id);

    if (id == null || id.length < 1) return null;

    return guild.members.get(id);
};

exports.isId = function (str) {
    let id = str.match(/^\d+$/);

    if (id == null) {
        id = str.match(/^<.?(\d+)>$/);
        if (id == null) return undefined;
        id = id[1];
    } else {
        id = id[0];
    }

    if (id.length < 17 || id.length > 19) return undefined;

    return id;
};

exports.getMatchStrength = function (fullStr, subStr) { // [v2.0]
    let value = 0;

    const fullStrLower = fullStr.toLowerCase();
    const subStrLower = subStr.toLowerCase();

    const nameMatch = fullStrLower.indexOf(subStrLower);

    if (nameMatch >= 0) {
        const filled = Math.min(subStr.length / fullStr.length, 0.999);
        value += 2 ** (2 + filled);

        const maxCaps = Math.min(subStr.length, fullStr.length);
        let numCaps = 0;
        for (let j = 0; j < maxCaps; j++) {
            if (subStr[j] === fullStr[nameMatch + j]) numCaps++;
        }
        const caps = Math.min(numCaps / maxCaps, 0.999);
        value += 2 ** (1 + caps);

        const totalPosition = fullStr.length - subStr.length;
        const perc = 1 - (totalPosition * nameMatch == 0 ? 0.001 : nameMatch / totalPosition);
        value += 2 ** perc;
    }

    return value;
};

exports.getDiscriminatorFromName = function (name) {
    const discrimPattern = /#(\d\d\d\d)$/gm;
    let discrim = discrimPattern.exec(name);
    discrim = discrim ? discrim[1] : null;
    return discrim;
};

exports.isNumeric = function (str) {
    return !isNaN(parseFloat(str)) && isFinite(str);
};

exports.entirelyNumbers = function (str) {
    return /^\d+$/.test(str);
};

exports.getBestMatch = function (container, key, name) { // [v3.0] Visible name match, real name match, length match, caps match, position match
    if (container == null) return undefined;

    let removeUnicode = false;

    if (key === 'username') {
        removeUnicode = true;
        const nameDiscrim = exports.getDiscriminatorFromName(name);
        if (nameDiscrim) {
            const namePre = name.substr(0, name.length - 5);
            const user = container.find(m => m.username === namePre && m.discriminator === nameDiscrim);
            if (user) return user;
        }
    }

    const origName = name.trim();

    if (removeUnicode) {
        name = name.replace(/[^\x00-\x7F]/g, '').trim();
        if (name.length == 0) {
            name = origName;
            removeUnicode = false;
        }
    }

    const str2Lower = name.toLowerCase();
    let strongest = null;

    container.forEach((obj) => {
        let realName = obj[key];
        if (removeUnicode) realName = realName.replace(/[^\x00-\x7F]/g, '');
        realName = realName.trim();
        const nameMatch = realName.toLowerCase().indexOf(str2Lower);

        const strength = { 'obj': obj };
        let layer = 0;

        if (nameMatch >= 0) {
            strength[layer++] = 1;

            // Util.log("(" + i + ") " + realName + ": " + value);
            const filled = Math.min(name.length / realName.length, 0.999);
            // Util.log("filled: " + filled);
            strength[layer++] = filled;

            const maxCaps = Math.min(name.length, realName.length);
            let numCaps = 0;
            for (let j = 0; j < maxCaps; j++) {
                if (name[j] === realName[nameMatch + j]) numCaps++;
            }
            const caps = Math.min(numCaps / maxCaps, 0.999);
            // const capsExp = (filledExp * 0.5 - 1 + caps);
            // Util.log("caps: " + caps + " (" + numCaps + "/" + maxCaps + ")");
            strength[layer++] = caps;

            const totalPosition = realName.length - name.length;
            const perc = 1 - (totalPosition * nameMatch == 0 ? 0.001 : nameMatch / totalPosition);
            // const percExp = (capsExp - 2 + perc);
            // Util.log("pos: " + perc + " (" + nameMatch + "/" + totalPosition + ")");
            strength[layer++] = perc;

            if (strongest == null) {
                strongest = strength;
            } else {
                for (let i = 0; i < layer; i++) {
                    if (strength[i] > strongest[i]) {
                        strongest = strength;
                        break;
                    } else if (strength[i] < strongest[i]) {
                        break;
                    }
                }
            }
        }
    });

    return strongest != null ? strongest.obj : undefined;
};

exports.getMemberByName = function (name, guild) { // [v3.0] Visible name match, real name match, length match, caps match, position match
    if (guild == null) return undefined;

    const nameDiscrim = exports.getDiscriminatorFromName(name);
    if (nameDiscrim) {
        const namePre = name.substr(0, name.length - 5);
        const member = guild.members.find(m => m.user.username === namePre && m.user.discriminator === nameDiscrim);
        if (member) return member;
    }

    let removeUnicode = true;
    const origName = name.trim();

    name = name.replace(/[^\x00-\x7F]/g, '').trim();

    if (name.length == 0) {
        name = origName;
        removeUnicode = false;
    }

    const str2Lower = name.toLowerCase();
    const members = guild.members;
    let strongest = null;

    if (str2Lower == 'vaeb') {
        const selfMember = members.get(vaebId);
        if (selfMember) return selfMember;
    }

    members.forEach((member) => {
        let realName = member.nickname != null ? member.nickname : exports.getName(member);
        if (removeUnicode) realName = realName.replace(/[^\x00-\x7F]/g, '');
        realName = realName.trim();
        let realstr2Lower = realName.toLowerCase();
        let nameMatch = realstr2Lower.indexOf(str2Lower);

        const strength = { 'member': member };
        let layer = 0;

        if (nameMatch >= 0) {
            strength[layer++] = 2;
        } else {
            realName = exports.getName(member);
            if (removeUnicode) realName = realName.replace(/[^\x00-\x7F]/g, '');
            realName = realName.trim();
            realstr2Lower = realName.toLowerCase();
            nameMatch = realstr2Lower.indexOf(str2Lower);
            if (nameMatch >= 0) {
                strength[layer++] = 1;
            }
        }

        if (nameMatch >= 0) {
            // Util.log("(" + i + ") " + realName + ": " + value);
            const filled = Math.min(name.length / realName.length, 0.999);
            // Util.log("filled: " + filled);
            strength[layer++] = filled;

            const maxCaps = Math.min(name.length, realName.length);
            let numCaps = 0;
            for (let j = 0; j < maxCaps; j++) {
                if (name[j] === realName[nameMatch + j]) numCaps++;
            }
            const caps = Math.min(numCaps / maxCaps, 0.999);
            // const capsExp = (filledExp * 0.5 - 1 + caps);
            // Util.log("caps: " + caps + " (" + numCaps + "/" + maxCaps + ")");
            strength[layer++] = caps;

            const totalPosition = realName.length - name.length;
            const perc = 1 - (totalPosition * nameMatch == 0 ? 0.001 : nameMatch / totalPosition);
            // const percExp = (capsExp - 2 + perc);
            // Util.log("pos: " + perc + " (" + nameMatch + "/" + totalPosition + ")");
            strength[layer++] = perc;

            if (strongest == null) {
                strongest = strength;
            } else {
                for (let i = 0; i < layer; i++) {
                    if (strength[i] > strongest[i]) {
                        strongest = strength;
                        break;
                    } else if (strength[i] < strongest[i]) {
                        break;
                    }
                }
            }
        }
    });

    return strongest != null ? strongest.member : undefined;
};

exports.getMemberByNameOld = function (name, guild) { // [v2.0] Visible name match, real name match, length match, caps match, position match //
    if (guild == null) return undefined;

    const nameDiscrim = exports.getDiscriminatorFromName(name);
    if (nameDiscrim) {
        const namePre = name.substr(0, name.length - 5);
        const member = guild.members.find(m => m.user.username === namePre && m.user.discriminator === nameDiscrim);
        if (member) return member;
    }

    let removeUnicode = true;
    const origName = name.trim();

    name = name.replace(/[^\x00-\x7F]/g, '');
    name = name.trim();

    if (name.length == 0) {
        name = origName;
        removeUnicode = false;
    }

    const str2Lower = name.toLowerCase();

    const members = guild.members;
    const matchStrength = [];
    let strongest = [0, undefined];

    members.forEach((member) => {
        let value = 0;

        let realName = member.nickname != null ? member.nickname : exports.getName(member);
        if (removeUnicode) realName = realName.replace(/[^\x00-\x7F]/g, '');
        realName = realName.trim();
        let realstr2Lower = realName.toLowerCase();
        let nameMatch = realstr2Lower.indexOf(str2Lower);

        if (nameMatch >= 0) {
            value += 2 ** 5;
        } else {
            realName = exports.getName(member);
            if (removeUnicode) realName = realName.replace(/[^\x00-\x7F]/g, '');
            realName = realName.trim();
            realstr2Lower = realName.toLowerCase();
            nameMatch = realstr2Lower.indexOf(str2Lower);
            if (nameMatch >= 0) {
                value += 2 ** 4;
            }
        }

        if (nameMatch >= 0) {
            // Util.log("(" + i + ") " + realName + ": " + value);
            const filled = Math.min(name.length / realName.length, 0.999);
            const filledExp = (2 + filled);
            // Util.log("filled: " + filled);
            value += 2 ** filledExp;

            const maxCaps = Math.min(name.length, realName.length);
            let numCaps = 0;
            for (let j = 0; j < maxCaps; j++) {
                if (name[j] === realName[nameMatch + j]) numCaps++;
            }
            const caps = Math.min(numCaps / maxCaps, 0.999);
            // const capsExp = (filledExp * 0.5 - 1 + caps);
            const capsExp = (1 + caps);
            // Util.log("caps: " + caps + " (" + numCaps + "/" + maxCaps + ")");
            value += 2 ** capsExp;

            const totalPosition = realName.length - name.length;
            const perc = 1 - (totalPosition * nameMatch == 0 ? 0.001 : nameMatch / totalPosition);
            // const percExp = (capsExp - 2 + perc);
            const percExp = (0 + perc);
            // Util.log("pos: " + perc + " (" + nameMatch + "/" + totalPosition + ")");
            value += 2 ** percExp;

            // Util.log(value);
            matchStrength.push([value, member]);
        }
    });

    for (let i = 0; i < matchStrength.length; i++) {
        const strength = matchStrength[i];
        if (strength[0] > strongest[0]) strongest = strength;
    }

    return strongest[1];
};

function getDataFromStringInner(str, funcs, returnExtra) {
    const mix = str.split(' ');
    const baseStart = mix.length - 1;
    let start = baseStart;
    let end = 0;
    let pos = start;
    let index = 0;
    let combine = [];
    const results = [];
    while (start >= 0) {
        const remainingFuncs = funcs.length - index - 1;
        const remainingTerms = baseStart - (start);
        if (remainingTerms < remainingFuncs) {
            start--;
            pos = start;
            combine = [];
            continue;
        }
        const chunk = mix[pos];
        if (chunk != null) combine.unshift(chunk);
        if (pos <= end) {
            const result = funcs[index](combine.join(' '), results);
            if (result != null) {
                /* if (index == 1) {
                    Util.log("[Z] " + combine.join(" "));
                    Util.log("[Z] " + remainingFuncs);
                    Util.log("[Z] " + remainingTerms);
                    Util.log("[Z] " + pos);
                    Util.log("[Z] " + start);
                    Util.log("[Z] " + end);
                    Util.log("[Z] " + result);
                } */
                results.push(result);
                index++;
                if (index >= funcs.length) {
                    if (returnExtra) {
                        combine = [];
                        for (let i = start + 1; i < mix.length; i++) {
                            const extra = mix[i];
                            if (extra != null) combine.push(extra);
                        }
                        let leftOver = '';
                        if (combine.length > 0) leftOver = combine.join(' ');
                        results.push(leftOver);
                    }
                    return results;
                }
                end = start + 1;
                if (end > baseStart) return undefined;
                start = baseStart;
            } else {
                start--;
            }
            pos = start;
            combine = [];
        } else {
            pos--;
        }
    }

    return undefined;
}

/*

    If optional parameters:
        -Fixed order
        -Only one function per optional parameter (so no need for putting each function in its own array)
        -Some optional parameters might only be an option if a previous optional parameter exists
        -Some optional parameters might not have a space before them
        -Don't know which parameters are being used

    ;cmd optionalParam1 name optionalParam2DependsOnOP1 optionalParam2 time

    const data = Util.getDataFromString(args,
        [
            {
                func: function (str) {
                    return Util.getMemberByMixed(str, guild) || Util.isId(str);
                },
            },
            {
                func: function (str) {
                    const timeHours = Util.matchWholeNumber(str);
                    return timeHours;
                },
                optional: true,
            },
            {
                func: function (str) {
                    let mult;
                    str = str.toLowerCase();
                    if (str.substr(str.length - 1, 1) == 's' && str.length > 2) str = str.substr(0, str.length - 1);
                    if (str == 'millisecond' || str == 'ms') mult = 1 / 60 / 60 / 1000;
                    if (str == 'second' || str == 's' || str == 'sec') mult = 1 / 60 / 60;
                    if (str == 'minute' || str == 'm' || str == 'min') mult = 1 / 60;
                    if (str == 'hour' || str == 'h') mult = 1;
                    if (str == 'day' || str == 'd') mult = 24;
                    if (str == 'week' || str == 'w') mult = 24 * 7;
                    if (str == 'month' || str == 'mo') mult = 24 * 30.42;
                    if (str == 'year' || str == 'y') mult = 24 * 365.2422;
                    return mult;
                },
                optional: true,
                requires: 1,
                prefix: / ?/,
            },
        ]
    , true);

    - Get all possible variations that match the prefixes

*/

exports.getDataFromString = function (str, funcSets, returnExtra) {
    if (typeof funcSets[0] == 'function') return getDataFromStringInner(str, funcSets, returnExtra);

    const mainData = getDataFromStringInner(str, funcSets[0], returnExtra);

    if (!mainData) return mainData;

    let lastExtra = mainData[funcSets[0].length];
    mainData.splice(funcSets[0].length);

    for (let i = 1; i < funcSets.length; i++) {
        if (!lastExtra || lastExtra.length == 0) break;
        const nowData = getDataFromStringInner(lastExtra, funcSets[i], returnExtra);
        if (nowData) {
            for (let j = 0; j < funcSets[i].length; j++) mainData.push(nowData[j]);
            lastExtra = nowData[funcSets[i].length];
        } else {
            for (let j = 0; j < funcSets[i].length; j++) mainData.push(null);
        }
    }

    mainData.push(lastExtra);

    return mainData;
};

function matchSet(str, funcSets, setIndex, data) {
    if (setIndex >= funcSets.length) {
        return true;
    }
    data.fail = Math.max(data.fail, setIndex);
    Util.log(`Loop ${setIndex}`);
    const set = funcSets[setIndex++];
    if (set.requires && data[set.requires] === undefined) {
        Util.log('Missing requires');
        if (!set.optional) return false;
        data.push(undefined);
        if (matchSet(str, funcSets, setIndex, data)) return true;
        data.pop();
    } else if (str.length === 0) {
        for (let i = setIndex - 1, s; s = funcSets[i]; i++) {
            if (!s.optional) return false;
        }
        return true;
    }
    const pMatch = str.match(set.prefix || (setIndex === 1 ? /\s*/ : /\s+/));
    Util.log('\t', pMatch, setIndex, set.prefix || (setIndex === 1 ? /\s*/ : /\s+/));
    if (!pMatch) return false;
    if (pMatch.index !== 0) return false;
    str = str.substr(pMatch[0].length);
    for (let i = str.length; i >= 0; i--) {
        const part = str.substr(0, i);
        // Util.log(`\tchecking part ${part}`);
        let good = true;
        if (set.match) {
            const mMatch = part.match(set.match);
            good = mMatch[0] == part;
        }
        const res = good && set.func(part);
        if (!res || !good) continue;
        // Util.log("Got", res, "for", data.length, "with length", i);
        data.push(res);
        const left = str.substr(i);
        if (matchSet(left, funcSets, setIndex, data)) {
            // Util.log("Reached the end, yeuy!");
            return true;
        }
        data.pop();
        if (set.longest) return false;
    }
    if (!set.optional) return false;
    return matchSet(str, funcSets, setIndex, data);
}

exports.getDataFromString2 = function (str, funcSets, returnExtra) {
    const data = [];
    data.fail = 0;
    const done = [];
    if (returnExtra) {
        funcSets.push({
            func(extra) {
                return extra;
            },
            optional: true,
        });
    }
    const success = matchSet(str, funcSets, 0, data, done);
    data.success = success;
    if (success) {
        for (let i = data.length; i < funcSets.length; i++) {
            data.push(undefined);
        }
        delete data.fail;
    }
    return data;
};

exports.clamp = function (num, minParam, maxParam) {
    let min = minParam;
    let max = maxParam;
    if (min == null) min = num;
    if (max == null) max = num;
    return Math.min(Math.max(num, min), max);
};

exports.noBlock = function (str) {
    return ` \`\`\`\n${str}\n\`\`\`\n `;
};

exports.toBoolean = function (str) {
    const result = (typeof (str) === 'boolean' ? str : (str === 'true' || (str === 'false' ? false : undefined)));
    return result;
};

exports.getNum = function (str, min, max) {
    const num = Number(str);
    if (isNaN(num)) return undefined;
    return exports.clamp(num, min, max);
};

exports.getInt = function (str, min, max) {
    const num = parseInt(str, 10); // Number() is better generally
    if (isNaN(num)) return undefined;
    return exports.clamp(num, min, max);
};

exports.isTextChannel = channel => channel.type === 'text';

exports.isVoiceChannel = channel => channel.type === 'voice';

exports.getTextChannels = guild => guild.channels.filter(exports.isTextChannel);

exports.getVoiceChannels = guild => guild.channels.filter(exports.isVoiceChannel);

exports.findChannel = function (nameParam, guild) {
    if (guild == null) return undefined;

    let name = nameParam;

    name = name.toLowerCase();
    const channels = exports.getTextChannels(guild);
    return channels.find(nowChannel => nowChannel.id === name || nowChannel.name.toLowerCase() === name);
};

exports.findVoiceChannel = function (nameParam, guild) {
    if (guild == null) return undefined;

    let name = nameParam;

    name = name.toLowerCase();
    const channels = exports.getVoiceChannels(guild);
    return channels.find(nowChannel => nowChannel.id === name || nowChannel.name.toLowerCase() === name);
};

exports.isAdmin = function (member) {
    const highestRole = member.highestRole;
    const guildRolesFromTop = exports.getGuildRoles(member.guild);

    for (let i = 0; i < guildRolesFromTop.length; i++) {
        const role = guildRolesFromTop[i];
        if (/\bmod/g.test(role.name.toLowerCase())) {
            return false;
        } else if (role.id == highestRole.id) {
            return true;
        }
    }

    return false;
};

exports.getRole = function (name, obj) {
    if (obj == null) return undefined;

    name = name.toLowerCase();

    const nameId = exports.getSafeId(name);
    const roles = obj.roles;
    if (roles.has(nameId)) return roles.get(nameId);

    const returnRole = exports.getBestMatch(roles, 'name', name);

    return returnRole;
};

exports.getHighestRole = member => member.highestRole;

exports.getPosition = function (speaker) {
    if (speaker == null || !exports.isObject(speaker) || speaker.guild == null) return undefined;

    if (speaker.id === speaker.guild.ownerID) return 999999999;

    return speaker.highestRole.position;
};

exports.getUserById = id => client.users.get(id);

exports.getUserByName = name => exports.getBestMatch(client.users, 'username', name);

exports.getUserByMixed = function (name) {
    let user = exports.getUserById(name);
    if (user == null) user = exports.getUserByName(name);
    return user;
};

exports.getMemberByMixed = function (name, guild) {
    if (guild == null) return undefined;
    let targetMember = exports.getMemberById(name, guild);
    if (targetMember == null) targetMember = exports.getMemberByName(name, guild);
    return targetMember;
};

exports.getMemberOrRoleByMixed = function (name, guild) {
    if (guild == null) return undefined;
    let targetObj = exports.getRole(name, guild);
    if (targetObj == null) targetObj = exports.getMemberById(name, guild);
    if (targetObj == null) targetObj = exports.getMemberByName(name, guild);
    return targetObj;
};

exports.getEitherByMixed = function (name, guild) {
    let user = exports.getMemberByMixed(name, guild);
    if (user == null) user = exports.getUserByMixed(name);
    return user;
};

exports.permEnabled = function (iPerms, permName) {
    const allowGeneral = iPerms.General;
    const allowText = iPerms.Text;
    const allowVoice = iPerms.Voice;

    if (has.call(allowGeneral, permName)) return allowGeneral[permName];
    if (has.call(allowText, permName)) return allowText[permName];
    if (has.call(allowVoice, permName)) return allowVoice[permName];

    return undefined;
};

exports.getRolePermissions = function (role, channel) {
    const outPerms = [];

    if (!channel) {
        for (let i = 0; i < exports.rolePermissions.length; i++) {
            const permName = exports.rolePermissions[i];
            if (role.hasPermission(permName)) {
                outPerms.push(permName);
            }
        }
    }

    return outPerms;
};

exports.getPermRating = function (guild, userOrRole) {
    if (userOrRole.hasPermission == null) return 0;

    const tempPermRating = Util.cloneObj(Util.permRating);

    let total = 0;
    let foundTop = false;

    for (let i = 0; i < tempPermRating.length; i++) {
        const permData = tempPermRating[i];
        if (userOrRole.hasPermission(permData[0], false)) {
            if (!foundTop && i < tempPermRating.length -1) {
                foundTop = true;

                let lastVal = null;
                let pointer0 = i + 1;
                let pointer1 = i + 1;
                let newVal = 5;

                // Util.log("found", permData[0]);

                for (let i2 = i + 1; i2 < tempPermRating.length; i2++) {
                    const nowVal = tempPermRating[i2][1];
                    if (lastVal == null) lastVal = nowVal;
                    if (nowVal !== lastVal) {
                        const numPoints = (pointer1 - pointer0) + 1;
                        newVal /= numPoints;
                        for (let n = pointer0; n <= pointer1; n++) {
                            tempPermRating[n][1] = newVal;
                        }
                        newVal /= 2;
                        pointer0 = i2;
                    }
                    pointer1 = i2;
                    lastVal = nowVal;
                }

                console.log('qqq', i, pointer0, pointer1);

                const numPoints = (pointer1 - pointer0) + 1;
                newVal /= numPoints;
                for (let n = pointer0; n <= pointer1; n++) {
                    tempPermRating[n][1] = newVal;
                }

                // Util.log(tempPermRating);
            }
            total += permData[1];
        }
    }

    total = Math.min(total, 100);

    return total;
};

exports.getMemberPowers = function (guild) {
    const sorted = [];
    const members = guild.members;
    for (let i = 0; i < members.size; i++) {
        const member = members[i];
        const power = exports.getPermRating(guild, member);
        let index = 0;
        for (index = 0; index < sorted.length; index++) {
            if (power >= sorted[index][1]) break;
        }
        sorted.splice(index, 0, [member, power]);
    }
    return sorted;
};

exports.strToPerm = function (strParam) {
    let str = strParam;

    str = exports.replaceAll(str.toUpperCase(), ' ', '_');

    let matchPerm = null;
    let matchTop = 0;

    for (const [permName] of Object.entries(exports.permissionsOrder)) {
        const matchScore = exports.getMatchStrength(permName, str);

        if (matchScore > matchTop) {
            matchTop = matchScore;
            matchPerm = permName;
        }
    }

    return matchPerm;
};

exports.setChannelPerms = function (channel, userOrRole, newPerms) {
    channel.overwritePermissions(userOrRole, newPerms)
        .catch(error => Util.log(`[E_SetChannelPerms] ${error}`));
};

// fetch more messages just like Discord client does
exports.fetchMessagesEx = function (channel, left, store, lastParam) {
    // message cache is sorted on insertion
    // channel.messages[0] will get oldest message
    let last = lastParam;

    if (last) last = last.id;
    return channel.fetchMessages({ limit: Math.min(left, 100), before: last })
        .then(messages => exports.onFetch(messages, channel, left, store));
};

function mirrorProperties(member) {
    const memberProto = Object.getPrototypeOf(member);
    const userProto = Object.getPrototypeOf(member.user);
    for (const key in member.user) {
        if (!Object.getOwnPropertyDescriptor(memberProto, key)) {
            Object.defineProperty(memberProto, key, {
                get() {
                    return this.user[key];
                },
                set(val) {
                    this.user[key] = val;
                },
            });
        }
    }
    const descriptors = Object.getOwnPropertyDescriptors(userProto);
    for (const key in descriptors) {
        if (!Object.getOwnPropertyDescriptor(memberProto, key)) {
            Object.defineProperty(memberProto, key, {
                get() {
                    return this.user[key];
                },
                set(val) {
                    this.user[key] = val;
                },
            });
        }
    }
}

exports.mergeUser = function (member) {
    // Util.log('Adding new proxy:');
    // Util.log(`Adding proxy to ${String(member)}`);

    /* const oldPrototype = Object.getPrototypeOf(member);

    if (Reflect.has(oldPrototype, 'proxyId')) return false;

    const nowProxyId = proxyId++;

    const userProxy = new Proxy({ proxyId: nowProxyId }, {
        get(storage, prop) {
            Util.log(`Getting ${prop} from ${nowProxyId}`);
            if (Reflect.has(member, prop)) return Reflect.get(member, prop);
            else if (Reflect.has(oldPrototype, prop)) return Reflect.get(oldPrototype, prop, member);
            else if (Reflect.has(member.user, prop)) return Reflect.get(member.user, prop);
            return storage[prop];
        },
        set(storage, prop, val) {
            Util.logc('Setter', 'Setting', prop, 'to', val);
            Reflect.set(member, prop, val); // Could 1st arg be oldPrototype and 4th be member?
            return val;
        },
        getPrototypeOf() {
            return Reflect.getPrototypeOf(member);
        },
    });
    
    Object.setPrototypeOf(member, userProxy); */

    mirrorProperties(member);

    return true;
};

exports.resolveMention = function (userResolvable) {
    if (userResolvable == null) return undefined;
    if (typeof user === 'string') return `<@${userResolvable}>`;
    return `${Util.getMostName(userResolvable)} (${userResolvable.toString()})`;
};

exports.fieldsToDesc = function (fields) {
    return `​\n${fields.filter(fieldData => fieldData.name != null).map(fieldData => `**${fieldData.name}${fieldData.value != null ? ': ' : ''}**${fieldData.value != null ? fieldData.value : ''}`).join('\n\n')}`;
};

exports.resolveUser = function (guild, userResolvable, canBeSystem) { // If user can be system, userResolvable as text would be the bot/system
    if (userResolvable == null) return undefined;

    const resolvedData = {
        member: userResolvable,
        user: userResolvable,
        id: userResolvable,
        mention: userResolvable,
        original: userResolvable,
    };

    let userType = 0; // Member
    let system = false;

    let wasId = false;

    if (typeof userResolvable === 'string') {
        const idMatch = exports.isId(userResolvable);
        if (idMatch) {
            userType = 1; // ID
            resolvedData.id = idMatch;
        } else {
            userType = 2; // Name or System
            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.
        }
    }

    // exports.logc('Admin1', `User type: ${userType} (canBeSystem ${canBeSystem || false})`);

    if (userType === 0) { // Member or User
        if (!userResolvable.guild) resolvedData.member = guild.members.get(resolvedData.user.id); // User
        else resolvedData.user = resolvedData.member.user; // Member
        resolvedData.id = resolvedData.user.id;
        resolvedData.mention = exports.resolveMention(resolvedData.member || resolvedData.user);
    } else if (userType === 1) { // Contained ID
        resolvedData.member = guild.members.get(resolvedData.id);
        resolvedData.user = resolvedData.member ? resolvedData.member.user : client.users.get(resolvedData.id);
        if (!resolvedData.user) { // Could be a name imitating an ID
            wasId = true;
            userType = 2;
        } else {
            resolvedData.mention = exports.resolveMention(resolvedData.member || resolvedData.user);
        }
    }

    if (userType === 2) { // Name or System (Separate if statement for branching from userType_1)
        if (system) { // VaeBot
            resolvedData.member = guild.members.get(selfId);
            resolvedData.user = resolvedData.member.user;
            resolvedData.id = selfId;
        } else { // Name
            resolvedData.member = exports.getMemberByMixed(userResolvable, guild);
            resolvedData.user = resolvedData.member ? resolvedData.member.user : exports.getUserByName(userResolvable);
            if (resolvedData.user) {
                resolvedData.id = resolvedData.user.id;
                resolvedData.mention = exports.resolveMention(resolvedData.member || resolvedData.user);
            } else if (!wasId) { // Didn't branch from id
                return 'User not found'; // No user or member
            }
        }
    }

    return resolvedData; // [Definite Values] ID: Always | Mention: Always | Member/User: All inputs except ID and Name
};

exports.onFetch = function (messagesParam, channel, leftParam, store) {
    let messages = messagesParam;
    let left = leftParam;

    messages = messages.array();

    if (!messages.length) return Promise.resolve();

    for (let i = 0; i < messages.length; i++) {
        store.push(messages[i]);
    }

    left -= messages.length;

    Util.log(`Received ${messages.length}, left: ${left}`);

    if (left <= 0) return Promise.resolve();

    return exports.fetchMessagesEx(channel, left, store, messages[messages.length - 1]);
};

exports.updateMessageCache = function (channel, speaker) {
    exports.fetchMessagesEx(channel, 100, [], channel.messages[0]).then(() => {
        if (speaker) {
            exports.sendDescEmbed(channel, 'Message Cache', 'Refreshed', exports.makeEmbedFooter(speaker), null, colGreen);
        }
    });
};

exports.isMember = function (userRes) {
    if (userRes.user != null) return true;
    return false;
};

exports.getUser = function (userRes) {
    if (!userRes) return null;
    return userRes.user || userRes;
};

exports.isMap = function (obj) {
    return obj instanceof Map;
};

exports.arrayToCollection = function (arr) {
    const newCol = new Discord.Collection();
    for (let i = 0; i < arr.length; i++) {
        const value = arr[i];
        newCol.set(value.id, value);
    }
    return newCol;
};

exports.chunkObj = function (obj, chunkSize) {
    const chunks = [];
    if (exports.isMap(obj)) {
        const objArray = obj.array();
        const size = obj.size;
        for (let i = 0; i < size; i += chunkSize) {
            const chunkArr = objArray.slice(i, i + chunkSize);
            const chunk = exports.arrayToCollection(chunkArr);
            chunks.push(chunk);
        }
    } else {
        const size = obj.length;
        for (let i = 0; i < size; i += chunkSize) {
            const chunk = obj.slice(i, i + chunkSize);
            chunks.push(chunk);
        }
    }
    return chunks;
};

exports.deleteMessages = function (messages) {
    let numMessages;
    let firstMessage;

    if (exports.isMap(messages)) {
        numMessages = messages.size;
        firstMessage = messages.first();
    } else {
        numMessages = messages.length;
        firstMessage = messages[0];
    }

    if (numMessages < 1) {
        Util.log('You must have at least 1 message to delete');
    } else {
        Util.log(`Deleting ${numMessages} messages`);
    }

    if (numMessages == 1) {
        firstMessage.delete()
            .catch((err) => {
                Util.log(`[E_DeleteMessages1] ${err}`);
            });
    } else {
        const chunks = exports.chunkObj(messages, 99);
        for (let i = 0; i < chunks.length; i++) {
            const chunk = chunks[i];
            firstMessage.channel.bulkDelete(chunk)
                .catch((err) => {
                    Util.log(`[E_DeleteMessages2] ${err}`);
                });
        }
    }
};

async function fetchMessagesInner(channel, remaining, foundMessages, lastMessage) {
    lastMessage = lastMessage != null ? lastMessage.id : undefined;

    const messages = await channel.fetchMessages({ limit: Math.min(remaining, 99), before: lastMessage });

    if (!messages || messages.size == 0) return foundMessages;

    const messagesArr = messages.array();

    for (let i = 0; i < messagesArr.length; i++) {
        foundMessages.push(messagesArr[i]);
    }

    remaining -= messagesArr.length;

    if (remaining <= 0) return foundMessages;

    return fetchMessagesInner(channel, remaining, foundMessages, messagesArr[messagesArr.length - 1]);
}

exports.fetchMessages = async function (channel, numScan, checkFunc) {
    if (!checkFunc) checkFunc = (() => true);

    const scanMessages = await fetchMessagesInner(channel, numScan, [], null);
    const foundMessages = scanMessages.filter(checkFunc);
    Util.log(`Num Messages Found: ${foundMessages.length}`);
    return foundMessages;
};

exports.banMember = function (member, moderator, reason, tempEnd) {
    const guild = member.guild;
    const memberId = member.id;
    const memberMostName = exports.getMostName(member);

    if (reason == null || reason.length < 1) reason = 'No reason provided';

    let modFullName = moderator;
    if (exports.isObject(moderator)) modFullName = exports.getFullName(moderator);

    const linkedGuilds = Data.getLinkedGuilds(member.guild);

    for (let i = 0; i < linkedGuilds.length; i++) {
        const linkedGuild = linkedGuilds[i];
        linkedGuild.ban(member.id, { days: 0, reason })
            .then((userResolvable) => {
                Util.logc('AddBan1', `Link-added ban for ${exports.getMention(userResolvable, true)} @ ${linkedGuild.name}`);
            })
            .catch(exports.logErr);
    }

    const sendLogData = [
        `Guild ${tempEnd ? 'Temporary ' : ''}Ban`,
        guild,
        member,
        { name: 'Username', value: member.toString() },
        { name: 'Moderator', value: member.toString() },
        { name: 'Ban Reason', value: reason },
    ];

    if (tempEnd) sendLogData.push({ name: 'Ban Ends', value: tempEnd });

    exports.sendLog(sendLogData, colAction);

    // Trello.addCard(member.guild, 'Bans', memberMostName, {
    //     'User ID': memberId,
    //     'Moderator': modFullName,
    //     'Reason': `[TempBan] ${reason}`,
    // });

    return true;
};

exports.kickMember = function (member, moderator, reason) {
    // const memberId = member.id;
    // const memberMostName = exports.getMostName(member);

    if (reason == null || reason.length < 1) reason = 'No reason provided';

    // let modFullName = moderator;
    // if (exports.isObject(moderator)) modFullName = exports.getFullName(moderator);

    member.kick()
        .catch(console.error);

    // Trello.addCard(member.guild, 'Kicks', memberMostName, {
    //     'User ID': memberId,
    //     'Moderator': modFullName,
    //     'Reason': reason,
    // });
};

exports.getChanges = function (str1, str2) {
    const len1 = str1.length;
    const len2 = str2.length;
    const matrix = []; // len1+1, len2+1

    if (len1 == 0) {
        return len2;
    } else if (len2 == 0) {
        return len1;
    } else if (str1 == str2) {
        return 0;
    }

    for (let i = 0; i <= len1; i++) {
        matrix[i] = {};
        matrix[i][0] = i;
    }

    for (let j = 0; j <= len2; j++) {
        matrix[0][j] = j;
    }

    for (let i = 1; i <= len1; i++) {
        for (let j = 1; j <= len2; j++) {
            let cost = 1;

            if (str1[i - 1] == str2[j - 1]) {
                cost = 0;
            }

            matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
        }
    }

    return matrix[len1][len2];
};

exports.getLines = function (str) {
    return str.split(/\r\n|\r|\n/);
};

exports.getLines2 = function (str) {
    return exports.chunkString(str, 153); // Take 153 characters as average line length on average 1080p window
};

exports.simplifyStr = function (str) {
    str = str.toLowerCase();
    const strLength = str.length;
    const midPoint = (str.length / 2) + 1;
    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)
        const sub = str.substr(0, i); // Get the substring from start of length i
        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)
        const repeatedSub = sub.repeat(num); // Repeat the substring floor(num) times
        if (repeatedSub == str) return [sub, num]; // If repeatedSub is equal to original string, return substring and repetition count
    }
    return [str, 1]; // Return substring and repetition count
};

exports.simplifyStrHeavy = function (str) {
    // Assume str is already lowercase
    str = str.replace(/\s/g, '');
    const strLength = str.length;
    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
    let numCanChange = 0;
    let nextInc = 2;
    for (let i = 1; i < midPoint; i++) { // Increments for number of characters in the string stopping before the midpoint
        const sub = str.substr(0, i); // Get the str substring of length i
        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)
        const repeatedSub = sub.repeat(num); // Repeat the substring num times
        const nowMaxChanges = Math.min(numCanChange * num, strLength / 2); // Get number of allowed alterations between strings to be classed as similar
        if (exports.getChanges(repeatedSub, str) <= nowMaxChanges) return [sub, num]; // If repeatedSub is similar to original string, return substring and repetition count
        if (i >= nextInc) { // Update multiplier for nowMaxChanges when length is large enough
            numCanChange++;
            nextInc *= 2;
        }
    }
    return [str, 1]; // Return substring and repetition count
};

exports.similarStrings = function (str1, str2) {
    str1 = str1.toLowerCase().trim();
    str2 = str2.toLowerCase().trim();

    // Get number of allowed alterations between strings to be classed as similar
    let maxChanges = Math.floor(Math.min(Math.max(Math.max(str1.length, str2.length) / 3, Math.abs(str2.length - str1.length)), 6));

    // Check if the original strings are similar (have a number of alterations between them [levenshtein distance] less/equal to maxChanges)
    if (exports.getChanges(str1, str2) <= maxChanges) return true;

    // Simplify both strings removing repeated similar data
    [str1] = exports.simplifyStrHeavy(str1); // Reduce similar repeated strings (e.g. dog1dog2dog3 becomes dog1)
    [str2] = exports.simplifyStrHeavy(str2);

    // Update maxChanges for new string lengths
    maxChanges = Math.floor(Math.min(Math.max(Math.max(str1.length, str2.length) / 3, Math.abs(str2.length - str1.length)), 6));

    // Check if simplified strings are similar
    return exports.getChanges(str1, str2) <= maxChanges;
};

exports.similarStringsStrict = function (str1, str2) {
    str1 = str1.toLowerCase().trim();
    str2 = str2.toLowerCase().trim();

    const minStr = str1.length < str2.length ? str1 : str2;
    const maxStr = str1.length < str2.length ? str2 : str1;

    // if (minStr.replace(/[|. ,/#!$%^&*;:{}=\-_`~()]/g, '').length < 10 && (str1.match(/\s/g) || []).length < 2 && (str2.match(/\s/g) || []).length < 2) return false;

    if ((minStr.length < 4) && (minStr.length != 3 || maxStr.length < 4)) return str1 == str2;

    // Get number of allowed alterations between strings to be classed as similar
    let maxChanges = maxStr.length / 3;
    maxChanges = Math.max(maxChanges, Math.abs(str2.length - str1.length));
    maxChanges = Math.min(maxChanges, 6);
    maxChanges = Math.floor(maxChanges);

    // const maxChanges = Math.floor(Math.min(Math.max(Math.max(str1.length, str2.length) / 3, Math.abs(str2.length - str1.length)), 6));

    // Check if the original strings are similar (have a number of alterations between them [levenshtein distance] less/equal to maxChanges)
    if (exports.getChanges(str1, str2) <= maxChanges) return true;

    return false;
};

exports.isSpam = function (content) {
    if (exports.getLines2(content).length >= 500) return true; // If the message contains too many chunk-lines (so characters) consider it spam

    const strLines = exports.getLines(content);

    if (strLines.length > 1) {
        let numSimilar = 0;
        let mostCommon = strLines[0];
        let numLines = strLines.length;

        for (let i = 1; i < strLines.length; i++) {
            const nowStr = strLines[i];
            if (nowStr.trim().length < 1) {
                numLines--;
                continue;
            }
            const compStr = numSimilar === 0 ? strLines[i - 1] : mostCommon;
            if (exports.similarStrings(nowStr, compStr)) {
                if (numSimilar === 0) mostCommon = nowStr;
                numSimilar++;
            } else if (i === 2 && numSimilar === 0 && exports.similarStrings(nowStr, mostCommon)) {
                numSimilar++;
            }
        }

        if (numSimilar >= 3 || numSimilar == numLines) return true;
    }

    // ////////////////////////////////////////////////////////////////////////////////////////

    const pattern = /\S+/g; // Pattern for finding all matches for continuous substrings of non space characters
    const matches = content.match(pattern); // Get the matches

    for (let i = 0; i < matches.length; i++) { // Iterate through the matches
        // Util.log(`---${i + 1}---`);
        for (let j = 0; j < matches.length; j++) { // Iterate through the matches again in each iteration for concatenating multiple adjacent matches
            let long = matches[j]; // Get the substring on non space characters
            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
            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
            // Util.log(long);
            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
            // sub: The substring that can be repeated to make up the long var
            // num: The number of times the substring needs to be repeated to make up the long var
            const subLength = sub.length; // The number of characters in the repeated substring
            let triggered = false; // Initialise spam detection variable
            if (num >= 3) { // Only check for spam if substring has been repeated at least 3 times
                if (subLength == 1) { // 1 character in substring, 100+ repetitions
                    if (num >= 100) triggered = true; // Is spam
                } else if (subLength == 2) { // 2 characters in substring, 20+ repetitions
                    if (num >= 20) triggered = true; // Is spam
                } else if (subLength == 3) { // 3 characters in substring, 7+ repetitions
                    if (num >= 7) triggered = true; // Is spam
                } else if (subLength <= 5) { // 4-5 characters in substring, 4+ repetitions
                    if (num >= 4) triggered = true; // Is spam
                } else { // 6+ characters in substring, 3+ repetitions
                    triggered = true; // Is spam
                }
            }
            if (triggered) { // If it was counted as spam
                Util.log(long, ':', sub, ':', num);
                return true; // Return true (spam)
            }
        }
    }

    return false; // Return false (not spam)
};

exports.reverse = function (str) {
    return str.split('').reverse().join('');
};

exports.format = function (...args) {
    const newArgs = [];

    for (let i = 0; i < args.length; i++) {
        newArgs[i] = exports.cloneObjDepth(args[i], 2);
    }

    return NodeUtil.format(...newArgs);
};

let lastTag = null;
let lastWasEmpty = true;

function postOutString(args, startNewline) {
    const nowDate = new Date();
    nowDate.setHours(nowDate.getHours() + 1);

    let out = (startNewline && !lastWasEmpty) ? '\n' : '';
    out += NodeUtil.format(...args);

    let outIndex = out.search(/[^\n\r]/g);
    if (outIndex === -1) outIndex = 0;

    out = out.slice(0, outIndex) + DateFormat(nowDate, '| dd/mm/yyyy | HH:MM | ') + out.slice(outIndex);

    console.log(out);

    lastWasEmpty = /[\n\r]\s*$/.test(out);
}

exports.log = function (...args) {
    postOutString(args, true);
    lastTag = null;
};

exports.logc = function (...args) {
    const nowTag = String(args.splice(0, 1)).toLowerCase();
    const isNew = lastTag != nowTag;
    postOutString(args, isNew);
    lastTag = nowTag;
};

exports.logn = function (...args) {
    postOutString(args, false);
    lastTag = null;
};

exports.logErr = function (...args) {
    args.unshift('[ERROR]');
    postOutString(args, true);
    lastTag = null;
};

const getAuditLogChunk = 1;
const getAuditLogMax = 4; // This is completely pointless ...?

async function getAuditLogRec(guild, auditLogOptions, userData, checkedLogs) {
    if (checkedLogs.length >= getAuditLogMax) return null;
    const entries = (await guild.fetchAuditLogs(auditLogOptions)).entries;
    const outLog = entries.find(log => userData.nowTimestamp - log.createdTimestamp < userData.maxElapsed && log.target.id === userData.target.id); // Needs to check latest first
    if (outLog) return outLog;
    entries.forEach((log) => { if (!checkedLogs.includes(log.id)) checkedLogs.push(log.id); });
    auditLogOptions.limit++;
    userData.maxElapsed += 700;
    return getAuditLogRec(guild, auditLogOptions, userData, checkedLogs);
}

exports.getAuditLog = async function (guild, type, userData) {
    userData.executor = Util.resolveUser(guild, userData.executor);

    if (!userData.target) {
        const auditLogOptions = { type, user: userData.executor, limit: 1 };
        return (await guild.fetchAuditLogs(auditLogOptions)).entries.first();
    }

    userData.target = Util.resolveUser(guild, userData.target);
    userData.maxElapsed = userData.maxElapsed || 3000;

    const nowTimestamp = +new Date();
    userData.nowTimestamp = nowTimestamp;

    const auditLogOptions = { type, user: userData.executor, limit: getAuditLogChunk };
    return getAuditLogRec(guild, auditLogOptions, userData, []);
};


================================================
FILE: commands/administrator/Actions.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';actions', ';guild actions', ';all actions'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Output all actions that can be used in ;link',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel) => {
        const sendEmbedFields = [];

        for (const actionName in Events.Actions) {
            if (!has.call(Events.Actions, actionName)) continue;

            sendEmbedFields.push({ name: actionName, value: '​', inline: false });
        }

        Util.sendEmbed(channel, 'Actions', 'All actions which can be used in ;link\n​', Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);
    },
});


================================================
FILE: commands/administrator/AddAction.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';addaction ', ';linkaction ', ';createaction ', ';action '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Creates an action to be used in ;link',

    args: '',

    example: 'EchoMessage (guild, eventName, actionArgs, eventArgs) => { Util.print(channel, ...eventArgs[3]) };',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel) => {
        const spaceIndex = args.indexOf(' ');
        if (spaceIndex == -1) return Util.commandFailed(channel, speaker, 'Invalid parameters');

        const actionName = args.substring(0, spaceIndex);
        const actionFuncStr = args.substring(spaceIndex + 1);

        const evalStr = `Events.Actions.${actionName} = ${actionFuncStr}`;

        Util.log(evalStr);

        eval(evalStr);

        Util.sendDescEmbed(channel, 'Added Action', `Added action ${actionName} for linking`, Util.makeEmbedFooter(speaker), null, colGreen);

        return true;
    },
});


================================================
FILE: commands/administrator/AddAuto.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';addauto ', ';adda ', ';addtoauto '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Adds a song to the music auto-playlist',

    args: '[song_name]',

    example: 'gonna give you up',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        // if (args.includes('http')) {
        //     let songId = /[^/=]+$/.exec(args);
        //     if (songId != null && songId[0]) {
        //         songId = songId[0];
        //         index.YtInfo.getById(songId, (error, result) => {
        //             const songData = result.items[0];
        //             if (songData != null) {
        //                 let autoPlaylist = Data.guildGet(guild, Data.playlist, 'songs');
        //                 if (autoPlaylist == null) {
        //                     autoPlaylist = [];
        //                     Data.guildSet(guild, Data.playlist, 'songNum', 0);
        //                     Data.guildSet(guild, Data.playlist, 'songs', autoPlaylist);
        //                 }
        //                 autoPlaylist.push([songData, speaker]);
        //                 Data.guildSaveData(Data.playlist);
        //                 Util.sendDescEmbed(channel, `[${autoPlaylist.length}] Auto-Playlist Appended`, songData.snippet.title, Util.makeEmbedFooter(speaker), null, colGreen);
        //             } else {
        //                 Util.print(channel, 'Audio not found');
        //             }
        //         });
        //     } else {
        //         Util.print(channel, 'Incorrect format for URL');
        //     }
        // } else {
        //     index.YtInfo.search(args, 6, (error, result) => {
        //         if (error) {
        //             Util.print(channel, error);
        //         } else {
        //             const items = result.items;
        //             let hasFound = false;
        //             for (let i = 0; i < items.length; i++) {
        //                 const songData = items[i];
        //                 if (songData != null && has.call(songData, 'id') && songData.id.kind == 'youtube#video') {
        //                     hasFound = true;
        //                     let autoPlaylist = Data.guildGet(guild, Data.playlist, 'songs');
        //                     if (autoPlaylist == null) {
        //                         autoPlaylist = [];
        //                         Data.guildSet(guild, Data.playlist, 'songNum', 0);
        //                         Data.guildSet(guild, Data.playlist, 'songs', autoPlaylist);
        //                     }
        //                     autoPlaylist.push([songData, speaker]);
        //                     Data.guildSaveData(Data.playlist);
        //                     Util.sendDescEmbed(channel, `[${autoPlaylist.length}] Auto-Playlist Appended`, songData.snippet.title, Util.makeEmbedFooter(speaker), null, colGreen);
        //                     break;
        //                 }
        //             }
        //             if (!hasFound) {
        //                 Util.print(channel, 'Audio not found');
        //             }
        //         }
        //     });
        // }
        // // Data.guildSet(guild, playlist, args);
    },
});


================================================
FILE: commands/administrator/AddAutoRole.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";setautorole ", ";addautorole ", ";arole "],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Set a new autorole",

    args: "([role_name]) ([auto_role_name])",

    example: "for-hire hire",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var data = Util.getDataFromString(args, [
            function(str, results) {
                return Util.getRole(str, guild);
            },
        ], true);
        if (!data) return Util.commandFailed(channel, speaker, "Role not found");
        var role = data[0];
        var name = data[1].toLowerCase();
        if (Util.getPosition(speaker) <= role.position) {
            Util.commandFailed(channel, speaker, "Role has equal or higher rank");
            return;
        }
        Data.guildSet(guild, Data.autoRoles, name, role.name);
        Util.print(channel, "Added the autorole", Util.fix(name), "for the", role.name, "role");
    }
});

================================================
FILE: commands/administrator/AddRole.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';addrole '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Add a role to a user',

    args: '([@user] | [id] | [name]) ([role_name])',

    example: 'vae mod',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const data = Util.getDataFromString(
            args,
            [
                function (str) {
                    return Util.getMemberByMixed(str, guild);
                },
                function (str) {
                    return Util.getRole(str, guild);
                },
            ],
            false,
        );

        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');

        const user = data[0];
        const role = data[1];

        if (role.name == 'Vashta-Owner' && !Util.isAdmin(speaker)) {
            return Util.commandFailed(channel, speaker, 'You are not allowed to add this role');
        }

        if (speaker != user && Util.getPosition(speaker) <= Util.getPosition(user)) {
            Util.commandFailed(channel, speaker, 'User has equal or higher rank');
            return false;
        }

        if (Util.getPosition(speaker) <= role.position) {
            Util.commandFailed(channel, speaker, 'Role has equal or higher rank');
            return false;
        }
        user.addRole(role).catch(console.error);

        const sendEmbedFields = [
            { name: 'Username', value: Util.getMention(user) },
            // {name: "Moderator", value: Util.getMention(speaker)},
            { name: 'Role Name', value: role.name },
        ];
        Util.sendEmbed(
            channel, // Channel Object
            'Assigned Role', // Title String
            null, // Description String
            Util.makeEmbedFooter(speaker), // Username + ID String
            Util.getAvatar(user), // Avatar URL String
            colGreen, // Color Number
            sendEmbedFields,
        );

        return true;
    },
});


================================================
FILE: commands/administrator/Alert.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';alert ', ';dm ', ';announce '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Sends a DM to everyone in the guild with a certain role',

    args: '([@role] | [id] | [name]) ([message])',

    example: 'subscribers new update today',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        if (speaker.id !== guild.ownerID && speaker.id !== vaebId) return Util.commandFailed(channel, speaker, 'Command is owner-only');

        const data = Util.getDataFromString(args, [
            function (strOld) {
                let str = strOld;
                if (str[0] === '@') str = str.substring(1);
                return Util.getRole(str, guild);
            },
            function (str) {
                return str;
            },
        ], false);

        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');

        const role = data[0];
        const message = data[1];

        const title = `Alert | ${Util.getMostName(speaker)} | ${guild.name}`;
        const footer = Util.makeEmbedFooter(speaker);

        guild.members.forEach((member) => {
            if (!Util.hasRole(member, role) || member.id === selfId) return;
            Util.log(`Sent DM to ${Util.getFullName(member)}`);
            Util.sendDescEmbed(member, title, message, footer, null, colBlue);
        });

        return undefined;
    },
});


================================================
FILE: commands/administrator/AllInfo.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';allinfo'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Get guild, role, channel and permission info in one huge set of messages',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const outStr = ['**Guild Info**\n```'];
        outStr.push(`Name: ${guild.name}`);
        outStr.push(`ID: ${guild.id}`);
        outStr.push(`Owner: ${Util.getName(guild.owner)} (${guild.ownerID})`);
        outStr.push(`Icon: ${guild.iconURL}`);
        outStr.push(`AFK timeout: ${guild.afkTimeout} seconds`);
        outStr.push(`Region: ${guild.region}`);
        outStr.push(`Member count: ${guild.memberCount}`);
        outStr.push(`Created: ${guild.createdAt}`);
        outStr.push(`Main channel: #${guild.defaultChannel.name}`);
        outStr.push(`Emojis: ${guild.emojis.size > 0 ? JSON.stringify(guild.emojis.array()) : 'null'}`);
        outStr.push('```');
        outStr.push('**Guild Text Channels**\n```');
        Util.getTextChannels(guild).forEach((value) => {
            outStr.push(
                `Channel: ${value.name} (${value.id}) | Topic: ${value.topic} | Position: ${value.position} | Created: ${value.createdAt}`,
            );
        });
        outStr.push('```');
        outStr.push('**Guild Voice Channels**\n```');
        Util.getVoiceChannels(guild).forEach((value) => {
            outStr.push(
                `Channel: ${value.name} (${value.id}) | Topic: ${value.topic} | Position: ${value.position} | Created: ${
                    value.createdAt
                } | Bitrate: ${value.bitrate}`,
            );
        });
        outStr.push('```');
        outStr.push('**Guild Roles**\n```');
        guild.roles.forEach((value) => {
            outStr.push(
                `Role: ${value.name} (${value.id}) | Position: ${value.position} | Mentionable: ${value.mentionable} | Color: ${
                    value.color
                }`,
            );
        });
        outStr.push('```');
        outStr.push('**Guild Permissions**\n```');
        guild.roles.forEach((value) => {
            outStr.push(`Role: ${value.name} (${value.id})`);
            outStr.push(JSON.stringify(Util.getRolePermissions(value)));
            outStr.push('');
        });
        outStr.push('');
        outStr.push('-END-');
        outStr.push('```');
        Util.print(channel, outStr.join('\n'));
    },
});


================================================
FILE: commands/administrator/Ban.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';ban ', ';banhammer ', ';permaban '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Ban a user from the guild',

    args: '([user_resolvable) (OPTIONAL: [reason])',

    example: 'vaeb being weird',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        args = args.trim();

        // const highestRoleLower = speaker.highestRole.name.toLowerCase();
        // 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');

        const data = Util.getDataFromString(
            args,
            [
                function (str) {
                    return Util.getMemberByMixed(str, guild) || Util.isId(str);
                },
            ],
            true,
        );
        if (!data) return Util.commandFailed(channel, speaker, 'User not found');

        const target = data[0];
        const reason = data[1];

        Admin.addBan(guild, channel, target, speaker, { reason });

        // if (guild.id == '168742643021512705') index.dailyBans.push([targId, `${targName}#${target.discriminator}`, reason]);

        return true;
    },
});


================================================
FILE: commands/administrator/Calm.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';calm', ';calmchat', ';slow', ';slowchat'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Slows down chat speed',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    /* func: (cmd, args, msgObj, speaker, channel, guild) => {
        if (speaker.id != guild.ownerID && speaker.id != vaebId) return Util.commandFailed(channel, speaker, "Command is owner-only");
        
        if (index.slowChat[guild.id]) return;

        var chatQueue = [];

        index.chatQueue[guild.id] = chatQueue;

        index.slowInterval[guild.id] = setInterval(function() {
            if (chatQueue.length < 1) return;

            var msgObj = (chatQueue.splice(0, 1))[0];

            var msgChannel = msgObj.channel;
            var msgGuild = msgObj.guild;
            var msgSpeaker = msgObj.member;
            var msgContent = msgObj.content;
            var msgCreatedAt = msgObj.createdAt;

            // Util.sendEmbed(msgChannel, Util.getMostName(msgSpeaker), msgContent, Util.makeEmbedFooter(msgSpeaker, msgCreatedAt), null, colGreen, null);
            msgChannel.send(Util.getMostName(msgSpeaker) + ": " + msgContent).catch(console.error);
        }, index.calmSpeed);

        index.slowChat[guild.id] = true;
    } */

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        // if (speaker.id != guild.ownerID && speaker.id != vaebId) return Util.commandFailed(channel, speaker, 'Command is owner-only');

        if (index.slowChat[guild.id]) return Util.log('Slow is already active');

        index.chatNext[guild.id] = +new Date() + index.calmSpeed;

        index.slowChat[guild.id] = true;

        return undefined;
    },
});


================================================
FILE: commands/administrator/CheckPerms.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";perms ", ";checkperms ", ";getperms "],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Get guild and channel permissions for a user",

    args: "([@user] | [id] | [name]) ([reason])",

    example: "vae",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var member = Util.getMemberByMixed(args, guild);
        if (member == null) {
            Util.commandFailed(channel, speaker, "User not found");
            return;
        }
        var guildPerms = member.permissions.serialize();
        var channelPerms = member.permissionsIn(channel).serialize();

        var guildValue = [];
        var channelValue = [];

        for (let permName in guildPerms) {
            if (!guildPerms.hasOwnProperty(permName)) continue;
            let hasPerm = guildPerms[permName];
            if (!hasPerm) continue;
            guildValue.push(permName);
        }

        for (let permName in channelPerms) {
            if (!channelPerms.hasOwnProperty(permName)) continue;
            let hasPerm = channelPerms[permName];
            if (!hasPerm) continue;
            channelValue.push(permName);
        }

        Util.sortPerms(guildValue);
        Util.sortPerms(channelValue);

        var sendEmbedFields = [
            {name: "Guild Permissions", value: "​\n" + guildValue.join("\n\n"), inline: false},
            {name: "Channel Permissions", value: "​\n" + channelValue.join("\n\n"), inline: false}
        ];

        Util.sendEmbed(channel, Util.capitalize2(member.displayName + "'s Permissions"), null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);
    }
});

================================================
FILE: commands/administrator/ClearMutes.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';clearmutes ', ';cleanslate ', ';clearhistory '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: "Clear a user's mute history and unmute them if they are muted",

    args: '([@user] | [id] | [name])',

    example: 'vae',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        args = args.trim();

        Admin.clearMutes(guild, channel, args, speaker);
    },
});


================================================
FILE: commands/administrator/Events.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";events", ";guild events", ";all events"],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Output all events that can be used in ;link",

    args: "",

    example: "",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var sendEmbedFields = [];

        sendEmbedFields.push({name: "MessageCreate", value: "​", inline: false});
        sendEmbedFields.push({name: "MessageDelete", value: "​", inline: false});
        sendEmbedFields.push({name: "UserJoin", value: "​", inline: false});
        sendEmbedFields.push({name: "UserLeave", value: "​", inline: false});
        sendEmbedFields.push({name: "UserMute", value: "​", inline: false});
        sendEmbedFields.push({name: "UserUnMute", value: "​", inline: false});
        sendEmbedFields.push({name: "UserUnMute", value: "​", inline: false});
        sendEmbedFields.push({name: "UserRoleAdd", value: "​", inline: false});
        sendEmbedFields.push({name: "UserRoleRemove", value: "​", inline: false});
        sendEmbedFields.push({name: "UserNicknameUpdate", value: "​", inline: false});

        Util.sendEmbed(channel, "Events", "All events which can be used in ;link\n​", Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);
    }
});

================================================
FILE: commands/administrator/Fire.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';fire '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Set fire to the server',

    args: '(on | off)',

    example: 'on',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        if (!args || !/on|true|off|false/i.test(args)) return;

        const isOn = /on|true/i.test(args);

        if (isOn) {
            guild.channels.forEach((c) => {
                if (!c.name.endsWith('🔥')) c.setName(`${c.name}🔥`);
            });
        } else {
            guild.channels.forEach((c) => {
                if (c.name.endsWith('🔥')) c.setName(c.name.substr(0, c.name.length - 1));
            });
        }
    },
});


================================================
FILE: commands/administrator/FixRoles.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';fix roles'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Fix new role permissions',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const defRole = Util.getRole('@everyone', guild);
        const sendRole = Util.getRole('SendMessages', guild);
        const guildRoles = guild.roles;
        const defNew = {};
        const sendNew = {};

        // const newRolePerms = [
        //     'CHANGE_NICKNAME',
        //     'EMBED_LINKS',
        //     'SEND_MESSAGES',
        //     'VIEW_CHANNEL',
        //     'READ_MESSAGE_HISTORY',
        //     'ADD_REACTIONS',
        //     'USE_EXTERNAL_EMOJIS',
        //     'CONNECT',
        //     'SPEAK',
        //     'USE_VAD',
        // ];

        const newRolePerms = ['CHANGE_NICKNAME', 'SEND_MESSAGES', 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'CONNECT', 'SPEAK', 'USE_VAD'];

        const newRolePermsObj = Util.arrayToObj(newRolePerms);

        sendRole.setPermissions(newRolePerms).catch(error => Util.log(`[E_FixRoles1] ${error}`));

        guildRoles.forEach((role) => {
            if (role.id == sendRole.id) return;
            const newPerms = [];
            const rolePerms = role.serialize();
            for (const permName in rolePerms) {
                if (!rolePerms.hasOwnProperty(permName)) continue;
                if (!newRolePermsObj.hasOwnProperty(permName) && rolePerms[permName] == true) {
                    newPerms.push(permName);
                }
            }
            role.setPermissions(newPerms).catch(error => Util.log(`[E_FixRoles2] ${error}`));
        });

        /* var textChannels = Util.getTextChannels(guild);
        for (var i = 0; i < textChannels.length; i++) {
            var channel = textChannels[i];
            Util.setChannelPerms(channel, defRole, defNew);
            Util.setChannelPerms(channel, sendRole, sendNew);
        } */
    },
});


================================================
FILE: commands/administrator/GetLinks.js
================================================
/*

    [
        [
            eventName,
            [actionName, [actionArgs]],
            [actionName, [actionArgs]],
            [actionName, [actionArgs]]
        ],
        [
            eventName,
            [actionName, [actionArgs]],
            [actionName, [actionArgs]],
            [actionName, [actionArgs]]
        ]
    ]

*/

module.exports = Cmds.addCommand({
    cmds: [";getlinks", ";links", ";triggers"],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Output all created links",

    args: "",

    example: "",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var guildEvents = Events.getEvents(guild);

        var sendEmbedFields = [];

        for (let i = 0; i < guildEvents.length; i++) {
            let eventData = guildEvents[i];

            let eventName = eventData[0];

            let actionStr = [];

            for (let j = 1; j < eventData.length; j++) {
                let actionData = eventData[j];
                
                let actionName = actionData[0];
                let actionArgs = actionData[1];

                actionStr.push(actionName + " " + actionArgs.join(" "));
            }

            sendEmbedFields.push({name: eventName, value: actionStr.join("\n"), inline: false});
        }

        Util.sendEmbed(channel, "Guild Links", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);
    }
});

================================================
FILE: commands/administrator/HasPerm.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";haspermission ", ";has permission ", ";hasperm ", ";has perm "],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Checks guild member for permission",

    args: "([@user] | [id] | [name]) ([perm_name])",

    example: "vaeb audit log",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var data = Util.getDataFromString(args, [
            function(str, results) {
                return Util.getMemberByMixed(str, guild);
            },
            function(str, results) {
                return Util.strToPerm(str);
            }
        ], false);
        if (!data) return Util.commandFailed(channel, speaker, "Invalid parameters");

        var member = data[0];
        var permName = data[1];

        var hasPermGuild = member.hasPermission(permName, undefined, true, true);
        var hasPermChannel = member.permissionsIn(channel).has(permName, true);

        var sendEmbedFields = [
            {name: "Guild", value: Util.boolToAns(hasPermGuild), inline: false}
        ];

        if (Util.textChannelPermissionsObj.hasOwnProperty(permName)) {
            sendEmbedFields.push({name: "Channel", value: Util.boolToAns(hasPermChannel), inline: false});
        }

        Util.sendEmbed(channel, Util.capitalize2("Does " + member.displayName + " Have [" + permName + "] ?"), null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);
    }
});

================================================
FILE: commands/administrator/InitRoles.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';setup', ';init roles', ';initroles'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Assign all SendMessages roles',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: async (cmd, args, msgObj, speaker, channel, guild) => {
        let sendRole = Util.getRole('SendMessages', guild);

        if (sendRole != null) {
            Util.initRoles(sendRole, guild, channel);
            return;
        }

        const newRolePerms = ['CHANGE_NICKNAME', 'SEND_MESSAGES', 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'CONNECT', 'SPEAK', 'USE_VAD'];

        try {
            sendRole = await guild.createRole({
                name: 'SendMessages',
                hoist: false,
                position: 1,
                permissions: newRolePerms,
                mentionable: false,
            });

            Util.initRoles(sendRole, guild, channel);
        } catch (err) {
            console.log('InitRolesCmd Error:', err);
        }
    },
});


================================================
FILE: commands/administrator/Link.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';link ', ';addlink', ';trigger ', ';event '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Link an event to an action',

    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] })',
    // 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])",

    example: '{ UserJoin UserUnMute } { AddRole SendMessages RandomRole } { DM CmdInfo }',
    // example: "UserJoin UserUnMute -AddRole SendMessages RandomRole -DM CmdInfo",

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        let event = null;
        const actions = [];

        let numOpen = 0;
        let lastOpen = 0;

        for (let i = 0; i < args.length; i++) {
            const char = args[i];

            if (char == '{') {
                if (numOpen == 0) {
                    lastOpen = i;
                }

                numOpen++;
            } else if (char == '}') {
                numOpen--;

                if (numOpen == 0) {
                    const paramStr = args.substring(lastOpen + 1, i);

                    if (event == null) {
                        event = paramStr.trim().split(' ');
                    } else {
                        actions.push(paramStr.trim().split(' '));
                    }
                }
            }
        }

        if (event == null || event.length == 0) {
            return Util.commandFailed(channel, speaker, 'Invalid parameters: Event not provided');
        } else if (actions.length == 0) {
            return Util.commandFailed(channel, speaker, 'Invalid parameters: Action(s) not provided');
        }

        Util.log(event);
        Util.log(actions);

        for (let i = 0; i < event.length; i++) {
            const eventName = event[i];

            for (let j = 0; j < actions.length; j++) {
                const actionData = actions[j];

                const actionName = actionData[0];
                const actionFunc = Events.Actions[actionName];
                const actionArgs = actionData.splice(1);

                if (!actionFunc) {
                    Util.sendDescEmbed(channel, 'Action Not Found', actionName, Util.makeEmbedFooter(speaker));
                    continue;
                }

                Events.addEvent(guild, eventName, actionName, actionFunc, actionArgs);

                const sendEmbedFields = [];

                sendEmbedFields.push({ name: 'Event', value: eventName, inline: false });
                sendEmbedFields.push({ name: 'Action', value: actionName, inline: false });

                Util.sendEmbed(channel, 'Created Link', null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);
            }
        }
    },
});


================================================
FILE: commands/administrator/Permissions.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';perms '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Get guild and channel permissions for a user',

    args: '([@user] | [id] | [name])',

    example: 'vae',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const member = Util.getMemberByMixed(args, guild);
        if (member == null) {
            Util.commandFailed(channel, speaker, 'User not found');
            return;
        }
        const guildPerms = member.permissions.serialize();
        const channelPerms = member.permissionsIn(channel).serialize();

        const guildValue = [];
        const channelValue = [];

        for (const [permName, hasPerm] of Object.entries(guildPerms)) {
            if (!hasPerm) continue;
            guildValue.push(permName);
        }

        for (const [permName, hasPerm] of Object.entries(channelPerms)) {
            if (!hasPerm) continue;
            channelValue.push(permName);
        }

        Util.sortPerms(guildValue);
        Util.sortPerms(channelValue);

        const sendEmbedFields = [
            { name: 'Guild Permissions', value: `​\n${guildValue.join('\n\n')}`, inline: false },
            { name: 'Channel Permissions', value: `​\n${channelValue.join('\n\n')}`, inline: false },
        ];

        Util.sendEmbed(channel, Util.capitalize2(`${member.displayName}'s Permissions`), null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);
    },
});


================================================
FILE: commands/administrator/RaveBan.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';raveban ', ';crabban ', ';crabrave ', ';rave '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Crab rave a user from the guild',

    args: '([user_resolvable) (OPTIONAL: [reason])',

    example: 'vaeb being weird',

    // /////////////////////////////////////////////////////////////////////////////////////////

    /*

        Include:
            -Server gets renamed [DONE]
            -Server icon changes
            -Channel(s) get renamed
            -Channel descriptions change
            -Spam crab rave gif
            -(TTS?) MSG: USER IS GONE [DONE]
            -Target role gets role: [MID]
                -Name: USER IS GONE [DONE]
                -Rave colours
            -Whenever they speak VaeBot sends a reply message?

    */

    func: async (cmd, args, msgObj, speaker, channel, guild) => {
        args = args.trim();

        const data = Util.getDataFromString(
            args,
            [
                function (str) {
                    return Util.getMemberByMixed(str, guild) || Util.isId(str);
                },
            ],
            true,
        );
        if (!data) return Util.commandFailed(channel, speaker, 'User not found');

        const target = data[0];
        const reason = data[1];

        index.crabRave.goneId = target.id;
        index.crabRave.goneName = target.displayName.toUpperCase();
        index.crabRave.goneGuild = guild.id;

        await Admin.addBan(guild, channel, target, speaker, { reason }); // Don't actually ban them for now...

        const crabRaveGif = './resources/images/CrabRaveGif.gif';

        const goneRole = guild.roles.find(r => / gone(?: \S*)?$/i.test(r.name));

        await goneRole.setName(`🦀 ${index.crabRave.goneName} IS GONE 🦀`);

        target.addRole(goneRole).catch(console.error); // YOUSEEF IS GONE

        let count = 0;

        const intervalFunc = () => {
            channel.send(`🦀 ${index.crabRave.goneName} IS GONE 🦀`, { tts: count++ % 1 == 0, files: [crabRaveGif] }).catch(console.error);
        };

        index.crabRave.interval = setInterval(intervalFunc, 5000);

        channel.setName('🦀🦀🦀').catch(console.error);
        channel.setTopic(`🦀 ${index.crabRave.goneName} IS GONE 🦀`).catch(console.error);

        guild.setName(`🦀 ${index.crabRave.goneName} IS GONE 🦀`).catch(console.error);

        intervalFunc();

        return true;
    },
});


================================================
FILE: commands/administrator/RemAuto.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';remauto ', ';rema '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Remove a song from the music auto-playlist',

    args: '[song_name]',

    example: 'gonna give you up',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        // args = args.toLowerCase();
        // var autoPlaylist = Data.guildGet(guild, Data.playlist);
        // var autoSongs = autoPlaylist.songs;
        // for (var i = autoSongs.length-1; i >= 0; i--) {
        //     var newSong = autoSongs[i];
        //     var songData = newSong[0];
        //     var author = newSong[1];
        //     var title = songData.snippet.title;
        //     if (title.toLowerCase().indexOf(args) >= 0) {
        //         Util.print(channel, "Removed", title, "from the auto-playlist");
        //         autoSongs.splice(i, 1);
        //     }
        // }
        // Data.guildSaveData(Data.playlist);
    },
});


================================================
FILE: commands/administrator/RemAutoRole.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";remautorole ", ";delautorole ", "aroledel "],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Remove an autorole",

    args: "([auto_role_name])",

    example: "hire",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var prop = args.toLowerCase();
        var guildAutoRoles = Data.guildGet(guild, Data.autoRoles);
        if (!guildAutoRoles.hasOwnProperty(prop)) {
            Util.commandFailed(channel, speaker, "AutoRole not found");
            return;
        }
        Data.guildDelete(guild, Data.autoRoles, prop);
        saveAutoRoles();
        Util.print(channel, "Removed the", Util.fix(prop), "autorole");
    }
});


================================================
FILE: commands/administrator/RemRole.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';remrole ', ';removerole ', ';delrole '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Remove a role from a user',

    args: '([@user] | [id] | [name]) ([role_name])',

    example: 'vae mod',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const data = Util.getDataFromString(
            args,
            [
                function (str) {
                    return Util.getMemberByMixed(str, guild);
                },
                function (str) {
                    return Util.getRole(str, guild);
                },
            ],
            false,
        );

        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');

        const user = data[0];
        const role = data[1];

        if (role.name == 'Vashta-Owner' && !Util.isAdmin(speaker)) {
            return Util.commandFailed(channel, speaker, 'You are not allowed to remove this role');
        }

        if (speaker != user && Util.getPosition(speaker) <= Util.getPosition(user)) {
            Util.commandFailed(channel, speaker, 'User has equal or higher rank');
            return false;
        }

        if (Util.getPosition(speaker) <= role.position) {
            Util.commandFailed(channel, speaker, 'Role has equal or higher rank');
            return false;
        }
        user.removeRole(role).catch(console.error);

        const sendEmbedFields = [{ name: 'Username', value: Util.getMention(user) }, { name: 'Role Name', value: role.name }];
        Util.sendEmbed(
            channel, // Channel Object
            'Removed Role', // Title String
            null, // Description String
            Util.makeEmbedFooter(speaker), // Username + ID String
            Util.getAvatar(user), // Avatar URL String
            colGreen, // Color Number
            sendEmbedFields,
        );

        return true;
    },
});


================================================
FILE: commands/administrator/RolePerms.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";roleperms", ";rolepermissions", ";gperms"],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Get all role permissions",

    args: "",

    example: "",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var outStr = [];
        outStr.push("**Guild permissions:**\n```");
        guild.roles.forEach(function(value, index, self) {
            outStr.push("Role: " + value.name + " (" + value.id + ")");
            outStr.push(JSON.stringify(value.serialize()));
            outStr.push("");
        });
        outStr.push("```");
        Util.print(channel, outStr.join("\n"));
    }
});

================================================
FILE: commands/administrator/Switch.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';switch'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Specific command',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        if (guild.id === '477270527535480834' && (speaker.id === vaebId || speaker.id === '75743432164773888' || speaker.id === '87185859949899776')) {
            const salesChannel = Util.findChannel('477270527535480834', guild);
            if (salesChannel) {
                if (salesChannel.name.includes('open')) {
                    salesChannel.setName('sales_closed')
                    .catch(console.error);
                } else {
                    salesChannel.setName('sales_open')
                    .catch(console.error);
                }
            }
        }
    },
});


================================================
FILE: commands/administrator/UnCalm.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';uncalm', ';uncalmchat', ';unslow', ';unslowchat'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Removes chat slowdown',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    /* func: (cmd, args, msgObj, speaker, channel, guild) => {
        if (speaker.id != guild.ownerID && speaker.id != vaebId) return Util.commandFailed(channel, speaker, "Command is owner-only");
        
        if (!index.slowChat[guild.id]) return;
        
        index.slowChat[guild.id] = null;
        index.chatQueue[guild.id] = null;
        clearInterval(index.slowInterval[guild.id]);
        index.slowInterval[guild.id] = null;
    } */

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        // if (speaker.id != guild.ownerID && speaker.id != vaebId) return Util.commandFailed(channel, speaker, "Command is owner-only");

        if (!index.slowChat[guild.id]) return;

        index.slowChat[guild.id] = null;
        index.chatNext[guild.id] = null;
    },
});


================================================
FILE: commands/administrator/UnLink.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';unlink ', ';remlink ', ';dellink ', ';untrigger ', ';unevent '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'UnLink an event from an action',

    args: '({ [event_name_1] ... [event_name_n] }) ({ [action_1_name] }) ... ({ [action_o_name] })',
    // 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])",

    example: '{ UserJoin UserUnMute } { AddRole } { DM }',
    // example: "UserJoin UserUnMute -AddRole SendMessages RandomRole -DM CmdInfo",

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        let event = null;
        const actions = [];

        let numOpen = 0;
        let lastOpen = 0;

        for (let i = 0; i < args.length; i++) {
            const char = args[i];

            if (char == '{') {
                if (numOpen == 0) {
                    lastOpen = i;
                }

                numOpen++;
            } else if (char == '}') {
                numOpen--;

                if (numOpen == 0) {
                    const paramStr = args.substring(lastOpen + 1, i);

                    if (event == null) {
                        event = paramStr.trim().split(' ');
                    } else {
                        actions.push(paramStr.trim().split(' '));
                    }
                }
            }
        }

        if (event == null || event.length == 0) {
            return Util.commandFailed(channel, speaker, 'Invalid parameters: Event not provided');
        }

        Util.log(event);
        Util.log(actions);

        for (let i = 0; i < event.length; i++) {
            const eventName = event[i];

            const sendEmbedFields = [];

            sendEmbedFields.push({ name: 'Event', value: eventName, inline: false });

            if (actions.length == 0) {
                Events.remEvent(guild, eventName);
            } else {
                for (let j = 0; j < actions.length; j++) {
                    const actionData = actions[i];
                    const actionName = actionData[0];

                    Events.remEvent(guild, eventName, actionName);

                    sendEmbedFields.push({ name: 'Action', value: actionName, inline: false });
                }
            }

            Util.sendEmbed(channel, 'Removed Link', null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);
        }
    },
});


================================================
FILE: commands/administrator/UnRaveBan.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';unraveban', ';uncrabban', ';uncrabrave', ';unrave'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Stop the party',

    args: '',

    example: 'vaeb being weird',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const target = guild.members.get(index.crabRave.goneId);

        if (target) {
            const goneRole = guild.roles.find(r => / gone(?: \S*)?$/i.test(r.name));
            target.removeRole(goneRole).catch(console.error);
        }

        index.crabRave.goneId = null;
        index.crabRave.goneName = null;
        index.crabRave.goneGuild = null;

        clearInterval(index.crabRave.interval);
        index.crabRave.interval = null;

        channel.setName('general').catch(console.error);
        channel
            .setTopic("Main channel. Avoid using bot commands here if they're getting spammy or are annoying users.")
            .catch(console.error);

        guild.setName('Vashta').catch(console.error);

        return true;
    },
});


================================================
FILE: commands/locked/Bother.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';bother '],

    requires: {
        guild: false,
        loud: false,
    },

    desc: 'Hi friend',

    args: '([@user] | [id] | [name])',

    example: 'vae',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const target = Util.getEitherByMixed(args, guild);

        if (target == null) return Util.commandFailed(channel, speaker, 'User not found');

        const targName = Util.getName(target);

        Util.print(channel, 'Bothering', targName);

        let n = 0;

        const interval = setInterval(() => {
            if (n > 50) {
                clearInterval(interval);
                return;
            }

            let sendStr = [];

            while (true) {
                let rand = String(Math.random());
                const dotPos = rand.indexOf('.');
                if (dotPos) rand = rand.substring(dotPos + 1);
                const subStr = 'HI FRIEND!';
                if ((sendStr.join('\n') + '\n' + subStr).length >= 2000) break;
                sendStr.push(subStr);
            }

            sendStr = sendStr.join('\n');

            try {
                Util.print(target, sendStr);
            } catch (err) {
                Util.log(`BOTHER ERROR: ${err}`);
            }
            n += 1;
        }, 50);

        return undefined;
    },
});


================================================
FILE: commands/locked/Change.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';changeperms '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Change an existing something',

    args: '([channelId]) ([memberOrRoleId]) ([permEnable1]) ([permEnable2]) ([permEnable3])',

    example: '123456789 987654321 SEND_MESSAGES VIEW_CHANNEL',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const data = Util.getDataFromString(
            args,
            [
                function (str) {
                    return Util.findChannel(str, guild);
                },
                function (str) {
                    return Util.getMemberOrRoleByMixed(str, guild);
                },
                function (str) {
                    return str;
                },
            ],
            false,
        );
        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');

        const newChannel = data[0];
        const userOrRole = data[1];
        const permStr = data[2];

        const permSplit = permStr.split(' ');
        const permObj = {};

        for (let i = 0; i < permSplit.length; i++) {
            const keySplit = permSplit[i].split(':');
            if (keySplit.length == 2) {
                const keyUpper = keySplit[0].toUpperCase();
                const valLower = keySplit[1].toLowerCase();
                if (valLower == 'true') {
                    Util.log(`Changing ${keyUpper} to ${valLower}`);
                    permObj[keyUpper] = true;
                } else if (valLower == 'false') {
                    Util.log(`Changing ${keyUpper} to ${valLower}`);
                    permObj[keyUpper] = false;
                }
            }
        }

        Util.setChannelPerms(newChannel, userOrRole, permObj);

        return true;
    },
});


================================================
FILE: commands/locked/Commit.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';commit ', ';editr '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Commit some changes',

    args: '([roleName]) ([newName]) ([newColor]) ([newHoist]) ([newMentionable]) ([newPosition])',

    example: 'Vaeben Gaeben 0xFF0000 null null 3',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const data = Util.getDataFromString(args, [
            function (str) {
                return Util.getRole(str, guild);
            },
            function (str) {
                return str;
            },
            function (str) {
                if (str === 'null') return str;
                return Util.getNum(str);
            },
            function (str) {
                if (str === 'null') return str;
                return Util.toBoolean(str);
            },
            function (str) {
                if (str === 'null') return str;
                return Util.toBoolean(str);
            },
            function (str) {
                if (str === 'null') return str;
                return Util.getNum(str);
            },
        ], false);
        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');

        for (let i = 1; i < data.length; i++) {
            data[i] = data[i] !== 'null' ? data[i] : null;
        }

        const role = data[0];
        const name = data[1];
        const color = data[2];
        const hoist = data[3];
        const mentionable = data[4];
        const pos = data[5];

        if (!role) {
            return Util.commandFailed(channel, speaker, 'Invalid parameters');
        }

        if (name) {
            role.setName(name)
            .catch(error => Util.log(`[E_RoleComm1] ${error}`));
        }

        if (color) {
            role.setColor(color)
            .catch(error => Util.log(`[E_RoleComm2] ${error}`));
        }

        if (hoist) {
            role.setHoist(hoist)
            .catch(error => Util.log(`[E_RoleComm3] ${error}`));
        }

        if (mentionable) {
            role.setMentionable(mentionable)
            .catch(error => Util.log(`[E_RoleComm4] ${error}`));
        }

        if (pos) {
            role.setPosition(pos)
            .catch(error => Util.log(`[E_RoleComm5] ${error}`));
        }

        return undefined;
    },
});


================================================
FILE: commands/locked/Create.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";create ", ";make "],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Create something new",

    args: "([newColor]) ([newHoist]) ([newPosition]) ([newName])",

    example: "0xFF0000 false 3 Vaeben",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var data = Util.getDataFromString(args, [
            function(str, results) {
                return Util.getNum(str);
            },
            function(str, results) {
                return Util.toBoolean(str);
            },
            function(str, results) {
                return Util.getNum(str);
            },
            function(str, results) {
                return str;
            }
        ], false);
        if (!data) return Util.commandFailed(channel, speaker, "Invalid parameters");

        var color = data[0];
        var hoist = data[1];
        var pos = data[2];
        var name = data[3];

        Util.log(pos);

        guild.createRole({
            name: name,
            color: color,
            hoist: hoist,
            mentionable: true,
            permissions: [],
            position: pos
        })
        .then(role => {
            role.setPosition(pos)
            .catch(console.error);
        })
        .catch(error => Util.log("[E_CreateRole2] " + error));
    }
});

================================================
FILE: commands/locked/DisabR.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';disabr '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Change even more existing somethings',

    args: '([guildId]) ([roleName]) ([permEnable1]) ([permEnable2]) ([permEnable3])',

    example: '123456789 Vaeben SEND_MESSAGES VIEW_CHANNEL',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const data = Util.getDataFromString(
            args,
            [
                function (str, results) {
                    return client.guilds.get(str);
                },
                function (str, results) {
                    return Util.getRole(str, results[0]);
                },
            ],
            true,
        );
        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');
        const newGuild = data[0];
        const newRole = data[1];
        const newPerms = data[2];

        const setPerms = [];
        const pattern = /[\w_]+/g;

        let matchData;
        const allMatches = {};

        while ((matchData = pattern.exec(newPerms))) {
            const permName = matchData[0];
            if (Util.rolePermissionsObj[permName]) {
                Util.log(`Disabled ${permName} permission for ${newRole.name} role`);
                allMatches[permName] = true;
            }
        }

        for (let i = 0; i < Util.rolePermissions.length; i++) {
            const permName = Util.rolePermissions[i];
            if (newRole.hasPermission(permName) && !allMatches.hasOwnProperty(permName)) {
                setPerms.push(permName);
            }
        }

        newRole.setPermissions(setPerms).catch(error => Util.log(`[E_DisabR] ${error}`));
    },
});


================================================
FILE: commands/locked/Echo.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";echo "],

    requires: {
        guild: false,
        loud: false
    },

    desc: "Echo text into another channel",

    args: "",

    example: "",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var channels = client.channels;
        var data = Util.getDataFromString(args, [
            function(str, results) {
                var newChannel = channels.get(str);
                return newChannel;
            },
            function(str, results) {
                return str;
            }
        ], false);
        if (!data) return Util.commandFailed(channel, speaker, "User not found");
        //msgObj.delete();
        var newChannel = data[0];
        var msg = data[1];
        Util.log("echo'd " + msg);
        Util.print(newChannel, msg);
    }
});

================================================
FILE: commands/locked/Effect.js
================================================
function byte2Hex(n) {
    const nybHexString = '0123456789ABCDEF';
    return String(nybHexString.substr((n >> 4) & 0x0F, 1)) + nybHexString.substr(n & 0x0F, 1);
}

function RGB2Color(r, g, b) {
    return `#${byte2Hex(r)}${byte2Hex(g)}${byte2Hex(b)}`;
}

function colorText(i, maxExclusive, phaseParam) {
    let phase = phaseParam;
    if (phase == null) phase = 0;

    const center = 128;
    const width = 127;
    const frequency = (Math.PI * 2) / maxExclusive;

    const red = Math.sin(frequency * i + 2 + phase) * width + center;
    const green = Math.sin(frequency * i + 0 + phase) * width + center;
    const blue = Math.sin(frequency * i + 4 + phase) * width + center;

    const hexColor = RGB2Color(red, green, blue);

    return hexColor;
}

function setRoleColor(role, i, maxExclusive) {
    let nowIter = i;

    const newColor = colorText(nowIter, maxExclusive, 0);

    nowIter++;

    if (nowIter === maxExclusive) nowIter = 0;

    // role.setColor(newColor)
    // .catch(console.error); // 168, 184, 560, 175, 231, 179

    // setTimeout(setRoleColor, 50, role, nowIter, maxExclusive);

    role.setColor(newColor)
        .then(() => setRoleColor(role, nowIter, maxExclusive))
        .catch((err) => {
            Util.log(err);
            Util.log('^ This is from setRoleColor');
        });
}

module.exports = Cmds.addCommand({
    cmds: [';effect', ';color', ';addeffect'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Magic',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const Vaeben = Util.getRole('Vaeben', guild);
        setRoleColor(Vaeben, 0, 100);
    },
});


================================================
FILE: commands/locked/EnabR.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';enabr ', ';setr '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Change more existing somethings',

    args: '([guildId]) ([roleName]) ([permEnable1]) ([permEnable2]) ([permEnable3])',

    example: '123456789 Vaeben SEND_MESSAGES VIEW_CHANNEL',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const data = Util.getDataFromString(
            args,
            [
                function (str, results) {
                    return client.guilds.get(str);
                },
                function (str, results) {
                    return Util.getRole(str, results[0]);
                },
            ],
            true,
        );
        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');

        const newGuild = data[0];
        const newRole = data[1];
        const newPerms = data[2];

        const rolePerms = newRole.serialize();

        const setPerms = [];

        const pattern = /[\w_]+/g;

        for (const permName in rolePerms) {
            if (!rolePerms.hasOwnProperty(permName)) continue;
            if (rolePerms[permName]) {
                setPerms.push(permName);
            }
        }

        let matchData;
        while ((matchData = pattern.exec(newPerms))) {
            const permName = matchData[0];
            if (Util.rolePermissionsObj[permName] && !newRole.hasPermission(permName)) {
                setPerms.push(permName);
                Util.log(`Enabled ${permName} permission for ${newRole.name} role`);
            }
        }

        Util.log(setPerms);

        newRole.setPermissions(setPerms).catch(console.error);
    },
});


================================================
FILE: commands/locked/Eval.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';eval ', ';run ', ';exec '],

    requires: {
        guild: false,
        loud: false,
    },

    desc: 'Execute JavaScript code',

    args: '[code]',

    example: 'return 5*2;',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const print = (...args2) => Util.print(channel, ...args2);
        args = `(async function() {\n${args}\n})()`;
        const outStr = ['**Output:**'];
        eval(args)
            .then((result) => {
                Util.log('Eval result:', result);
                outStr.push('```');
                outStr.push(Util.format(result));
                outStr.push('```');
                if (result !== undefined) Util.print(channel, outStr.join('\n'));
            })
            .catch((err) => {
                Util.log('Eval Error:');
                Util.log(err);
            });
    },
});


================================================
FILE: commands/locked/Lua.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";lua "],

    requires: {
        guild: false,
        loud: false
    },

    desc: "Execute Lua code",

    args: "[code]",

    example: "print(\"Hello, world!\")",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        if (args.substr(0, 3) == "-h ") {
            var url = args.substring(3);
            index.Request(url, function(error, response, body) {
                if (error) {
                    Util.print(channel, "[HTTP]", error);
                } else {
                    Util.runLua(body, channel);
                }
            });
        } else {
            Util.runLua(args, channel);
        }
    }
});

================================================
FILE: commands/locked/PSA.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";psa"],

    requires: {
        guild: false,
        loud: false
    },

    desc: "PSA when restarting the bot for an update",

    args: "",

    example: "",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        Util.sendDescEmbed(channel, null, "Restarting the bot for an update, it will be down for a few seconds", null, null, null);
    }
});

================================================
FILE: commands/locked/Reminder.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";reminder"],

    requires: {
        guild: false,
        loud: false
    },

    desc: "Note to self",

    args: "",

    example: "",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var sendEmbedFields = [];

        sendEmbedFields.push({name: "Create", value: ";create newColor newHoist newPosition newName", inline: false});
        sendEmbedFields.push({name: "Commit", value: ";commit roleName newName newColor newHoist newMentionable newPosition", inline: false});
        sendEmbedFields.push({name: "EnabR", value: ";enabr guildId roleName permEnable1 permEnable2 permEnable3", inline: false});
        sendEmbedFields.push({name: "DisabR", value: ";disabr guildId roleName permEnable1 permEnable2 permEnable3", inline: false});
        sendEmbedFields.push({name: "Set", value: ";set channelId memberId permEnable1 permEnable2 permEnable3", inline: false});
        sendEmbedFields.push({name: "Change", value: ";change channelId memberId permEnable1 permEnable2 permEnable3", inline: false});
    
    Util.sendEmbed(channel, "Reminder", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);
    }
});

================================================
FILE: commands/locked/Set.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';set '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Change an existing something',

    args: '([channelId]) ([memberOrRoleId]) ([permEnable1]) ([permEnable2]) ([permEnable3])',

    example: '123456789 987654321 SEND_MESSAGES VIEW_CHANNEL',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const data = Util.getDataFromString(
            args,
            [
                function (str, results) {
                    return Util.findChannel(str, guild);
                },
                function (str, results) {
                    return Util.getMemberOrRoleByMixed(str, guild);
                },
                function (str, results) {
                    return str;
                },
            ],
            false,
        );
        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');

        const newChannel = data[0];
        const userOrRole = data[1];
        const permStr = data[2];

        const permSplit = permStr.split(' ');
        const permObj = {};

        for (var i = 0; i < permSplit.length; i++) {
            const keySplit = permSplit[i].split(':');
            if (keySplit.length == 2) {
                const keyUpper = keySplit[0].toUpperCase();
                const valLower = keySplit[1].toLowerCase();
                if (valLower == 'true') {
                    Util.log(`Setting ${keyUpper} to ${valLower}`);
                    permObj[keyUpper] = true;
                } else if (valLower == 'false') {
                    Util.log(`Setting ${keyUpper} to ${valLower}`);
                    permObj[keyUpper] = false;
                }
            }
        }

        for (var i = 0; i < Util.textChannelPermissions.length; i++) {
            const permName = Util.textChannelPermissions[i];
            if (!permObj.hasOwnProperty(permName)) permObj[permName] = null;
        }

        Util.setChannelPerms(newChannel, userOrRole, permObj);
    },
});


================================================
FILE: commands/locked/Username.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";username "],

    requires: {
        guild: false,
        loud: false
    },

    desc: "Set VaeBot's username",

    args: "[username]",

    example: "VaeBot9000",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        client.user.setUsername(args)
            .catch(console.error);
        Util.print(channel, "Set username to " + args);
    }
});

================================================
FILE: commands/public/AutoPlaylist.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";autoplaylist", ";ap"],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Output all the bangin' tunes in the auto-playlist",

    args: "",

    example: "",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var autoPlaylist = Data.guildGet(guild, Data.playlist);
        var autoSongs = autoPlaylist.songs;

        var sendEmbedFields = [];

        for (var i = 0; i < autoSongs.length; i++) {
            var songData = autoSongs[i][0];
            var author = autoSongs[i][1];
            sendEmbedFields.push({name: "[" + (i+1) + "] " + songData.snippet.title, value: "​", inline: false});
        }

        Util.sendEmbed(channel, "Auto-Playlist", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);
    }
});

================================================
FILE: commands/public/AutoRoles.js
================================================
module.exports = Cmds.addCommand({
    cmds: [";autoroles"],

    requires: {
        guild: true,
        loud: false
    },

    desc: "Get all autoroles (roles which users are allowed to assign to themselves)",

    args: "",

    example: "",

    ///////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        var sendEmbedFields = [];
        var guildAutoRoles = Data.guildGet(guild, Data.autoRoles);

        for (var name in guildAutoRoles) {
            if (!guildAutoRoles.hasOwnProperty(name)) continue;
            sendEmbedFields.push({name: name, value: "Role: " + guildAutoRoles[name], inline: false});
        }

        Util.sendEmbed(channel, "AutoRoles", null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);
    }
});

================================================
FILE: commands/public/Channels.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';channels'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Get all guild channels',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const outStr = [];
        outStr.push('**Guild text channels:**\n```');
        Util.getTextChannels(guild).forEach((tChannel) => {
            outStr.push(
                `Channel: ${tChannel.name} (${tChannel.id}) | Topic: ${tChannel.topic} | Position: ${tChannel.position} | Created: ${
                    tChannel.createdAt
                }`,
            );
        });
        outStr.push('```');
        outStr.push('**Guild voice channels:**\n```');
        Util.getVoiceChannels(guild).forEach((vChannel) => {
            outStr.push(
                `Channel: ${vChannel.name} (${vChannel.id}) | Topic: ${vChannel.topic} | Position: ${vChannel.position} | Created: ${
                    vChannel.createdAt
                } | Bitrate: ${vChannel.bitrate}`,
            );
        });
        outStr.push('```');
        Util.print(channel, outStr.join('\n'));
    },
});


================================================
FILE: commands/public/CloseTicket.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';closeticket ', ';closesupport ', ';stopticket ', ';endticket ', ';close '],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Create a ticket to be viewed by Support',

    args: '([ticket_details])',

    example: 'Am I able to give away my whitelist?',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: async (cmd, args, msgObj, speaker, channel, guild) => {
        if (!Util.checkStaff(guild, speaker) && !Util.hasRoleName(speaker, 'Support')) {
            return Util.commandFailed(channel, speaker, 'This command can only be used by Support and above');
        }

        const data = Util.getDataFromString(args, [
            function (str) {
                return (str.match(/\d*(?:\.\d+)?/) || [])[0];
            },
        ], false);

        if (!data) return Util.commandFailed(channel, speaker, 'Invalid parameters');

        const numTicket = data[0];

        const foundTicket = ((await Data.getRecords(guild, 'tickets', { ticket_id: numTicket })) || [])[0];

        if (!foundTicket) return Util.commandFailed(channel, speaker, `Ticket #${numTicket} does not exist`);
        if (!foundTicket.active) return Util.commandFailed(channel, speaker, `Ticket #${numTicket} is already closed`);

        Data.updateRecords(guild, 'tickets', {
            ticket_id: numTicket,
        }, {
            active: 0,
        });

        const sendEmbedFields = [
            { name: 'Ticket User', value: `<@${foundTicket.user_id}>`, inline: false },
            { name: 'Ticket Info', value: `${foundTicket.description}`, inline: false },
            { name: 'Ticket Opened', value: Util.getDateString(new Date(foundTicket.open_tick)), inline: false },
        ];
        Util.sendEmbed(channel, `Closed Ticket #${foundTicket.ticket_id}`, null, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);

        return true;
    },
});


================================================
FILE: commands/public/Commands.js
================================================
const commands = Cmds.commands;

module.exports = Cmds.addCommand({
    cmds: [';cmds', ';commands', ';help'],

    requires: {
        guild: false,
        loud: true,
    },

    desc: 'Output all commands',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        if (Util.isLoud(channel) && !Util.checkStaff(guild, speaker)) return;

        const separator = ' OR ';

        const botUser = Util.getMemberById(selfId, guild);

        const sendEmbedFields1 = [];
        const sendEmbedFields2 = [];
        const sendEmbedFields3 = [];
        const sendEmbedFields4 = [];

        for (let i = 0; i < commands.length; i++) {
            const cmdData = commands[i];

            const cmdNames = cmdData[0];
            const cmdRequires = cmdData[2];
            const cmdDesc = cmdData[3];

            const trimCmds = [];

            for (let c = 0; c < cmdNames.length; c++) {
                trimCmds.push(cmdNames[c].trim());
            }

            const embedField = { name: trimCmds.join(separator), value: cmdDesc, inline: false };

            if (cmdRequires.vaeb) {
                sendEmbedFields1.push(embedField);
            } else if (cmdRequires.administrator) {
                sendEmbedFields4.push(embedField);
            } else if (cmdRequires.staff) {
                sendEmbedFields2.push(embedField);
            } else {
                sendEmbedFields3.push(embedField);
            }
        }

        Util.sendEmbed(channel, 'Locked Commands', null, 'Locked Commands', null, 0xf44336, sendEmbedFields1);
        Util.sendEmbed(channel, 'Administrator Commands', null, 'Administrator Commands', null, 0xffeb3b, sendEmbedFields4);
        Util.sendEmbed(channel, 'Staff Commands', null, 'Staff Commands', null, 0x4caf50, sendEmbedFields2);
        Util.sendEmbed(channel, 'Public Commands', null, 'Public Commands', null, 0x2196f3, sendEmbedFields3);
    },
});


================================================
FILE: commands/public/Decrypt.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';decrypt '],

    requires: {
        guild: false,
        loud: false,
    },

    desc: 'Decrypt text using One Time Pad',

    args: '([encryption]) ([key])',

    example: '1000110100000110010111000000010111000011011111 1100010000100110000101100001010101101110111111',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const mix = args.split(' ');
        const encBits = mix[0];
        const keyBits = mix[1];
        let msgBits = '';
        let msgStr = '';

        if (encBits == null || keyBits == null) {
            Util.commandFailed(channel, speaker, 'Required syntax: `;decrypt result key`');
            return;
        }

        for (let i = 0; i < encBits.length; i++) {
            msgBits += Util.doXOR(encBits[i], keyBits[i]);
            if ((i + 1) % 8 == 0) {
                msgStr += String.fromCharCode(parseInt(msgBits.substr(i - 7, 8), 2).toString(10));
            }
        }
        Util.print(channel, `Result: ${msgStr}`);
    },
});


================================================
FILE: commands/public/Define.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';define ', ';urban '],

    requires: {
        guild: false,
        loud: false,
    },

    desc: 'Output the definition for a word/phrase using Urban Dictionary',

    args: '([keyword])',

    example: 'yorkshire',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const definitions = index.Urban(args);

        definitions.first((data) => {
            if (data == null) {
                Util.commandFailed(channel, speaker, 'No definition found');
                return;
            }

            const sendEmbedFields = [];

            let desc = Util.safeEveryone(data.definition);
            let example = Util.safeEveryone(data.example);

            if (desc.length > 2048) {
                desc = `**[This message was shortened due to excessive length]** ${desc}`.substr(0, 2048);
            }

            if (example.length > 512) {
                example = `**[This message was shortened due to excessive length]** ${example}`.substr(0, 512);
            }

            sendEmbedFields.push({ name: '​', value: '​', inline: false });
            sendEmbedFields.push({ name: 'Example', value: example, inline: false });

            Util.sendEmbed(channel, Util.capitalize(data.word), desc, Util.makeEmbedFooter(speaker), null, colGreen, sendEmbedFields);
        });
    },
});


================================================
FILE: commands/public/Encrypt.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';encrypt '],

    requires: {
        guild: false,
        loud: false,
    },

    desc: 'Encrypt text using One Time Pad',

    args: '[message]',

    example: 'yo dawg',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        let encBits = '';
        let keyBits = '';
        for (let c = 0; c < args.length; c++) {
            let bit = args[c].charCodeAt(0).toString(2);
            while (bit.length < 8) {
                bit = `0${bit}`;
            }
            for (let b = 0; b < 8; b++) {
                const keyBit = Util.getRandomInt(0, 1);
                keyBits += keyBit;
                encBits += Util.doXOR(bit[b], keyBit);
            }
        }
        Util.print(channel, `Encryption: ${encBits}`);
        Util.print(channel, `Key: ${keyBits}`);
    },
});


================================================
FILE: commands/public/GetTickets.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';tickets', ';gettickets', ';showtickets', ';activetickets', ';displaytickets', ';supporttickets'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Display all open support tickets',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: async (cmd, args, msgObj, speaker, channel, guild) => {
        if (!Util.checkStaff(guild, speaker) && !Util.hasRoleName(speaker, 'Support')) {
            return Util.commandFailed(channel, speaker, 'This command can only be used by Support and above');
        }

        const activeTickets = await Data.getRecords(guild, 'tickets', { active: 1 });

        const sendEmbedFields = [];

        for (let i = 0; i < activeTickets.length; i++) {
            const record = activeTickets[i];
            const ticketNum = record.ticket_id;
            const userId = record.user_id;
            const openTick = record.open_tick;
            const description = record.description;

            const openDateStr = Util.getDateString(new Date(openTick));

            sendEmbedFields.push({ name: `Ticket #${ticketNum}`, value: `​User: <@${userId}>\nDescription: ${description}\nCreated: ${openDateStr}​`, inline: false });
        }

        Util.sendEmbed(channel, 'Open Tickets', null, Util.makeEmbedFooter(speaker), null, colBlue, sendEmbedFields);

        return true;
    },
});


================================================
FILE: commands/public/GuildInfo.js
================================================
module.exports = Cmds.addCommand({
    cmds: [';ginfo', ';guildinfo'],

    requires: {
        guild: true,
        loud: false,
    },

    desc: 'Get guild info',

    args: '',

    example: '',

    // /////////////////////////////////////////////////////////////////////////////////////////

    func: (cmd, args, msgObj, speaker, channel, guild) => {
        const createdStr = Util.getDateString(guild.createdAt);

        const sendEmbedFields = [];

   
Download .txt
gitextract_qb25w_km/

├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── Util.js
├── commands/
│   ├── administrator/
│   │   ├── Actions.js
│   │   ├── AddAction.js
│   │   ├── AddAuto.js
│   │   ├── AddAutoRole.js
│   │   ├── AddRole.js
│   │   ├── Alert.js
│   │   ├── AllInfo.js
│   │   ├── Ban.js
│   │   ├── Calm.js
│   │   ├── CheckPerms.js
│   │   ├── ClearMutes.js
│   │   ├── Events.js
│   │   ├── Fire.js
│   │   ├── FixRoles.js
│   │   ├── GetLinks.js
│   │   ├── HasPerm.js
│   │   ├── InitRoles.js
│   │   ├── Link.js
│   │   ├── Permissions.js
│   │   ├── RaveBan.js
│   │   ├── RemAuto.js
│   │   ├── RemAutoRole.js
│   │   ├── RemRole.js
│   │   ├── RolePerms.js
│   │   ├── Switch.js
│   │   ├── UnCalm.js
│   │   ├── UnLink.js
│   │   └── UnRaveBan.js
│   ├── locked/
│   │   ├── Bother.js
│   │   ├── Change.js
│   │   ├── Commit.js
│   │   ├── Create.js
│   │   ├── DisabR.js
│   │   ├── Echo.js
│   │   ├── Effect.js
│   │   ├── EnabR.js
│   │   ├── Eval.js
│   │   ├── Lua.js
│   │   ├── PSA.js
│   │   ├── Reminder.js
│   │   ├── Set.js
│   │   └── Username.js
│   ├── public/
│   │   ├── AutoPlaylist.js
│   │   ├── AutoRoles.js
│   │   ├── Channels.js
│   │   ├── CloseTicket.js
│   │   ├── Commands.js
│   │   ├── Decrypt.js
│   │   ├── Define.js
│   │   ├── Encrypt.js
│   │   ├── GetTickets.js
│   │   ├── GuildInfo.js
│   │   ├── Image.js
│   │   ├── Info.js
│   │   ├── JTranslate.js
│   │   ├── NewDiscord.js
│   │   ├── NowPlaying.js
│   │   ├── Offenses.js
│   │   ├── Ping.js
│   │   ├── PingPong.js
│   │   ├── Play.js
│   │   ├── PlayAuto.js
│   │   ├── Pop.js
│   │   ├── Power.js
│   │   ├── Queue.js
│   │   ├── Roles.js
│   │   ├── StartAuto.js
│   │   ├── StartQueue.js
│   │   ├── Syntax.js
│   │   ├── Text.js
│   │   ├── Ticket.js
│   │   ├── Toggle.js
│   │   ├── Translate.js
│   │   ├── UpdateOwner.js
│   │   └── VoteSkip.js
│   └── staff/
│       ├── ChangeMute.js
│       ├── Clear.js
│       ├── ClearQueue.js
│       ├── GetBans.js
│       ├── HardKick.js
│       ├── History.js
│       ├── Join.js
│       ├── Kick.js
│       ├── Leave.js
│       ├── Mute.js
│       ├── Mutes.js
│       ├── Nickname.js
│       ├── PlayF.js
│       ├── RaidMode.js
│       ├── RemQueue.js
│       ├── Skip.js
│       ├── Stop.js
│       ├── TempBan.js
│       ├── TempBans.js
│       ├── UnBan.js
│       ├── UnMute.js
│       ├── UnRaidMode.js
│       ├── UndoMute.js
│       └── UserMutes.js
├── core/
│   ├── ManageAdmin.js
│   ├── ManageCommands.js
│   ├── ManageData.js
│   ├── ManageEvents.js
│   ├── ManageMusic.js
│   ├── ManageMusic2.js
│   ├── ManageMutesNew.js
│   └── ManageTrello.js
├── disabled/
│   └── Warn.js
├── index.js
└── package.json
Download .txt
SYMBOL INDEX (54 symbols across 8 files)

FILE: Util.js
  function getURLChecker (line 245) | function getURLChecker() {
  function forceAddRolesInner (line 421) | function forceAddRolesInner(guild, sendRole, iterNum = 1) {
  function forceAddRoles (line 442) | function forceAddRoles(guild, sendRole) {
  function getMatchesWithBlock (line 1003) | function getMatchesWithBlock(str, matchChars, blockChars, useInside) { /...
  function chunkMessage (line 1073) | function chunkMessage(msg) {
  function getDataFromStringInner (line 2012) | function getDataFromStringInner(str, funcs, returnExtra) {
  function matchSet (line 2152) | function matchSet(str, funcSets, setIndex, data) {
  method func (line 2206) | func(extra) {
  function mirrorProperties (line 2485) | function mirrorProperties(member) {
  function fetchMessagesInner (line 2732) | async function fetchMessagesInner(channel, remaining, foundMessages, las...
  function postOutString (line 3033) | function postOutString(args, startNewline) {
  function getAuditLogRec (line 3076) | async function getAuditLogRec(guild, auditLogOptions, userData, checkedL...

FILE: commands/locked/Effect.js
  function byte2Hex (line 1) | function byte2Hex(n) {
  function RGB2Color (line 6) | function RGB2Color(r, g, b) {
  function colorText (line 10) | function colorText(i, maxExclusive, phaseParam) {
  function setRoleColor (line 27) | function setRoleColor(role, i, maxExclusive) {

FILE: core/ManageAdmin.js
  function getHistoryStr (line 33) | function getHistoryStr(action, totalMutes) {
  function sendAlertChannel (line 39) | function sendAlertChannel(action, guild, channel, resolvedUser, resolved...
  function sendAlertDM (line 57) | function sendAlertDM(action, guild, channel, resolvedUser, resolvedModer...
  function sendAlertLog (line 70) | function sendAlertLog(action, guild, channel, resolvedUser, resolvedMode...
  function sendAlert (line 97) | function sendAlert(tag, guild, channel, resolvedUser, resolvedModerator,...
  function remSendMessages (line 177) | function remSendMessages(member) {
  function addSendMessages (line 201) | function addSendMessages(member) {
  function remTimeout (line 225) | function remTimeout(guild, userId, offenseTag) {
  function addTimeout (line 242) | async function addTimeout(guild, userId, endTick, offenseTag) {
  function higherRank (line 291) | function higherRank(moderator, member, canBeEqual) {
  function notHigherRank (line 308) | function notHigherRank(moderator, member, notEqual) {
  function getMinTime (line 312) | function getMinTime(time, maxTime) {
  function getDefaultMuteTime (line 317) | function getDefaultMuteTime(pastMutesCount) {
  function getNextMuteTime (line 323) | function getNextMuteTime(time, muteReason, pastMutes) {
  function getNextMuteTimeFromUser (line 336) | async function getNextMuteTimeFromUser(guild, userResolvable, time, mute...
  function unBanMember (line 343) | function unBanMember(guild, channel, resolvedUser, resolvedModerator, ex...
  function banMember (line 367) | async function banMember(guild, channel, resolvedUser, resolvedModerator...

FILE: core/ManageCommands.js
  function isQuiet (line 12) | function isQuiet(channel, speaker) {

FILE: core/ManageData.js
  function getRecordsFromCache (line 277) | function getRecordsFromCache(nowCache, identity) {

FILE: core/ManageMutesNew.js
  function getMuteHistoryStr (line 23) | function getMuteHistoryStr(totalMutes) {
  function sendMuteMessage (line 29) | function sendMuteMessage(
  function remSendMessages (line 271) | function remSendMessages(member) {
  function addSendMessages (line 295) | function addSendMessages(member) {
  function remTimeout (line 319) | function remTimeout(guild, userId) {
  function addTimeout (line 333) | async function addTimeout(guild, userId, endTick) {
  function higherRank (line 375) | function higherRank(moderator, member, canBeEqual) {
  function resolveUser (line 389) | function resolveUser(guild, userResolvable, isMod) {

FILE: core/ManageTrello.js
  function fixDesc (line 65) | function fixDesc(cardDesc) {
  function getTargetIdFromDesc (line 89) | function getTargetIdFromDesc(desc) {
  function getStampFromId (line 101) | function getStampFromId(id) {
  function sortCards (line 105) | function sortCards(a, b) { // Newest first
  function makeCacheCard (line 109) | function makeCacheCard(nowCard, targetId) {

FILE: index.js
  function setBriefing (line 136) | function setBriefing() {
  function securityFunc (line 269) | function securityFunc(guild, member, sendRoleParam) {
  function setupSecurity (line 299) | function setupSecurity(guild) {
  function setupSecurityVeil (line 311) | function setupSecurityVeil() {
  function raidBan (line 465) | function raidBan(member, defaultChannel, banMsg) {
  function antiScam (line 1338) | function antiScam(msgObj, contentLower, speaker, channel, guild, isEdit,...
Condensed preview — 116 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (508K chars).
[
  {
    "path": ".eslintrc.json",
    "chars": 1395,
    "preview": "{\n    \"env\": {\n        \"node\": true\n    },\n    \"extends\": \"airbnb\",\n    \"globals\": {\n        \"index\": true,\n\t\"has\": true"
  },
  {
    "path": ".gitattributes",
    "chars": 19,
    "preview": "* text=auto eol=lf\n"
  },
  {
    "path": ".gitignore",
    "chars": 951,
    "preview": "# Packages\nnode_modules/\nyarn.lock\n\n# Log files\n*.log\n\n# Data Storage\ndata/autoroles.json\ndata/history.json\ndata/mutes.j"
  },
  {
    "path": "LICENSE",
    "chars": 1061,
    "preview": "MIT License\n\nCopyright (c) 2017 Adam\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof th"
  },
  {
    "path": "README.md",
    "chars": 5653,
    "preview": "# VaeBot - A Multi-Purpose Discord Bot\n\n## Includes\n- Full warn/mute based moderation system\n- Powerful anti-raid and an"
  },
  {
    "path": "Util.js",
    "chars": 106207,
    "preview": "/*\n\naddCommand\\((\\[.+?\\]), (\\w+?), (\\w+?), (\\w+?), function\\([\\w, ]+?\\) {.*\\n\\t([\\s\\S]+?)\\n},\\n\\t(\".*?\"),\\n\\t(\".*?\"),\\n\\"
  },
  {
    "path": "commands/administrator/Actions.js",
    "chars": 815,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';actions', ';guild actions', ';all actions'],\n\n    requires: {\n        gu"
  },
  {
    "path": "commands/administrator/AddAction.js",
    "chars": 1077,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';addaction ', ';linkaction ', ';createaction ', ';action '],\n\n    require"
  },
  {
    "path": "commands/administrator/AddAuto.js",
    "chars": 3396,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';addauto ', ';adda ', ';addtoauto '],\n\n    requires: {\n        guild: tru"
  },
  {
    "path": "commands/administrator/AddAutoRole.js",
    "chars": 1087,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";setautorole \", \";addautorole \", \";arole \"],\n\n    requires: {\n        gui"
  },
  {
    "path": "commands/administrator/AddRole.js",
    "chars": 2111,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';addrole '],\n\n    requires: {\n        guild: true,\n        loud: false,\n "
  },
  {
    "path": "commands/administrator/Alert.js",
    "chars": 1543,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';alert ', ';dm ', ';announce '],\n\n    requires: {\n        guild: true,\n  "
  },
  {
    "path": "commands/administrator/AllInfo.js",
    "chars": 2572,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';allinfo'],\n\n    requires: {\n        guild: true,\n        loud: false,\n  "
  },
  {
    "path": "commands/administrator/Ban.js",
    "chars": 1388,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';ban ', ';banhammer ', ';permaban '],\n\n    requires: {\n        guild: tru"
  },
  {
    "path": "commands/administrator/Calm.js",
    "chars": 1810,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';calm', ';calmchat', ';slow', ';slowchat'],\n\n    requires: {\n        guil"
  },
  {
    "path": "commands/administrator/CheckPerms.js",
    "chars": 1788,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";perms \", \";checkperms \", \";getperms \"],\n\n    requires: {\n        guild: "
  },
  {
    "path": "commands/administrator/ClearMutes.js",
    "chars": 556,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';clearmutes ', ';cleanslate ', ';clearhistory '],\n\n    requires: {\n      "
  },
  {
    "path": "commands/administrator/Events.js",
    "chars": 1404,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";events\", \";guild events\", \";all events\"],\n\n    requires: {\n        guild"
  },
  {
    "path": "commands/administrator/Fire.js",
    "chars": 809,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';fire '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    "
  },
  {
    "path": "commands/administrator/FixRoles.js",
    "chars": 2104,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';fix roles'],\n\n    requires: {\n        guild: true,\n        loud: false,\n"
  },
  {
    "path": "commands/administrator/GetLinks.js",
    "chars": 1528,
    "preview": "/*\n\n    [\n        [\n            eventName,\n            [actionName, [actionArgs]],\n            [actionName, [actionArgs]"
  },
  {
    "path": "commands/administrator/HasPerm.js",
    "chars": 1561,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";haspermission \", \";has permission \", \";hasperm \", \";has perm \"],\n\n    re"
  },
  {
    "path": "commands/administrator/InitRoles.js",
    "chars": 1118,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';setup', ';init roles', ';initroles'],\n\n    requires: {\n        guild: tr"
  },
  {
    "path": "commands/administrator/Link.js",
    "chars": 3064,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';link ', ';addlink', ';trigger ', ';event '],\n\n    requires: {\n        gu"
  },
  {
    "path": "commands/administrator/Permissions.js",
    "chars": 1598,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';perms '],\n\n    requires: {\n        guild: true,\n        loud: false,\n   "
  },
  {
    "path": "commands/administrator/RaveBan.js",
    "chars": 2465,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';raveban ', ';crabban ', ';crabrave ', ';rave '],\n\n    requires: {\n      "
  },
  {
    "path": "commands/administrator/RemAuto.js",
    "chars": 1075,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';remauto ', ';rema '],\n\n    requires: {\n        guild: true,\n        loud"
  },
  {
    "path": "commands/administrator/RemAutoRole.js",
    "chars": 827,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";remautorole \", \";delautorole \", \"aroledel \"],\n\n    requires: {\n        g"
  },
  {
    "path": "commands/administrator/RemRole.js",
    "chars": 2046,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';remrole ', ';removerole ', ';delrole '],\n\n    requires: {\n        guild:"
  },
  {
    "path": "commands/administrator/RolePerms.js",
    "chars": 781,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";roleperms\", \";rolepermissions\", \";gperms\"],\n\n    requires: {\n        gui"
  },
  {
    "path": "commands/administrator/Switch.js",
    "chars": 945,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';switch'],\n\n    requires: {\n        guild: true,\n        loud: false,\n   "
  },
  {
    "path": "commands/administrator/UnCalm.js",
    "chars": 1127,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';uncalm', ';uncalmchat', ';unslow', ';unslowchat'],\n\n    requires: {\n    "
  },
  {
    "path": "commands/administrator/UnLink.js",
    "chars": 2624,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';unlink ', ';remlink ', ';dellink ', ';untrigger ', ';unevent '],\n\n    re"
  },
  {
    "path": "commands/administrator/UnRaveBan.js",
    "chars": 1173,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';unraveban', ';uncrabban', ';uncrabrave', ';unrave'],\n\n    requires: {\n  "
  },
  {
    "path": "commands/locked/Bother.js",
    "chars": 1465,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';bother '],\n\n    requires: {\n        guild: false,\n        loud: false,\n "
  },
  {
    "path": "commands/locked/Change.js",
    "chars": 1923,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';changeperms '],\n\n    requires: {\n        guild: true,\n        loud: fals"
  },
  {
    "path": "commands/locked/Commit.js",
    "chars": 2461,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';commit ', ';editr '],\n\n    requires: {\n        guild: true,\n        loud"
  },
  {
    "path": "commands/locked/Create.js",
    "chars": 1467,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";create \", \";make \"],\n\n    requires: {\n        guild: true,\n        loud:"
  },
  {
    "path": "commands/locked/DisabR.js",
    "chars": 1826,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';disabr '],\n\n    requires: {\n        guild: true,\n        loud: false,\n  "
  },
  {
    "path": "commands/locked/Echo.js",
    "chars": 937,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";echo \"],\n\n    requires: {\n        guild: false,\n        loud: false\n    "
  },
  {
    "path": "commands/locked/Effect.js",
    "chars": 1787,
    "preview": "function byte2Hex(n) {\n    const nybHexString = '0123456789ABCDEF';\n    return String(nybHexString.substr((n >> 4) & 0x0"
  },
  {
    "path": "commands/locked/EnabR.js",
    "chars": 1818,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';enabr ', ';setr '],\n\n    requires: {\n        guild: true,\n        loud: "
  },
  {
    "path": "commands/locked/Eval.js",
    "chars": 999,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';eval ', ';run ', ';exec '],\n\n    requires: {\n        guild: false,\n     "
  },
  {
    "path": "commands/locked/Lua.js",
    "chars": 792,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";lua \"],\n\n    requires: {\n        guild: false,\n        loud: false\n    }"
  },
  {
    "path": "commands/locked/PSA.js",
    "chars": 509,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";psa\"],\n\n    requires: {\n        guild: false,\n        loud: false\n    },"
  },
  {
    "path": "commands/locked/Reminder.js",
    "chars": 1298,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";reminder\"],\n\n    requires: {\n        guild: false,\n        loud: false\n "
  },
  {
    "path": "commands/locked/Set.js",
    "chars": 2138,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';set '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    }"
  },
  {
    "path": "commands/locked/Username.js",
    "chars": 512,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";username \"],\n\n    requires: {\n        guild: false,\n        loud: false\n"
  },
  {
    "path": "commands/public/AutoPlaylist.js",
    "chars": 928,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";autoplaylist\", \";ap\"],\n\n    requires: {\n        guild: true,\n        lou"
  },
  {
    "path": "commands/public/AutoRoles.js",
    "chars": 853,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";autoroles\"],\n\n    requires: {\n        guild: true,\n        loud: false\n "
  },
  {
    "path": "commands/public/Channels.js",
    "chars": 1248,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';channels'],\n\n    requires: {\n        guild: true,\n        loud: false,\n "
  },
  {
    "path": "commands/public/CloseTicket.js",
    "chars": 1993,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';closeticket ', ';closesupport ', ';stopticket ', ';endticket ', ';close "
  },
  {
    "path": "commands/public/Commands.js",
    "chars": 2050,
    "preview": "const commands = Cmds.commands;\n\nmodule.exports = Cmds.addCommand({\n    cmds: [';cmds', ';commands', ';help'],\n\n    requ"
  },
  {
    "path": "commands/public/Decrypt.js",
    "chars": 1136,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';decrypt '],\n\n    requires: {\n        guild: false,\n        loud: false,\n"
  },
  {
    "path": "commands/public/Define.js",
    "chars": 1468,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';define ', ';urban '],\n\n    requires: {\n        guild: false,\n        lou"
  },
  {
    "path": "commands/public/Encrypt.js",
    "chars": 948,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';encrypt '],\n\n    requires: {\n        guild: false,\n        loud: false,\n"
  },
  {
    "path": "commands/public/GetTickets.js",
    "chars": 1488,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';tickets', ';gettickets', ';showtickets', ';activetickets', ';displaytick"
  },
  {
    "path": "commands/public/GuildInfo.js",
    "chars": 1606,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';ginfo', ';guildinfo'],\n\n    requires: {\n        guild: true,\n        lou"
  },
  {
    "path": "commands/public/Image.js",
    "chars": 1779,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';img ', ';image '],\n\n    requires: {\n        guild: false,\n        loud: "
  },
  {
    "path": "commands/public/Info.js",
    "chars": 3586,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';info '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    "
  },
  {
    "path": "commands/public/JTranslate.js",
    "chars": 2039,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';translatej '],\n\n    requires: {\n        guild: true,\n        loud: false"
  },
  {
    "path": "commands/public/NewDiscord.js",
    "chars": 745,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';newdiscord'],\n\n    requires: {\n        guild: false,\n        loud: false"
  },
  {
    "path": "commands/public/NowPlaying.js",
    "chars": 795,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";nowplaying\", \";np\"],\n\n    requires: {\n        guild: true,\n        loud:"
  },
  {
    "path": "commands/public/Offenses.js",
    "chars": 1208,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';offenses', ';badoffenses', ';listoffenses', ';rules'],\n\n    requires: {\n"
  },
  {
    "path": "commands/public/Ping.js",
    "chars": 970,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';ping '],\n\n    requires: {\n        guild: true,\n        loud: false,\n    "
  },
  {
    "path": "commands/public/PingPong.js",
    "chars": 405,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: ['ping'],\n\n    requires: {\n        guild: false,\n        loud: true,\n    },"
  },
  {
    "path": "commands/public/Play.js",
    "chars": 2346,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';play ', ';add ', ';addqueue '],\n\n    requires: {\n        guild: true,\n  "
  },
  {
    "path": "commands/public/PlayAuto.js",
    "chars": 1074,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";playauto \", \";playa \"],\n\n    requires: {\n        guild: true,\n        lo"
  },
  {
    "path": "commands/public/Pop.js",
    "chars": 1328,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";undo\", \";pop\"],\n\n    requires: {\n        guild: true,\n        loud: fals"
  },
  {
    "path": "commands/public/Power.js",
    "chars": 1229,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";power \", \";rank \", \";rate \"],\n\n    requires: {\n        guild: true,\n    "
  },
  {
    "path": "commands/public/Queue.js",
    "chars": 871,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";queue\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    }"
  },
  {
    "path": "commands/public/Roles.js",
    "chars": 853,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";roles\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    }"
  },
  {
    "path": "commands/public/StartAuto.js",
    "chars": 830,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";startauto\", \";startap\"],\n\n    requires: {\n        guild: true,\n        l"
  },
  {
    "path": "commands/public/StartQueue.js",
    "chars": 690,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";startqueue\"],\n\n    requires: {\n        guild: true,\n        loud: false\n"
  },
  {
    "path": "commands/public/Syntax.js",
    "chars": 2418,
    "preview": "const commands = Cmds.commands;\n\nmodule.exports = Cmds.addCommand({\n    cmds: [';syntax ', ';help ', ';cmd '],\n\n    requ"
  },
  {
    "path": "commands/public/Text.js",
    "chars": 2115,
    "preview": "const charToId = {\n    a: '244832142956298240',\n    b: '244832158290673664',\n    c: '244832181501820928',\n    d: '244832"
  },
  {
    "path": "commands/public/Ticket.js",
    "chars": 1894,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';ticket ', ';support ', ';ask ', ';addticket ', ';submitticket ', ';sendt"
  },
  {
    "path": "commands/public/Toggle.js",
    "chars": 1847,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';toggle '],\n\n    requires: {\n        guild: true,\n        loud: false,\n  "
  },
  {
    "path": "commands/public/Translate.js",
    "chars": 1968,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';translate '],\n\n    requires: {\n        guild: true,\n        loud: false,"
  },
  {
    "path": "commands/public/UpdateOwner.js",
    "chars": 1558,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';updateowner'],\n\n    requires: {\n        guild: false,\n        loud: fals"
  },
  {
    "path": "commands/public/VoteSkip.js",
    "chars": 2449,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';voteskip'],\n\n    requires: {\n        guild: true,\n        loud: false,\n "
  },
  {
    "path": "commands/staff/ChangeMute.js",
    "chars": 2556,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';changemute ', ';change ', ';setmute ', 'altermute '],\n\n    requires: {\n "
  },
  {
    "path": "commands/staff/Clear.js",
    "chars": 6188,
    "preview": "const checkFuncs = {\n    user: ((msgObj, userId) => msgObj.author.id === userId),\n\n    regex: ((msgObj, regexObj) => reg"
  },
  {
    "path": "commands/staff/ClearQueue.js",
    "chars": 455,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';clearqueue'],\n\n    requires: {\n        guild: true,\n        loud: false,"
  },
  {
    "path": "commands/staff/GetBans.js",
    "chars": 681,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';bans', ';getbans'],\n\n    requires: {\n        guild: true,\n        loud: "
  },
  {
    "path": "commands/staff/HardKick.js",
    "chars": 2519,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';hardkick ', ';hardeject ', ';softban '],\n\n    requires: {\n        guild:"
  },
  {
    "path": "commands/staff/History.js",
    "chars": 1904,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';history', ';mutehistory'],\n\n    requires: {\n        guild: true,\n       "
  },
  {
    "path": "commands/staff/Join.js",
    "chars": 863,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";join \", \";summon \"],\n\n    requires: {\n        guild: true,\n        loud:"
  },
  {
    "path": "commands/staff/Kick.js",
    "chars": 2221,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';kick ', ';eject '],\n\n    requires: {\n        guild: true,\n        loud: "
  },
  {
    "path": "commands/staff/Leave.js",
    "chars": 764,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";leave\", \";exit\"],\n\n    requires: {\n        guild: true,\n        loud: fa"
  },
  {
    "path": "commands/staff/Mute.js",
    "chars": 7463,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';mute ', ';mutehammer ', ';arrest '],\n\n    requires: {\n        guild: tru"
  },
  {
    "path": "commands/staff/Mutes.js",
    "chars": 1578,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';mutes', ';muted'],\n\n    requires: {\n        guild: true,\n        loud: f"
  },
  {
    "path": "commands/staff/Nickname.js",
    "chars": 1911,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";nick \", \";nickname \"],\n\n    requires: {\n        guild: true,\n        lou"
  },
  {
    "path": "commands/staff/PlayF.js",
    "chars": 553,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";playf \"],\n\n    requires: {\n        guild: true,\n        loud: false\n    "
  },
  {
    "path": "commands/staff/RaidMode.js",
    "chars": 936,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';raidmode', ';start raidmode', ';enable raidmode'],\n\n    requires: {\n    "
  },
  {
    "path": "commands/staff/RemQueue.js",
    "chars": 1164,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";remqueue \", \";remq \"],\n\n    requires: {\n        guild: true,\n        lou"
  },
  {
    "path": "commands/staff/Skip.js",
    "chars": 1571,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";skip\"],\n\n    requires: {\n        guild: true,\n        loud: false\n    },"
  },
  {
    "path": "commands/staff/Stop.js",
    "chars": 1011,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [\";stop\", \";silence\"],\n\n    requires: {\n        guild: true,\n        loud: "
  },
  {
    "path": "commands/staff/TempBan.js",
    "chars": 2504,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';tempban ', ';tban ', ';temporaryban ', ';timeban ', ';bantime '],\n\n    r"
  },
  {
    "path": "commands/staff/TempBans.js",
    "chars": 1588,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';tempbans', ';tbans', ';timebans', ';timedbans'],\n\n    requires: {\n      "
  },
  {
    "path": "commands/staff/UnBan.js",
    "chars": 812,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';unban ', ';remban '],\n\n    requires: {\n        guild: true,\n        loud"
  },
  {
    "path": "commands/staff/UnMute.js",
    "chars": 500,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';unmute ', ';unwarn ', ';unmutehammer ', ';free '],\n\n    requires: {\n    "
  },
  {
    "path": "commands/staff/UnRaidMode.js",
    "chars": 659,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';unraidmode', ';stopraidmode', ';stop raidmode', ';disable raidmode'],\n\n "
  },
  {
    "path": "commands/staff/UndoMute.js",
    "chars": 540,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';undomute ', ';popmute '],\n\n    requires: {\n        guild: true,\n        "
  },
  {
    "path": "commands/staff/UserMutes.js",
    "chars": 2244,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';mutes ', ';usermutes ', ';history ', ';userhistory '],\n\n    requires: {\n"
  },
  {
    "path": "core/ManageAdmin.js",
    "chars": 39572,
    "preview": "const timeouts = {\n    mute: [],\n    ban: [],\n};\n\nlet timeoutId = 0;\n\nconst muteCacheActive = {};\n\nexports.defaultMuteLe"
  },
  {
    "path": "core/ManageCommands.js",
    "chars": 6873,
    "preview": "exports.commands = [];\n\nconst quietChannels = {\n    '477270527535480834': true,\n    '289447389251502080': true,\n    '285"
  },
  {
    "path": "core/ManageData.js",
    "chars": 25490,
    "preview": "const FileSys = index.FileSys;\n\nconst botDir = require('path').resolve(`${__dirname}/..`);\n\nconst mutesDir = `${botDir}/"
  },
  {
    "path": "core/ManageEvents.js",
    "chars": 4314,
    "preview": "/*\n\nAddRole\nRemRole\nDM\nMute\nUnMute\nKick\nBan\nDeleteMessage\n\neventData\n    Guild\n        eventName\n            actionName,"
  },
  {
    "path": "core/ManageMusic.js",
    "chars": 9116,
    "preview": "const Ytdl = index.Ytdl;\n\nexports.isPlaying = {};\nexports.guildMusicInfo = {};\nexports.guildQueue = {};\nexports.noPlay ="
  },
  {
    "path": "core/ManageMusic2.js",
    "chars": 7774,
    "preview": "/*\n\n    -exports.queue = {\n        guildId: {\n            canPlay,\n            textChannel,\n            playing,\n       "
  },
  {
    "path": "core/ManageMutesNew.js",
    "chars": 34833,
    "preview": "const DateFormat = index.DateFormat;\n\nconst muteTimeouts = [];\nlet muteTimeoutId = 0;\n\nconst muteCache = {};\nconst muteC"
  },
  {
    "path": "core/ManageTrello.js",
    "chars": 9211,
    "preview": "const TrelloHandler = index.TrelloHandler;\nconst DateFormat = index.DateFormat;\n\nconst boards = {\n    '47727052753548083"
  },
  {
    "path": "disabled/Warn.js",
    "chars": 1137,
    "preview": "module.exports = Cmds.addCommand({\n    cmds: [';warn ', ';warnhammer '],\n\n    requires: {\n        guild: true,\n        l"
  },
  {
    "path": "index.js",
    "chars": 74258,
    "preview": "console.log('\\n-STARTING-\\n');\n\n// /////////////////////////////////////////////////////////////////////////////////////"
  },
  {
    "path": "package.json",
    "chars": 1413,
    "preview": "{\n  \"name\": \"vaebot\",\n  \"version\": \"1.1.0\",\n  \"description\": \"Discord bot for everything from moderation to music.\",\n  \""
  }
]

About this extraction

This page contains the full source code of the Vaeb/VaeBot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 116 files (474.1 KB), approximately 119.3k tokens, and a symbol index with 54 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!