first commit

This commit is contained in:
2022-04-10 19:39:54 +02:00
commit e80edb6d0c
1403 changed files with 161802 additions and 0 deletions

157
node_modules/discord.js-commando/.eslintrc.json generated vendored Normal file
View File

@@ -0,0 +1,157 @@
{
"extends": "eslint:recommended",
"env": {
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"no-await-in-loop": "warn",
"no-compare-neg-zero": "error",
"no-extra-parens": ["warn", "all", {
"nestedBinaryExpressions": false
}],
"no-template-curly-in-string": "error",
"no-unsafe-negation": "error",
"valid-jsdoc": ["warn", {
"requireReturn": false,
"requireReturnDescription": false,
"preferType": {
"String": "string",
"Number": "number",
"Boolean": "boolean",
"Symbol": "symbol",
"function": "Function",
"object": "Object",
"date": "Date",
"error": "Error"
}
}],
"accessor-pairs": "warn",
"array-callback-return": "error",
"complexity": "warn",
"consistent-return": "error",
"curly": ["error", "multi-line", "consistent"],
"dot-location": ["error", "property"],
"dot-notation": "error",
"eqeqeq": "error",
"no-empty-function": "error",
"no-floating-decimal": "error",
"no-implied-eval": "error",
"no-invalid-this": "error",
"no-lone-blocks": "error",
"no-multi-spaces": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-new": "error",
"no-octal-escape": "error",
"no-return-assign": "error",
"no-return-await": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unmodified-loop-condition": "error",
"no-unused-expressions": "error",
"no-useless-call": "error",
"no-useless-concat": "error",
"no-useless-escape": "error",
"no-useless-return": "error",
"no-void": "error",
"no-warning-comments": "warn",
"prefer-promise-reject-errors": "error",
"require-await": "warn",
"wrap-iife": "error",
"yoda": "error",
"no-label-var": "error",
"no-var": "error",
"no-shadow": "error",
"no-undef-init": "error",
"callback-return": "error",
"handle-callback-err": "error",
"no-mixed-requires": "error",
"no-new-require": "error",
"no-path-concat": "error",
"no-process-env": "error",
"array-bracket-spacing": "error",
"block-spacing": "error",
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"camelcase": "error",
"capitalized-comments": ["error", "always", { "ignoreConsecutiveComments": true }],
"comma-dangle": "error",
"comma-spacing": "error",
"comma-style": "error",
"computed-property-spacing": "error",
"consistent-this": "error",
"eol-last": "error",
"func-names": "error",
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"id-length": ["error", { "exceptions": ["i", "j", "a", "b"] }],
"indent": "off",
"indent-legacy": ["error", "tab", { "SwitchCase": 1 }],
"key-spacing": "error",
"keyword-spacing": ["error", {
"overrides": {
"if": { "after": false },
"for": { "after": false },
"while": { "after": false },
"catch": { "after": false },
"switch": { "after": false }
}
}],
"max-depth": "error",
"max-len": ["error", 120, 2],
"max-nested-callbacks": ["error", { "max": 4 }],
"max-statements-per-line": ["error", { "max": 2 }],
"new-cap": "error",
"newline-per-chained-call": ["error", { "ignoreChainWithDepth": 3 }],
"no-array-constructor": "error",
"no-bitwise": "warn",
"no-inline-comments": "error",
"no-lonely-if": "error",
"no-mixed-operators": "error",
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
"no-new-object": "error",
"no-spaced-func": "error",
"no-trailing-spaces": "error",
"no-unneeded-ternary": "error",
"no-whitespace-before-property": "error",
"nonblock-statement-body-position": "error",
"object-curly-newline": "error",
"object-curly-spacing": ["error", "always"],
"operator-assignment": "error",
"operator-linebreak": ["error", "after"],
"padded-blocks": ["error", "never"],
"quote-props": ["error", "as-needed"],
"quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }],
"semi-spacing": "error",
"semi": "error",
"space-before-blocks": "error",
"space-before-function-paren": ["error", "never"],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "error",
"template-tag-spacing": "error",
"unicode-bom": "error",
"arrow-body-style": "error",
"arrow-parens": ["error", "as-needed"],
"arrow-spacing": "error",
"no-duplicate-imports": "error",
"no-useless-computed-key": "error",
"no-useless-constructor": "error",
"prefer-arrow-callback": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"rest-spread-spacing": "error",
"template-curly-spacing": "error",
"yield-star-spacing": "error"
}
}

1
node_modules/discord.js-commando/.gitattributes generated vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

View File

@@ -0,0 +1,29 @@
#!/bin/bash
set -euxo pipefail
echo -e "\n# Initialise some useful variables"
REPO="https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
BRANCH_OR_TAG=`awk -F/ '{print $2}' <<< $GITHUB_REF`
CURRENT_BRANCH=`awk -F/ '{print $NF}' <<< $GITHUB_REF`
if [ "$BRANCH_OR_TAG" == "heads" ]; then
SOURCE_TYPE="branch"
else
SOURCE_TYPE="tag"
fi
echo -e "\n# Checkout the repo in the target branch"
TARGET_BRANCH="docs"
git clone $REPO out -b $TARGET_BRANCH
echo -e "\n# Move the generated JSON file to the newly-checked-out repo, to be committed and pushed"
mv docs/docs.json out/$CURRENT_BRANCH.json
echo -e "\n# Commit and push"
cd out
git pull
git add .
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
git commit -m "Docs build for ${SOURCE_TYPE} ${CURRENT_BRANCH}: ${GITHUB_SHA}" || true
git push origin $TARGET_BRANCH

View File

@@ -0,0 +1,30 @@
name: Deployment
on:
push:
branches:
- '*'
- '!gh-action'
- '!docs'
jobs:
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v1
- name: Install Node v12
uses: actions/setup-node@v1
with:
node-version: 12
- name: Install dependencies
run: yarn
- name: Build documentation
run: yarn docs
- name: Deploy documentation
run: bash ./.github/workflows/deploy.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,42 @@
name: Testing
on: [push, pull_request]
jobs:
lint:
name: ESLint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v1
- name: Install Node v12
uses: actions/setup-node@v1
with:
node-version: 12
- name: Install dependencies
run: yarn
- name: Run ESLint
uses: discordjs/action-eslint@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
job-name: ESLint
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v1
- name: Install Node v12
uses: actions/setup-node@v1
with:
node-version: 12
- name: Install dependencies
run: yarn
- name: Test documentation
run: yarn docs:test

190
node_modules/discord.js-commando/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,190 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2017 Schuyler Cebulskie
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

38
node_modules/discord.js-commando/README.md generated vendored Normal file
View File

@@ -0,0 +1,38 @@
# Commando
[![Discord](https://discordapp.com/api/guilds/222078108977594368/embed.png)](https://discord.gg/bRCvFy9)
[![Downloads](https://img.shields.io/npm/dt/discord.js-commando.svg)](https://www.npmjs.com/package/discord.js-commando)
[![Version](https://img.shields.io/npm/v/discord.js-commando.svg)](https://www.npmjs.com/package/discord.js-commando)
[![Dependency status](https://david-dm.org/discordjs/Commando.svg)](https://david-dm.org/discordjs/Commando)
[![Build status](https://github.com/discordjs/Commando/workflows/Testing/badge.svg)](https://github.com/discordjs/Commando/actions?query=workflow%3ATesting)
## About
Commando is the official command framework for [discord.js](https://github.com/discordjs/discord.js).
It is flexible, fully object-oriented, easy to use, and makes it trivial to create your own powerful commands.
Additionally, it makes full use of ES2017's `async`/`await` functionality for clear, concise code that is simple to write and easy to comprehend.
## Features
- Plain command names and aliases
- Regular expression triggers
- Robust parsing of arguments (with "quoted strings" support)
- Sophisticated argument system (optional)
* Automatic prompting for arguments that aren't provided
* Type system with rules, automatic validation, and parsing to usable values
- Basic types (string, integer, float, boolean)
- Discord objects (user, member, role, channel, message)
- User-defined custom types
- Union types
* Automatic re-prompting of invalid arguments
* Optional arguments with default values
* Infinite arguments (arguments that accept as many values as provided)
- Multiple responses to commands
- Command editing (user edits their message that triggered the command, and the bot's response updates with it)
- Command reloading, as well as loading/unloading
- Command throttling/cooldowns
## Installation
**Node 12.0.0 or newer is required.**
`npm install discord.js@12 discord.js-commando`
## Documentation
[View the docs here.](https://discord.js.org/#/docs/commando)
See the [discord.js documentation](https://discord.js.org/#/docs) as well.

43
node_modules/discord.js-commando/package.json generated vendored Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "discord.js-commando",
"version": "0.12.3",
"description": "The official command framework for Discord.js",
"license": "Apache-2.0",
"author": "Schuyler Cebulskie <Gawdl3y@gawdl3y.com> (https://gawdl3y.com/)",
"repository": {
"type": "git",
"url": "https://github.com/discordjs/Commando.git"
},
"bugs": {
"url": "https://github.com/discordjs/Commando/issues"
},
"keywords": [
"discord",
"bot",
"commands"
],
"scripts": {
"test": "yarn run lint && yarn run docs:test",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"docs": "docgen --source src --custom docs/index.yml --output docs/docs.json",
"docs:test": "docgen --source src --custom docs/index.yml"
},
"main": "src/index",
"types": "./typings/index.d.ts",
"dependencies": {
"common-tags": "^1.8.0",
"emoji-regex": "^9.2.0",
"is-promise": "^4.0.0",
"require-all": "^3.0.0"
},
"devDependencies": {
"@types/node": "^12.7.3",
"discord.js-docgen": "discordjs/docgen",
"eslint": "^6.3.0",
"typescript": "^3.6.2"
},
"engines": {
"node": ">=12.0.0"
}
}

175
node_modules/discord.js-commando/src/client.js generated vendored Normal file
View File

@@ -0,0 +1,175 @@
const discord = require('discord.js');
const CommandoRegistry = require('./registry');
const CommandDispatcher = require('./dispatcher');
const GuildSettingsHelper = require('./providers/helper');
/**
* Discord.js Client with a command framework
* @extends {Client}
*/
class CommandoClient extends discord.Client {
/**
* Options for a CommandoClient
* @typedef {ClientOptions} CommandoClientOptions
* @property {string} [commandPrefix=!] - Default command prefix
* @property {number} [commandEditableDuration=30] - Time in seconds that command messages should be editable
* @property {boolean} [nonCommandEditable=true] - Whether messages without commands can be edited to a command
* @property {string|string[]|Set<string>} [owner] - ID of the bot owner's Discord user, or multiple IDs
* @property {string} [invite] - Invite URL to the bot's support server
*/
/**
* @param {CommandoClientOptions} [options] - Options for the client
*/
constructor(options = {}) {
if(typeof options.commandPrefix === 'undefined') options.commandPrefix = '!';
if(options.commandPrefix === null) options.commandPrefix = '';
if(typeof options.commandEditableDuration === 'undefined') options.commandEditableDuration = 30;
if(typeof options.nonCommandEditable === 'undefined') options.nonCommandEditable = true;
super(options);
/**
* The client's command registry
* @type {CommandoRegistry}
*/
this.registry = new CommandoRegistry(this);
/**
* The client's command dispatcher
* @type {CommandDispatcher}
*/
this.dispatcher = new CommandDispatcher(this, this.registry);
/**
* The client's setting provider
* @type {?SettingProvider}
*/
this.provider = null;
/**
* Shortcut to use setting provider methods for the global settings
* @type {GuildSettingsHelper}
*/
this.settings = new GuildSettingsHelper(this, null);
/**
* Internal global command prefix, controlled by the {@link CommandoClient#commandPrefix} getter/setter
* @type {?string}
* @private
*/
this._commandPrefix = null;
// Set up command handling
const msgErr = err => { this.emit('error', err); };
this.on('message', message => { this.dispatcher.handleMessage(message).catch(msgErr); });
this.on('messageUpdate', (oldMessage, newMessage) => {
this.dispatcher.handleMessage(newMessage, oldMessage).catch(msgErr);
});
// Fetch the owner(s)
if(options.owner) {
this.once('ready', () => {
if(options.owner instanceof Array || options.owner instanceof Set) {
for(const owner of options.owner) {
this.users.fetch(owner).catch(err => {
this.emit('warn', `Unable to fetch owner ${owner}.`);
this.emit('error', err);
});
}
} else {
this.users.fetch(options.owner).catch(err => {
this.emit('warn', `Unable to fetch owner ${options.owner}.`);
this.emit('error', err);
});
}
});
}
}
/**
* Global command prefix. An empty string indicates that there is no default prefix, and only mentions will be used.
* Setting to `null` means that the default prefix from {@link CommandoClient#options} will be used instead.
* @type {string}
* @emits {@link CommandoClient#commandPrefixChange}
*/
get commandPrefix() {
if(typeof this._commandPrefix === 'undefined' || this._commandPrefix === null) return this.options.commandPrefix;
return this._commandPrefix;
}
set commandPrefix(prefix) {
this._commandPrefix = prefix;
this.emit('commandPrefixChange', null, this._commandPrefix);
}
/**
* Owners of the bot, set by the {@link CommandoClientOptions#owner} option
* <info>If you simply need to check if a user is an owner of the bot, please instead use
* {@link CommandoClient#isOwner}.</info>
* @type {?Array<User>}
* @readonly
*/
get owners() {
if(!this.options.owner) return null;
if(typeof this.options.owner === 'string') return [this.users.cache.get(this.options.owner)];
const owners = [];
for(const owner of this.options.owner) owners.push(this.users.cache.get(owner));
return owners;
}
/**
* Checks whether a user is an owner of the bot (in {@link CommandoClientOptions#owner})
* @param {UserResolvable} user - User to check for ownership
* @return {boolean}
*/
isOwner(user) {
if(!this.options.owner) return false;
user = this.users.resolve(user);
if(!user) throw new RangeError('Unable to resolve user.');
if(typeof this.options.owner === 'string') return user.id === this.options.owner;
if(this.options.owner instanceof Array) return this.options.owner.includes(user.id);
if(this.options.owner instanceof Set) return this.options.owner.has(user.id);
throw new RangeError('The client\'s "owner" option is an unknown value.');
}
/**
* Sets the setting provider to use, and initialises it once the client is ready
* @param {SettingProvider|Promise<SettingProvider>} provider Provider to use
* @return {Promise<void>}
*/
async setProvider(provider) {
const newProvider = await provider;
this.provider = newProvider;
if(this.readyTimestamp) {
this.emit('debug', `Provider set to ${newProvider.constructor.name} - initialising...`);
await newProvider.init(this);
this.emit('debug', 'Provider finished initialisation.');
return undefined;
}
this.emit('debug', `Provider set to ${newProvider.constructor.name} - will initialise once ready.`);
await new Promise(resolve => {
this.once('ready', () => {
this.emit('debug', `Initialising provider...`);
resolve(newProvider.init(this));
});
});
/**
* Emitted upon the client's provider finishing initialisation
* @event CommandoClient#providerReady
* @param {SettingProvider} provider - Provider that was initialised
*/
this.emit('providerReady', provider);
this.emit('debug', 'Provider finished initialisation.');
return undefined;
}
async destroy() {
await super.destroy();
if(this.provider) await this.provider.destroy();
}
}
module.exports = CommandoClient;

View File

@@ -0,0 +1,443 @@
const { escapeMarkdown } = require('discord.js');
const { oneLine, stripIndents } = require('common-tags');
const isPromise = require('is-promise');
const ArgumentUnionType = require('../types/union');
/** A fancy argument */
class Argument {
/**
* @typedef {Object} ArgumentInfo
* @property {string} key - Key for the argument
* @property {string} [label=key] - Label for the argument
* @property {string} prompt - First prompt for the argument when it wasn't specified
* @property {string} [error] - Predefined error message to output for the argument when it isn't valid
* @property {string} [type] - Type of the argument (must be the ID of one of the registered argument types
* or multiple IDs in order of priority separated by `|` for a union type - see
* {@link CommandoRegistry#registerDefaultTypes} for the built-in types)
* @property {number} [max] - If type is `integer` or `float`, this is the maximum value of the number.
* If type is `string`, this is the maximum length of the string.
* @property {number} [min] - If type is `integer` or `float`, this is the minimum value of the number.
* If type is `string`, this is the minimum length of the string.
* @property {ArgumentDefault} [default] - Default value for the argument (makes the arg optional - cannot be `null`)
* @property {string[]} [oneOf] - An array of values that are allowed to be used
* @property {boolean} [infinite=false] - Whether the argument accepts infinite values
* @property {Function} [validate] - Validator function for the argument (see {@link ArgumentType#validate})
* @property {Function} [parse] - Parser function for the argument (see {@link ArgumentType#parse})
* @property {Function} [isEmpty] - Empty checker for the argument (see {@link ArgumentType#isEmpty})
* @property {number} [wait=30] - How long to wait for input (in seconds)
*/
/**
* Either a value or a function that returns a value. The function is passed the CommandoMessage and the Argument.
* @typedef {*|Function} ArgumentDefault
*/
/**
* @param {CommandoClient} client - Client the argument is for
* @param {ArgumentInfo} info - Information for the command argument
*/
constructor(client, info) {
this.constructor.validateInfo(client, info);
/**
* Key for the argument
* @type {string}
*/
this.key = info.key;
/**
* Label for the argument
* @type {string}
*/
this.label = info.label || info.key;
/**
* Question prompt for the argument
* @type {string}
*/
this.prompt = info.prompt;
/**
* Error message for when a value is invalid
* @type {?string}
*/
this.error = info.error || null;
/**
* Type of the argument
* @type {?ArgumentType}
*/
this.type = this.constructor.determineType(client, info.type);
/**
* If type is `integer` or `float`, this is the maximum value of the number.
* If type is `string`, this is the maximum length of the string.
* @type {?number}
*/
this.max = typeof info.max !== 'undefined' ? info.max : null;
/**
* If type is `integer` or `float`, this is the minimum value of the number.
* If type is `string`, this is the minimum length of the string.
* @type {?number}
*/
this.min = typeof info.min !== 'undefined' ? info.min : null;
/**
* The default value for the argument
* @type {?ArgumentDefault}
*/
this.default = typeof info.default !== 'undefined' ? info.default : null;
/**
* Values the user can choose from
* If type is `string`, this will be case-insensitive
* If type is `channel`, `member`, `role`, or `user`, this will be the IDs.
* @type {?string[]}
*/
this.oneOf = typeof info.oneOf !== 'undefined' ?
info.oneOf.map(el => el.toLowerCase ? el.toLowerCase() : el) :
null;
/**
* Whether the argument accepts an infinite number of values
* @type {boolean}
*/
this.infinite = Boolean(info.infinite);
/**
* Validator function for validating a value for the argument
* @type {?Function}
* @see {@link ArgumentType#validate}
*/
this.validator = info.validate || null;
/**
* Parser function for parsing a value for the argument
* @type {?Function}
* @see {@link ArgumentType#parse}
*/
this.parser = info.parse || null;
/**
* Function to check whether a raw value is considered empty
* @type {?Function}
* @see {@link ArgumentType#isEmpty}
*/
this.emptyChecker = info.isEmpty || null;
/**
* How long to wait for input (in seconds)
* @type {number}
*/
this.wait = typeof info.wait !== 'undefined' ? info.wait : 30;
}
/**
* Result object from obtaining a single {@link Argument}'s value(s)
* @typedef {Object} ArgumentResult
* @property {?*|?Array<*>} value - Final value(s) for the argument
* @property {?string} cancelled - One of:
* - `user` (user cancelled)
* - `time` (wait time exceeded)
* - `promptLimit` (prompt limit exceeded)
* @property {Message[]} prompts - All messages that were sent to prompt the user
* @property {Message[]} answers - All of the user's messages that answered a prompt
*/
/**
* Prompts the user and obtains the value for the argument
* @param {CommandoMessage} msg - Message that triggered the command
* @param {string} [val] - Pre-provided value for the argument
* @param {number} [promptLimit=Infinity] - Maximum number of times to prompt for the argument
* @return {Promise<ArgumentResult>}
*/
async obtain(msg, val, promptLimit = Infinity) {
let empty = this.isEmpty(val, msg);
if(empty && this.default !== null) {
return {
value: typeof this.default === 'function' ? await this.default(msg, this) : this.default,
cancelled: null,
prompts: [],
answers: []
};
}
if(this.infinite) return this.obtainInfinite(msg, val, promptLimit);
const wait = this.wait > 0 && this.wait !== Infinity ? this.wait * 1000 : undefined;
const prompts = [];
const answers = [];
let valid = !empty ? await this.validate(val, msg) : false;
while(!valid || typeof valid === 'string') {
/* eslint-disable no-await-in-loop */
if(prompts.length >= promptLimit) {
return {
value: null,
cancelled: 'promptLimit',
prompts,
answers
};
}
// Prompt the user for a new value
prompts.push(await msg.reply(stripIndents`
${empty ? this.prompt : valid ? valid : `You provided an invalid ${this.label}. Please try again.`}
${oneLine`
Respond with \`cancel\` to cancel the command.
${wait ? `The command will automatically be cancelled in ${this.wait} seconds.` : ''}
`}
`));
// Get the user's response
const responses = await msg.channel.awaitMessages(msg2 => msg2.author.id === msg.author.id, {
max: 1,
time: wait
});
// Make sure they actually answered
if(responses && responses.size === 1) {
answers.push(responses.first());
val = answers[answers.length - 1].content;
} else {
return {
value: null,
cancelled: 'time',
prompts,
answers
};
}
// See if they want to cancel
if(val.toLowerCase() === 'cancel') {
return {
value: null,
cancelled: 'user',
prompts,
answers
};
}
empty = this.isEmpty(val, msg, responses.first());
valid = await this.validate(val, msg, responses.first());
/* eslint-enable no-await-in-loop */
}
return {
value: await this.parse(val, msg, answers.length ? answers[answers.length - 1] : msg),
cancelled: null,
prompts,
answers
};
}
/**
* Prompts the user and obtains multiple values for the argument
* @param {CommandoMessage} msg - Message that triggered the command
* @param {string[]} [vals] - Pre-provided values for the argument
* @param {number} [promptLimit=Infinity] - Maximum number of times to prompt for the argument
* @return {Promise<ArgumentResult>}
* @private
*/
async obtainInfinite(msg, vals, promptLimit = Infinity) { // eslint-disable-line complexity
const wait = this.wait > 0 && this.wait !== Infinity ? this.wait * 1000 : undefined;
const results = [];
const prompts = [];
const answers = [];
let currentVal = 0;
while(true) { // eslint-disable-line no-constant-condition
/* eslint-disable no-await-in-loop */
let val = vals && vals[currentVal] ? vals[currentVal] : null;
let valid = val ? await this.validate(val, msg) : false;
let attempts = 0;
while(!valid || typeof valid === 'string') {
attempts++;
if(attempts > promptLimit) {
return {
value: null,
cancelled: 'promptLimit',
prompts,
answers
};
}
// Prompt the user for a new value
if(val) {
const escaped = escapeMarkdown(val).replace(/@/g, '@\u200b');
prompts.push(await msg.reply(stripIndents`
${valid ? valid : oneLine`
You provided an invalid ${this.label},
"${escaped.length < 1850 ? escaped : '[too long to show]'}".
Please try again.
`}
${oneLine`
Respond with \`cancel\` to cancel the command, or \`finish\` to finish entry up to this point.
${wait ? `The command will automatically be cancelled in ${this.wait} seconds.` : ''}
`}
`));
} else if(results.length === 0) {
prompts.push(await msg.reply(stripIndents`
${this.prompt}
${oneLine`
Respond with \`cancel\` to cancel the command, or \`finish\` to finish entry.
${wait ? `The command will automatically be cancelled in ${this.wait} seconds, unless you respond.` : ''}
`}
`));
}
// Get the user's response
const responses = await msg.channel.awaitMessages(msg2 => msg2.author.id === msg.author.id, {
max: 1,
time: wait
});
// Make sure they actually answered
if(responses && responses.size === 1) {
answers.push(responses.first());
val = answers[answers.length - 1].content;
} else {
return {
value: null,
cancelled: 'time',
prompts,
answers
};
}
// See if they want to finish or cancel
const lc = val.toLowerCase();
if(lc === 'finish') {
return {
value: results.length > 0 ? results : null,
cancelled: this.default ? null : results.length > 0 ? null : 'user',
prompts,
answers
};
}
if(lc === 'cancel') {
return {
value: null,
cancelled: 'user',
prompts,
answers
};
}
valid = await this.validate(val, msg, responses.first());
}
results.push(await this.parse(val, msg, answers.length ? answers[answers.length - 1] : msg));
if(vals) {
currentVal++;
if(currentVal === vals.length) {
return {
value: results,
cancelled: null,
prompts,
answers
};
}
}
/* eslint-enable no-await-in-loop */
}
}
/**
* Checks if a value is valid for the argument
* @param {string} val - Value to check
* @param {CommandoMessage} originalMsg - Message that triggered the command
* @param {?CommandoMessage} [currentMsg=originalMsg] - Current response message
* @return {boolean|string|Promise<boolean|string>}
*/
validate(val, originalMsg, currentMsg = originalMsg) {
const valid = this.validator ?
this.validator(val, originalMsg, this, currentMsg) :
this.type.validate(val, originalMsg, this, currentMsg);
if(!valid || typeof valid === 'string') return this.error || valid;
if(isPromise(valid)) return valid.then(vld => !vld || typeof vld === 'string' ? this.error || vld : vld);
return valid;
}
/**
* Parses a value string into a proper value for the argument
* @param {string} val - Value to parse
* @param {CommandoMessage} originalMsg - Message that triggered the command
* @param {?CommandoMessage} [currentMsg=originalMsg] - Current response message
* @return {*|Promise<*>}
*/
parse(val, originalMsg, currentMsg = originalMsg) {
if(this.parser) return this.parser(val, originalMsg, this, currentMsg);
return this.type.parse(val, originalMsg, this, currentMsg);
}
/**
* Checks whether a value for the argument is considered to be empty
* @param {string} val - Value to check for emptiness
* @param {CommandoMessage} originalMsg - Message that triggered the command
* @param {?CommandoMessage} [currentMsg=originalMsg] - Current response message
* @return {boolean}
*/
isEmpty(val, originalMsg, currentMsg = originalMsg) {
if(this.emptyChecker) return this.emptyChecker(val, originalMsg, this, currentMsg);
if(this.type) return this.type.isEmpty(val, originalMsg, this, currentMsg);
if(Array.isArray(val)) return val.length === 0;
return !val;
}
/**
* Validates the constructor parameters
* @param {CommandoClient} client - Client to validate
* @param {ArgumentInfo} info - Info to validate
* @private
*/
static validateInfo(client, info) { // eslint-disable-line complexity
if(!client) throw new Error('The argument client must be specified.');
if(typeof info !== 'object') throw new TypeError('Argument info must be an Object.');
if(typeof info.key !== 'string') throw new TypeError('Argument key must be a string.');
if(info.label && typeof info.label !== 'string') throw new TypeError('Argument label must be a string.');
if(typeof info.prompt !== 'string') throw new TypeError('Argument prompt must be a string.');
if(info.error && typeof info.error !== 'string') throw new TypeError('Argument error must be a string.');
if(info.type && typeof info.type !== 'string') throw new TypeError('Argument type must be a string.');
if(info.type && !info.type.includes('|') && !client.registry.types.has(info.type)) {
throw new RangeError(`Argument type "${info.type}" isn't registered.`);
}
if(!info.type && !info.validate) {
throw new Error('Argument must have either "type" or "validate" specified.');
}
if(info.validate && typeof info.validate !== 'function') {
throw new TypeError('Argument validate must be a function.');
}
if(info.parse && typeof info.parse !== 'function') {
throw new TypeError('Argument parse must be a function.');
}
if(!info.type && (!info.validate || !info.parse)) {
throw new Error('Argument must have both validate and parse since it doesn\'t have a type.');
}
if(typeof info.wait !== 'undefined' && (typeof info.wait !== 'number' || Number.isNaN(info.wait))) {
throw new TypeError('Argument wait must be a number.');
}
}
/**
* Gets the argument type to use from an ID
* @param {CommandoClient} client - Client to use the registry of
* @param {string} id - ID of the type to use
* @returns {?ArgumentType}
* @private
*/
static determineType(client, id) {
if(!id) return null;
if(!id.includes('|')) return client.registry.types.get(id);
let type = client.registry.types.get(id);
if(type) return type;
type = new ArgumentUnionType(client, id);
client.registry.registerType(type);
return type;
}
}
module.exports = Argument;

571
node_modules/discord.js-commando/src/commands/base.js generated vendored Normal file
View File

@@ -0,0 +1,571 @@
const path = require('path');
const { escapeMarkdown } = require('discord.js');
const { oneLine, stripIndents } = require('common-tags');
const ArgumentCollector = require('./collector');
const { permissions } = require('../util');
/** A command that can be run in a client */
class Command {
/**
* @typedef {Object} ThrottlingOptions
* @property {number} usages - Maximum number of usages of the command allowed in the time frame.
* @property {number} duration - Amount of time to count the usages of the command within (in seconds).
*/
/**
* @typedef {Object} CommandInfo
* @property {string} name - The name of the command (must be lowercase)
* @property {string[]} [aliases] - Alternative names for the command (all must be lowercase)
* @property {boolean} [autoAliases=true] - Whether automatic aliases should be added
* @property {string} group - The ID of the group the command belongs to (must be lowercase)
* @property {string} memberName - The member name of the command in the group (must be lowercase)
* @property {string} description - A short description of the command
* @property {string} [format] - The command usage format string - will be automatically generated if not specified,
* and `args` is specified
* @property {string} [details] - A detailed description of the command and its functionality
* @property {string[]} [examples] - Usage examples of the command
* @property {boolean} [guildOnly=false] - Whether or not the command should only function in a guild channel
* @property {boolean} [ownerOnly=false] - Whether or not the command is usable only by an owner
* @property {PermissionResolvable[]} [clientPermissions] - Permissions required by the client to use the command.
* @property {PermissionResolvable[]} [userPermissions] - Permissions required by the user to use the command.
* @property {boolean} [nsfw=false] - Whether the command is usable only in NSFW channels.
* @property {ThrottlingOptions} [throttling] - Options for throttling usages of the command.
* @property {boolean} [defaultHandling=true] - Whether or not the default command handling should be used.
* If false, then only patterns will trigger the command.
* @property {ArgumentInfo[]} [args] - Arguments for the command.
* @property {number} [argsPromptLimit=Infinity] - Maximum number of times to prompt a user for a single argument.
* Only applicable if `args` is specified.
* @property {string} [argsType=single] - One of 'single' or 'multiple'. Only applicable if `args` is not specified.
* When 'single', the entire argument string will be passed to run as one argument.
* When 'multiple', it will be passed as multiple arguments.
* @property {number} [argsCount=0] - The number of arguments to parse from the command string.
* Only applicable when argsType is 'multiple'. If nonzero, it should be at least 2.
* When this is 0, the command argument string will be split into as many arguments as it can be.
* When nonzero, it will be split into a maximum of this number of arguments.
* @property {boolean} [argsSingleQuotes=true] - Whether or not single quotes should be allowed to box-in arguments
* in the command string.
* @property {RegExp[]} [patterns] - Patterns to use for triggering the command
* @property {boolean} [guarded=false] - Whether the command should be protected from disabling
* @property {boolean} [hidden=false] - Whether the command should be hidden from the help command
* @property {boolean} [unknown=false] - Whether the command should be run when an unknown command is used - there
* may only be one command registered with this property as `true`.
*/
/**
* @param {CommandoClient} client - The client the command is for
* @param {CommandInfo} info - The command information
*/
// eslint-disable-next-line complexity
constructor(client, info) {
this.constructor.validateInfo(client, info);
/**
* Client that this command is for
* @name Command#client
* @type {CommandoClient}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });
/**
* Name of this command
* @type {string}
*/
this.name = info.name;
/**
* Aliases for this command
* @type {string[]}
*/
this.aliases = info.aliases || [];
if(typeof info.autoAliases === 'undefined' || info.autoAliases) {
if(this.name.includes('-')) this.aliases.push(this.name.replace(/-/g, ''));
for(const alias of this.aliases) {
if(alias.includes('-')) this.aliases.push(alias.replace(/-/g, ''));
}
}
/**
* ID of the group the command belongs to
* @type {string}
*/
this.groupID = info.group;
/**
* The group the command belongs to, assigned upon registration
* @type {?CommandGroup}
*/
this.group = null;
/**
* Name of the command within the group
* @type {string}
*/
this.memberName = info.memberName;
/**
* Short description of the command
* @type {string}
*/
this.description = info.description;
/**
* Usage format string of the command
* @type {string}
*/
this.format = info.format || null;
/**
* Long description of the command
* @type {?string}
*/
this.details = info.details || null;
/**
* Example usage strings
* @type {?string[]}
*/
this.examples = info.examples || null;
/**
* Whether the command can only be run in a guild channel
* @type {boolean}
*/
this.guildOnly = Boolean(info.guildOnly);
/**
* Whether the command can only be used by an owner
* @type {boolean}
*/
this.ownerOnly = Boolean(info.ownerOnly);
/**
* Permissions required by the client to use the command.
* @type {?PermissionResolvable[]}
*/
this.clientPermissions = info.clientPermissions || null;
/**
* Permissions required by the user to use the command.
* @type {?PermissionResolvable[]}
*/
this.userPermissions = info.userPermissions || null;
/**
* Whether the command can only be used in NSFW channels
* @type {boolean}
*/
this.nsfw = Boolean(info.nsfw);
/**
* Whether the default command handling is enabled for the command
* @type {boolean}
*/
this.defaultHandling = 'defaultHandling' in info ? info.defaultHandling : true;
/**
* Options for throttling command usages
* @type {?ThrottlingOptions}
*/
this.throttling = info.throttling || null;
/**
* The argument collector for the command
* @type {?ArgumentCollector}
*/
this.argsCollector = info.args && info.args.length ?
new ArgumentCollector(client, info.args, info.argsPromptLimit) :
null;
if(this.argsCollector && typeof info.format === 'undefined') {
this.format = this.argsCollector.args.reduce((prev, arg) => {
const wrapL = arg.default !== null ? '[' : '<';
const wrapR = arg.default !== null ? ']' : '>';
return `${prev}${prev ? ' ' : ''}${wrapL}${arg.label}${arg.infinite ? '...' : ''}${wrapR}`;
}, '');
}
/**
* How the arguments are split when passed to the command's run method
* @type {string}
*/
this.argsType = info.argsType || 'single';
/**
* Maximum number of arguments that will be split
* @type {number}
*/
this.argsCount = info.argsCount || 0;
/**
* Whether single quotes are allowed to encapsulate an argument
* @type {boolean}
*/
this.argsSingleQuotes = 'argsSingleQuotes' in info ? info.argsSingleQuotes : true;
/**
* Regular expression triggers
* @type {RegExp[]}
*/
this.patterns = info.patterns || null;
/**
* Whether the command is protected from being disabled
* @type {boolean}
*/
this.guarded = Boolean(info.guarded);
/**
* Whether the command should be hidden from the help command
* @type {boolean}
*/
this.hidden = Boolean(info.hidden);
/**
* Whether the command will be run when an unknown command is used
* @type {boolean}
*/
this.unknown = Boolean(info.unknown);
/**
* Whether the command is enabled globally
* @type {boolean}
* @private
*/
this._globalEnabled = true;
/**
* Current throttle objects for the command, mapped by user ID
* @type {Map<string, Object>}
* @private
*/
this._throttles = new Map();
}
/**
* Checks whether the user has permission to use the command
* @param {CommandoMessage} message - The triggering command message
* @param {boolean} [ownerOverride=true] - Whether the bot owner(s) will always have permission
* @return {boolean|string} Whether the user has permission, or an error message to respond with if they don't
*/
hasPermission(message, ownerOverride = true) {
if(!this.ownerOnly && !this.userPermissions) return true;
if(ownerOverride && this.client.isOwner(message.author)) return true;
if(this.ownerOnly && (ownerOverride || !this.client.isOwner(message.author))) {
return `The \`${this.name}\` command can only be used by the bot owner.`;
}
if(message.channel.type === 'text' && this.userPermissions) {
const missing = message.channel.permissionsFor(message.author).missing(this.userPermissions);
if(missing.length > 0) {
if(missing.length === 1) {
return `The \`${this.name}\` command requires you to have the "${permissions[missing[0]]}" permission.`;
}
return oneLine`
The \`${this.name}\` command requires you to have the following permissions:
${missing.map(perm => permissions[perm]).join(', ')}
`;
}
}
return true;
}
/**
* Runs the command
* @param {CommandoMessage} message - The message the command is being run for
* @param {Object|string|string[]} args - The arguments for the command, or the matches from a pattern.
* If args is specified on the command, thise will be the argument values object. If argsType is single, then only
* one string will be passed. If multiple, an array of strings will be passed. When fromPattern is true, this is the
* matches array from the pattern match
* (see [RegExp#exec](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec)).
* @param {boolean} fromPattern - Whether or not the command is being run from a pattern match
* @param {?ArgumentCollectorResult} result - Result from obtaining the arguments from the collector (if applicable)
* @return {Promise<?Message|?Array<Message>>}
* @abstract
*/
async run(message, args, fromPattern, result) { // eslint-disable-line no-unused-vars, require-await
throw new Error(`${this.constructor.name} doesn't have a run() method.`);
}
/**
* Called when the command is prevented from running
* @param {CommandMessage} message - Command message that the command is running from
* @param {string} reason - Reason that the command was blocked
* (built-in reasons are `guildOnly`, `nsfw`, `permission`, `throttling`, and `clientPermissions`)
* @param {Object} [data] - Additional data associated with the block. Built-in reason data properties:
* - guildOnly: none
* - nsfw: none
* - permission: `response` ({@link string}) to send
* - throttling: `throttle` ({@link Object}), `remaining` ({@link number}) time in seconds
* - clientPermissions: `missing` ({@link Array}<{@link string}>) permission names
* @returns {Promise<?Message|?Array<Message>>}
*/
onBlock(message, reason, data) {
switch(reason) {
case 'guildOnly':
return message.reply(`The \`${this.name}\` command must be used in a server channel.`);
case 'nsfw':
return message.reply(`The \`${this.name}\` command can only be used in NSFW channels.`);
case 'permission': {
if(data.response) return message.reply(data.response);
return message.reply(`You do not have permission to use the \`${this.name}\` command.`);
}
case 'clientPermissions': {
if(data.missing.length === 1) {
return message.reply(
`I need the "${permissions[data.missing[0]]}" permission for the \`${this.name}\` command to work.`
);
}
return message.reply(oneLine`
I need the following permissions for the \`${this.name}\` command to work:
${data.missing.map(perm => permissions[perm]).join(', ')}
`);
}
case 'throttling': {
return message.reply(
`You may not use the \`${this.name}\` command again for another ${data.remaining.toFixed(1)} seconds.`
);
}
default:
return null;
}
}
/**
* Called when the command produces an error while running
* @param {Error} err - Error that was thrown
* @param {CommandMessage} message - Command message that the command is running from (see {@link Command#run})
* @param {Object|string|string[]} args - Arguments for the command (see {@link Command#run})
* @param {boolean} fromPattern - Whether the args are pattern matches (see {@link Command#run})
* @param {?ArgumentCollectorResult} result - Result from obtaining the arguments from the collector
* (if applicable - see {@link Command#run})
* @returns {Promise<?Message|?Array<Message>>}
*/
onError(err, message, args, fromPattern, result) { // eslint-disable-line no-unused-vars
const owners = this.client.owners;
const ownerList = owners ? owners.map((usr, i) => {
const or = i === owners.length - 1 && owners.length > 1 ? 'or ' : '';
return `${or}${escapeMarkdown(usr.username)}#${usr.discriminator}`;
}).join(owners.length > 2 ? ', ' : ' ') : '';
const invite = this.client.options.invite;
return message.reply(stripIndents`
An error occurred while running the command: \`${err.name}: ${err.message}\`
You shouldn't ever receive an error like this.
Please contact ${ownerList || 'the bot owner'}${invite ? ` in this server: ${invite}` : '.'}
`);
}
/**
* Creates/obtains the throttle object for a user, if necessary (owners are excluded)
* @param {string} userID - ID of the user to throttle for
* @return {?Object}
* @private
*/
throttle(userID) {
if(!this.throttling || this.client.isOwner(userID)) return null;
let throttle = this._throttles.get(userID);
if(!throttle) {
throttle = {
start: Date.now(),
usages: 0,
timeout: this.client.setTimeout(() => {
this._throttles.delete(userID);
}, this.throttling.duration * 1000)
};
this._throttles.set(userID, throttle);
}
return throttle;
}
/**
* Enables or disables the command in a guild
* @param {?GuildResolvable} guild - Guild to enable/disable the command in
* @param {boolean} enabled - Whether the command should be enabled or disabled
*/
setEnabledIn(guild, enabled) {
if(typeof guild === 'undefined') throw new TypeError('Guild must not be undefined.');
if(typeof enabled === 'undefined') throw new TypeError('Enabled must not be undefined.');
if(this.guarded) throw new Error('The command is guarded.');
if(!guild) {
this._globalEnabled = enabled;
this.client.emit('commandStatusChange', null, this, enabled);
return;
}
guild = this.client.guilds.resolve(guild);
guild.setCommandEnabled(this, enabled);
}
/**
* Checks if the command is enabled in a guild
* @param {?GuildResolvable} guild - Guild to check in
* @param {boolean} [bypassGroup] - Whether to bypass checking the group's status
* @return {boolean}
*/
isEnabledIn(guild, bypassGroup) {
if(this.guarded) return true;
if(!guild) return this.group._globalEnabled && this._globalEnabled;
guild = this.client.guilds.resolve(guild);
return (bypassGroup || guild.isGroupEnabled(this.group)) && guild.isCommandEnabled(this);
}
/**
* Checks if the command is usable for a message
* @param {?Message} message - The message
* @return {boolean}
*/
isUsable(message = null) {
if(!message) return this._globalEnabled;
if(this.guildOnly && message && !message.guild) return false;
const hasPermission = this.hasPermission(message);
return this.isEnabledIn(message.guild) && hasPermission && typeof hasPermission !== 'string';
}
/**
* Creates a usage string for the command
* @param {string} [argString] - A string of arguments for the command
* @param {string} [prefix=this.client.commandPrefix] - Prefix to use for the prefixed command format
* @param {User} [user=this.client.user] - User to use for the mention command format
* @return {string}
*/
usage(argString, prefix = this.client.commandPrefix, user = this.client.user) {
return this.constructor.usage(`${this.name}${argString ? ` ${argString}` : ''}`, prefix, user);
}
/**
* Reloads the command
*/
reload() {
let cmdPath, cached, newCmd;
try {
cmdPath = this.client.registry.resolveCommandPath(this.groupID, this.memberName);
cached = require.cache[cmdPath];
delete require.cache[cmdPath];
newCmd = require(cmdPath);
} catch(err) {
if(cached) require.cache[cmdPath] = cached;
try {
cmdPath = path.join(__dirname, this.groupID, `${this.memberName}.js`);
cached = require.cache[cmdPath];
delete require.cache[cmdPath];
newCmd = require(cmdPath);
} catch(err2) {
if(cached) require.cache[cmdPath] = cached;
if(err2.message.includes('Cannot find module')) throw err; else throw err2;
}
}
this.client.registry.reregisterCommand(newCmd, this);
}
/**
* Unloads the command
*/
unload() {
const cmdPath = this.client.registry.resolveCommandPath(this.groupID, this.memberName);
if(!require.cache[cmdPath]) throw new Error('Command cannot be unloaded.');
delete require.cache[cmdPath];
this.client.registry.unregisterCommand(this);
}
/**
* Creates a usage string for a command
* @param {string} command - A command + arg string
* @param {string} [prefix] - Prefix to use for the prefixed command format
* @param {User} [user] - User to use for the mention command format
* @return {string}
*/
static usage(command, prefix = null, user = null) {
const nbcmd = command.replace(/ /g, '\xa0');
if(!prefix && !user) return `\`${nbcmd}\``;
let prefixPart;
if(prefix) {
if(prefix.length > 1 && !prefix.endsWith(' ')) prefix += ' ';
prefix = prefix.replace(/ /g, '\xa0');
prefixPart = `\`${prefix}${nbcmd}\``;
}
let mentionPart;
if(user) mentionPart = `\`@${user.username.replace(/ /g, '\xa0')}#${user.discriminator}\xa0${nbcmd}\``;
return `${prefixPart || ''}${prefix && user ? ' or ' : ''}${mentionPart || ''}`;
}
/**
* Validates the constructor parameters
* @param {CommandoClient} client - Client to validate
* @param {CommandInfo} info - Info to validate
* @private
*/
static validateInfo(client, info) { // eslint-disable-line complexity
if(!client) throw new Error('A client must be specified.');
if(typeof info !== 'object') throw new TypeError('Command info must be an Object.');
if(typeof info.name !== 'string') throw new TypeError('Command name must be a string.');
if(info.name !== info.name.toLowerCase()) throw new Error('Command name must be lowercase.');
if(info.aliases && (!Array.isArray(info.aliases) || info.aliases.some(ali => typeof ali !== 'string'))) {
throw new TypeError('Command aliases must be an Array of strings.');
}
if(info.aliases && info.aliases.some(ali => ali !== ali.toLowerCase())) {
throw new RangeError('Command aliases must be lowercase.');
}
if(typeof info.group !== 'string') throw new TypeError('Command group must be a string.');
if(info.group !== info.group.toLowerCase()) throw new RangeError('Command group must be lowercase.');
if(typeof info.memberName !== 'string') throw new TypeError('Command memberName must be a string.');
if(info.memberName !== info.memberName.toLowerCase()) throw new Error('Command memberName must be lowercase.');
if(typeof info.description !== 'string') throw new TypeError('Command description must be a string.');
if('format' in info && typeof info.format !== 'string') throw new TypeError('Command format must be a string.');
if('details' in info && typeof info.details !== 'string') throw new TypeError('Command details must be a string.');
if(info.examples && (!Array.isArray(info.examples) || info.examples.some(ex => typeof ex !== 'string'))) {
throw new TypeError('Command examples must be an Array of strings.');
}
if(info.clientPermissions) {
if(!Array.isArray(info.clientPermissions)) {
throw new TypeError('Command clientPermissions must be an Array of permission key strings.');
}
for(const perm of info.clientPermissions) {
if(!permissions[perm]) throw new RangeError(`Invalid command clientPermission: ${perm}`);
}
}
if(info.userPermissions) {
if(!Array.isArray(info.userPermissions)) {
throw new TypeError('Command userPermissions must be an Array of permission key strings.');
}
for(const perm of info.userPermissions) {
if(!permissions[perm]) throw new RangeError(`Invalid command userPermission: ${perm}`);
}
}
if(info.throttling) {
if(typeof info.throttling !== 'object') throw new TypeError('Command throttling must be an Object.');
if(typeof info.throttling.usages !== 'number' || isNaN(info.throttling.usages)) {
throw new TypeError('Command throttling usages must be a number.');
}
if(info.throttling.usages < 1) throw new RangeError('Command throttling usages must be at least 1.');
if(typeof info.throttling.duration !== 'number' || isNaN(info.throttling.duration)) {
throw new TypeError('Command throttling duration must be a number.');
}
if(info.throttling.duration < 1) throw new RangeError('Command throttling duration must be at least 1.');
}
if(info.args && !Array.isArray(info.args)) throw new TypeError('Command args must be an Array.');
if('argsPromptLimit' in info && typeof info.argsPromptLimit !== 'number') {
throw new TypeError('Command argsPromptLimit must be a number.');
}
if('argsPromptLimit' in info && info.argsPromptLimit < 0) {
throw new RangeError('Command argsPromptLimit must be at least 0.');
}
if(info.argsType && !['single', 'multiple'].includes(info.argsType)) {
throw new RangeError('Command argsType must be one of "single" or "multiple".');
}
if(info.argsType === 'multiple' && info.argsCount && info.argsCount < 2) {
throw new RangeError('Command argsCount must be at least 2.');
}
if(info.patterns && (!Array.isArray(info.patterns) || info.patterns.some(pat => !(pat instanceof RegExp)))) {
throw new TypeError('Command patterns must be an Array of regular expressions.');
}
}
}
module.exports = Command;

View File

@@ -0,0 +1,105 @@
const Argument = require('./argument');
/** Obtains, validates, and prompts for argument values */
class ArgumentCollector {
/**
* @param {CommandoClient} client - Client the collector will use
* @param {ArgumentInfo[]} args - Arguments for the collector
* @param {number} [promptLimit=Infinity] - Maximum number of times to prompt for a single argument
*/
constructor(client, args, promptLimit = Infinity) {
if(!client) throw new TypeError('Collector client must be specified.');
if(!args || !Array.isArray(args)) throw new TypeError('Collector args must be an Array.');
if(promptLimit === null) promptLimit = Infinity;
/**
* Client this collector is for
* @name ArgumentCollector#client
* @type {CommandoClient}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });
/**
* Arguments the collector handles
* @type {Argument[]}
*/
this.args = new Array(args.length);
let hasInfinite = false;
let hasOptional = false;
for(let i = 0; i < args.length; i++) {
if(hasInfinite) throw new Error('No other argument may come after an infinite argument.');
if(args[i].default !== null) hasOptional = true;
else if(hasOptional) throw new Error('Required arguments may not come after optional arguments.');
this.args[i] = new Argument(this.client, args[i]);
if(this.args[i].infinite) hasInfinite = true;
}
/**
* Maximum number of times to prompt for a single argument
* @type {number}
*/
this.promptLimit = promptLimit;
}
/**
* Result object from obtaining argument values from an {@link ArgumentCollector}
* @typedef {Object} ArgumentCollectorResult
* @property {?Object} values - Final values for the arguments, mapped by their keys
* @property {?string} cancelled - One of:
* - `user` (user cancelled)
* - `time` (wait time exceeded)
* - `promptLimit` (prompt limit exceeded)
* @property {Message[]} prompts - All messages that were sent to prompt the user
* @property {Message[]} answers - All of the user's messages that answered a prompt
*/
/**
* Obtains values for the arguments, prompting if necessary.
* @param {CommandoMessage} msg - Message that the collector is being triggered by
* @param {Array<*>} [provided=[]] - Values that are already available
* @param {number} [promptLimit=this.promptLimit] - Maximum number of times to prompt for a single argument
* @return {Promise<ArgumentCollectorResult>}
*/
async obtain(msg, provided = [], promptLimit = this.promptLimit) {
this.client.dispatcher._awaiting.add(msg.author.id + msg.channel.id);
const values = {};
const results = [];
try {
for(let i = 0; i < this.args.length; i++) {
/* eslint-disable no-await-in-loop */
const arg = this.args[i];
const result = await arg.obtain(msg, arg.infinite ? provided.slice(i) : provided[i], promptLimit);
results.push(result);
if(result.cancelled) {
this.client.dispatcher._awaiting.delete(msg.author.id + msg.channel.id);
return {
values: null,
cancelled: result.cancelled,
prompts: [].concat(...results.map(res => res.prompts)),
answers: [].concat(...results.map(res => res.answers))
};
}
values[arg.key] = result.value;
/* eslint-enable no-await-in-loop */
}
} catch(err) {
this.client.dispatcher._awaiting.delete(msg.author.id + msg.channel.id);
throw err;
}
this.client.dispatcher._awaiting.delete(msg.author.id + msg.channel.id);
return {
values,
cancelled: null,
prompts: [].concat(...results.map(res => res.prompts)),
answers: [].concat(...results.map(res => res.answers))
};
}
}
module.exports = ArgumentCollector;

View File

@@ -0,0 +1,49 @@
const { oneLine } = require('common-tags');
const Command = require('../base');
module.exports = class DisableCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'disable',
aliases: ['disable-command', 'cmd-off', 'command-off'],
group: 'commands',
memberName: 'disable',
description: 'Disables a command or command group.',
details: oneLine`
The argument must be the name/ID (partial or whole) of a command or command group.
Only administrators may use this command.
`,
examples: ['disable util', 'disable Utility', 'disable prefix'],
guarded: true,
args: [
{
key: 'cmdOrGrp',
label: 'command/group',
prompt: 'Which command or group would you like to disable?',
type: 'group|command'
}
]
});
}
hasPermission(msg) {
if(!msg.guild) return this.client.isOwner(msg.author);
return msg.member.permissions.has('ADMINISTRATOR') || this.client.isOwner(msg.author);
}
run(msg, args) {
if(!args.cmdOrGrp.isEnabledIn(msg.guild, true)) {
return msg.reply(
`The \`${args.cmdOrGrp.name}\` ${args.cmdOrGrp.group ? 'command' : 'group'} is already disabled.`
);
}
if(args.cmdOrGrp.guarded) {
return msg.reply(
`You cannot disable the \`${args.cmdOrGrp.name}\` ${args.cmdOrGrp.group ? 'command' : 'group'}.`
);
}
args.cmdOrGrp.setEnabledIn(msg.guild, false);
return msg.reply(`Disabled the \`${args.cmdOrGrp.name}\` ${args.cmdOrGrp.group ? 'command' : 'group'}.`);
}
};

View File

@@ -0,0 +1,55 @@
const { oneLine } = require('common-tags');
const Command = require('../base');
module.exports = class EnableCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'enable',
aliases: ['enable-command', 'cmd-on', 'command-on'],
group: 'commands',
memberName: 'enable',
description: 'Enables a command or command group.',
details: oneLine`
The argument must be the name/ID (partial or whole) of a command or command group.
Only administrators may use this command.
`,
examples: ['enable util', 'enable Utility', 'enable prefix'],
guarded: true,
args: [
{
key: 'cmdOrGrp',
label: 'command/group',
prompt: 'Which command or group would you like to enable?',
type: 'group|command'
}
]
});
}
hasPermission(msg) {
if(!msg.guild) return this.client.isOwner(msg.author);
return msg.member.permissions.has('ADMINISTRATOR') || this.client.isOwner(msg.author);
}
run(msg, args) {
const group = args.cmdOrGrp.group;
if(args.cmdOrGrp.isEnabledIn(msg.guild, true)) {
return msg.reply(
`The \`${args.cmdOrGrp.name}\` ${args.cmdOrGrp.group ? 'command' : 'group'} is already enabled${
group && !group.isEnabledIn(msg.guild) ?
`, but the \`${group.name}\` group is disabled, so it still can't be used` :
''
}.`
);
}
args.cmdOrGrp.setEnabledIn(msg.guild, true);
return msg.reply(
`Enabled the \`${args.cmdOrGrp.name}\` ${group ? 'command' : 'group'}${
group && !group.isEnabledIn(msg.guild) ?
`, but the \`${group.name}\` group is disabled, so it still can't be used` :
''
}.`
);
}
};

View File

@@ -0,0 +1,30 @@
const { stripIndents } = require('common-tags');
const Command = require('../base');
module.exports = class ListGroupsCommand extends Command {
constructor(client) {
super(client, {
name: 'groups',
aliases: ['list-groups', 'show-groups'],
group: 'commands',
memberName: 'groups',
description: 'Lists all command groups.',
details: 'Only administrators may use this command.',
guarded: true
});
}
hasPermission(msg) {
if(!msg.guild) return this.client.isOwner(msg.author);
return msg.member.permissions.has('ADMINISTRATOR') || this.client.isOwner(msg.author);
}
run(msg) {
return msg.reply(stripIndents`
__**Groups**__
${this.client.registry.groups.map(grp =>
`**${grp.name}:** ${grp.isEnabledIn(msg.guild) ? 'Enabled' : 'Disabled'}`
).join('\n')}
`);
}
};

View File

@@ -0,0 +1,72 @@
const fs = require('fs');
const { oneLine } = require('common-tags');
const Command = require('../base');
module.exports = class LoadCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'load',
aliases: ['load-command'],
group: 'commands',
memberName: 'load',
description: 'Loads a new command.',
details: oneLine`
The argument must be full name of the command in the format of \`group:memberName\`.
Only the bot owner(s) may use this command.
`,
examples: ['load some-command'],
ownerOnly: true,
guarded: true,
args: [
{
key: 'command',
prompt: 'Which command would you like to load?',
validate: val => new Promise(resolve => {
if(!val) return resolve(false);
const split = val.split(':');
if(split.length !== 2) return resolve(false);
if(this.client.registry.findCommands(val).length > 0) {
return resolve('That command is already registered.');
}
const cmdPath = this.client.registry.resolveCommandPath(split[0], split[1]);
fs.access(cmdPath, fs.constants.R_OK, err => err ? resolve(false) : resolve(true));
return null;
}),
parse: val => {
const split = val.split(':');
const cmdPath = this.client.registry.resolveCommandPath(split[0], split[1]);
delete require.cache[cmdPath];
return require(cmdPath);
}
}
]
});
}
async run(msg, args) {
this.client.registry.registerCommand(args.command);
const command = this.client.registry.commands.last();
if(this.client.shard) {
try {
await this.client.shard.broadcastEval(`
const ids = [${this.client.shard.ids.join(',')}];
if(!this.shard.ids.some(id => ids.includes(id))) {
const cmdPath = this.registry.resolveCommandPath('${command.groupID}', '${command.name}');
delete require.cache[cmdPath];
this.registry.registerCommand(require(cmdPath));
}
`);
} catch(err) {
this.client.emit('warn', `Error when broadcasting command load to other shards`);
this.client.emit('error', err);
await msg.reply(`Loaded \`${command.name}\` command, but failed to load on other shards.`);
return null;
}
}
await msg.reply(`Loaded \`${command.name}\` command${this.client.shard ? ' on all shards' : ''}.`);
return null;
}
};

View File

@@ -0,0 +1,68 @@
const { oneLine } = require('common-tags');
const Command = require('../base');
module.exports = class ReloadCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'reload',
aliases: ['reload-command'],
group: 'commands',
memberName: 'reload',
description: 'Reloads a command or command group.',
details: oneLine`
The argument must be the name/ID (partial or whole) of a command or command group.
Providing a command group will reload all of the commands in that group.
Only the bot owner(s) may use this command.
`,
examples: ['reload some-command'],
ownerOnly: true,
guarded: true,
args: [
{
key: 'cmdOrGrp',
label: 'command/group',
prompt: 'Which command or group would you like to reload?',
type: 'group|command'
}
]
});
}
async run(msg, args) {
const { cmdOrGrp } = args;
const isCmd = Boolean(cmdOrGrp.groupID);
cmdOrGrp.reload();
if(this.client.shard) {
try {
await this.client.shard.broadcastEval(`
const ids = [${this.client.shard.ids.join(',')}];
if(!this.shard.ids.some(id => ids.includes(id))) {
this.registry.${isCmd ? 'commands' : 'groups'}.get('${isCmd ? cmdOrGrp.name : cmdOrGrp.id}').reload();
}
`);
} catch(err) {
this.client.emit('warn', `Error when broadcasting command reload to other shards`);
this.client.emit('error', err);
if(isCmd) {
await msg.reply(`Reloaded \`${cmdOrGrp.name}\` command, but failed to reload on other shards.`);
} else {
await msg.reply(
`Reloaded all of the commands in the \`${cmdOrGrp.name}\` group, but failed to reload on other shards.`
);
}
return null;
}
}
if(isCmd) {
await msg.reply(`Reloaded \`${cmdOrGrp.name}\` command${this.client.shard ? ' on all shards' : ''}.`);
} else {
await msg.reply(
`Reloaded all of the commands in the \`${cmdOrGrp.name}\` group${this.client.shard ? ' on all shards' : ''}.`
);
}
return null;
}
};

View File

@@ -0,0 +1,52 @@
const { oneLine } = require('common-tags');
const Command = require('../base');
module.exports = class UnloadCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'unload',
aliases: ['unload-command'],
group: 'commands',
memberName: 'unload',
description: 'Unloads a command.',
details: oneLine`
The argument must be the name/ID (partial or whole) of a command.
Only the bot owner(s) may use this command.
`,
examples: ['unload some-command'],
ownerOnly: true,
guarded: true,
args: [
{
key: 'command',
prompt: 'Which command would you like to unload?',
type: 'command'
}
]
});
}
async run(msg, args) {
args.command.unload();
if(this.client.shard) {
try {
await this.client.shard.broadcastEval(`
const ids = [${this.client.shard.ids.join(',')}];
if(!this.shard.ids.some(id => ids.includes(id))) {
this.registry.commands.get('${args.command.name}').unload();
}
`);
} catch(err) {
this.client.emit('warn', `Error when broadcasting command unload to other shards`);
this.client.emit('error', err);
await msg.reply(`Unloaded \`${args.command.name}\` command, but failed to unload on other shards.`);
return null;
}
}
await msg.reply(`Unloaded \`${args.command.name}\` command${this.client.shard ? ' on all shards' : ''}.`);
return null;
}
};

89
node_modules/discord.js-commando/src/commands/group.js generated vendored Normal file
View File

@@ -0,0 +1,89 @@
const discord = require('discord.js');
/** A group for commands. Whodathunkit? */
class CommandGroup {
/**
* @param {CommandoClient} client - The client the group is for
* @param {string} id - The ID for the group
* @param {string} [name=id] - The name of the group
* @param {boolean} [guarded=false] - Whether the group should be protected from disabling
*/
constructor(client, id, name, guarded = false) {
if(!client) throw new Error('A client must be specified.');
if(typeof id !== 'string') throw new TypeError('Group ID must be a string.');
if(id !== id.toLowerCase()) throw new Error('Group ID must be lowercase.');
/**
* Client that this group is for
* @name CommandGroup#client
* @type {CommandoClient}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });
/**
* ID of this group
* @type {string}
*/
this.id = id;
/**
* Name of this group
* @type {string}
*/
this.name = name || id;
/**
* The commands in this group (added upon their registration)
* @type {Collection<string, Command>}
*/
this.commands = new discord.Collection();
/**
* Whether or not this group is protected from being disabled
* @type {boolean}
*/
this.guarded = guarded;
this._globalEnabled = true;
}
/**
* Enables or disables the group in a guild
* @param {?GuildResolvable} guild - Guild to enable/disable the group in
* @param {boolean} enabled - Whether the group should be enabled or disabled
*/
setEnabledIn(guild, enabled) {
if(typeof guild === 'undefined') throw new TypeError('Guild must not be undefined.');
if(typeof enabled === 'undefined') throw new TypeError('Enabled must not be undefined.');
if(this.guarded) throw new Error('The group is guarded.');
if(!guild) {
this._globalEnabled = enabled;
this.client.emit('groupStatusChange', null, this, enabled);
return;
}
guild = this.client.guilds.resolve(guild);
guild.setGroupEnabled(this, enabled);
}
/**
* Checks if the group is enabled in a guild
* @param {?GuildResolvable} guild - Guild to check in
* @return {boolean} Whether or not the group is enabled
*/
isEnabledIn(guild) {
if(this.guarded) return true;
if(!guild) return this._globalEnabled;
guild = this.client.guilds.resolve(guild);
return guild.isGroupEnabled(this);
}
/**
* Reloads all of the group's commands
*/
reload() {
for(const command of this.commands.values()) command.reload();
}
}
module.exports = CommandGroup;

View File

@@ -0,0 +1,116 @@
const util = require('util');
const discord = require('discord.js');
const tags = require('common-tags');
const { escapeRegex } = require('../../util');
const Command = require('../base');
const nl = '!!NL!!';
const nlPattern = new RegExp(nl, 'g');
module.exports = class EvalCommand extends Command {
constructor(client) {
super(client, {
name: 'eval',
group: 'util',
memberName: 'eval',
description: 'Executes JavaScript code.',
details: 'Only the bot owner(s) may use this command.',
ownerOnly: true,
args: [
{
key: 'script',
prompt: 'What code would you like to evaluate?',
type: 'string'
}
]
});
this.lastResult = null;
Object.defineProperty(this, '_sensitivePattern', { value: null, configurable: true });
}
run(msg, args) {
// Make a bunch of helpers
/* eslint-disable no-unused-vars */
const message = msg;
const client = msg.client;
const lastResult = this.lastResult;
const doReply = val => {
if(val instanceof Error) {
msg.reply(`Callback error: \`${val}\``);
} else {
const result = this.makeResultMessages(val, process.hrtime(this.hrStart));
if(Array.isArray(result)) {
for(const item of result) msg.reply(item);
} else {
msg.reply(result);
}
}
};
/* eslint-enable no-unused-vars */
// Remove any surrounding code blocks before evaluation
if(args.script.startsWith('```') && args.script.endsWith('```')) {
args.script = args.script.replace(/(^.*?\s)|(\n.*$)/g, '');
}
// Run the code and measure its execution time
let hrDiff;
try {
const hrStart = process.hrtime();
this.lastResult = eval(args.script);
hrDiff = process.hrtime(hrStart);
} catch(err) {
return msg.reply(`Error while evaluating: \`${err}\``);
}
// Prepare for callback time and respond
this.hrStart = process.hrtime();
const result = this.makeResultMessages(this.lastResult, hrDiff, args.script);
if(Array.isArray(result)) {
return result.map(item => msg.reply(item));
} else {
return msg.reply(result);
}
}
makeResultMessages(result, hrDiff, input = null) {
const inspected = util.inspect(result, { depth: 0 })
.replace(nlPattern, '\n')
.replace(this.sensitivePattern, '--snip--');
const split = inspected.split('\n');
const last = inspected.length - 1;
const prependPart = inspected[0] !== '{' && inspected[0] !== '[' && inspected[0] !== "'" ? split[0] : inspected[0];
const appendPart = inspected[last] !== '}' && inspected[last] !== ']' && inspected[last] !== "'" ?
split[split.length - 1] :
inspected[last];
const prepend = `\`\`\`javascript\n${prependPart}\n`;
const append = `\n${appendPart}\n\`\`\``;
if(input) {
return discord.splitMessage(tags.stripIndents`
*Executed in ${hrDiff[0] > 0 ? `${hrDiff[0]}s ` : ''}${hrDiff[1] / 1000000}ms.*
\`\`\`javascript
${inspected}
\`\`\`
`, { maxLength: 1900, prepend, append });
} else {
return discord.splitMessage(tags.stripIndents`
*Callback executed after ${hrDiff[0] > 0 ? `${hrDiff[0]}s ` : ''}${hrDiff[1] / 1000000}ms.*
\`\`\`javascript
${inspected}
\`\`\`
`, { maxLength: 1900, prepend, append });
}
}
get sensitivePattern() {
if(!this._sensitivePattern) {
const client = this.client;
let pattern = '';
if(client.token) pattern += escapeRegex(client.token);
Object.defineProperty(this, '_sensitivePattern', { value: new RegExp(pattern, 'gi'), configurable: false });
}
return this._sensitivePattern;
}
};

View File

@@ -0,0 +1,105 @@
const { stripIndents, oneLine } = require('common-tags');
const Command = require('../base');
const { disambiguation } = require('../../util');
module.exports = class HelpCommand extends Command {
constructor(client) {
super(client, {
name: 'help',
group: 'util',
memberName: 'help',
aliases: ['commands'],
description: 'Displays a list of available commands, or detailed information for a specified command.',
details: oneLine`
The command may be part of a command name or a whole command name.
If it isn't specified, all available commands will be listed.
`,
examples: ['help', 'help prefix'],
guarded: true,
args: [
{
key: 'command',
prompt: 'Which command would you like to view the help for?',
type: 'string',
default: ''
}
]
});
}
async run(msg, args) { // eslint-disable-line complexity
const groups = this.client.registry.groups;
const commands = this.client.registry.findCommands(args.command, false, msg);
const showAll = args.command && args.command.toLowerCase() === 'all';
if(args.command && !showAll) {
if(commands.length === 1) {
let help = stripIndents`
${oneLine`
__Command **${commands[0].name}**:__ ${commands[0].description}
${commands[0].guildOnly ? ' (Usable only in servers)' : ''}
${commands[0].nsfw ? ' (NSFW)' : ''}
`}
**Format:** ${msg.anyUsage(`${commands[0].name}${commands[0].format ? ` ${commands[0].format}` : ''}`)}
`;
if(commands[0].aliases.length > 0) help += `\n**Aliases:** ${commands[0].aliases.join(', ')}`;
help += `\n${oneLine`
**Group:** ${commands[0].group.name}
(\`${commands[0].groupID}:${commands[0].memberName}\`)
`}`;
if(commands[0].details) help += `\n**Details:** ${commands[0].details}`;
if(commands[0].examples) help += `\n**Examples:**\n${commands[0].examples.join('\n')}`;
const messages = [];
try {
messages.push(await msg.direct(help));
if(msg.channel.type !== 'dm') messages.push(await msg.reply('Sent you a DM with information.'));
} catch(err) {
messages.push(await msg.reply('Unable to send you the help DM. You probably have DMs disabled.'));
}
return messages;
} else if(commands.length > 15) {
return msg.reply('Multiple commands found. Please be more specific.');
} else if(commands.length > 1) {
return msg.reply(disambiguation(commands, 'commands'));
} else {
return msg.reply(
`Unable to identify command. Use ${msg.usage(
null, msg.channel.type === 'dm' ? null : undefined, msg.channel.type === 'dm' ? null : undefined
)} to view the list of all commands.`
);
}
} else {
const messages = [];
try {
messages.push(await msg.direct(stripIndents`
${oneLine`
To run a command in ${msg.guild ? msg.guild.name : 'any server'},
use ${Command.usage('command', msg.guild ? msg.guild.commandPrefix : null, this.client.user)}.
For example, ${Command.usage('prefix', msg.guild ? msg.guild.commandPrefix : null, this.client.user)}.
`}
To run a command in this DM, simply use ${Command.usage('command', null, null)} with no prefix.
Use ${this.usage('<command>', null, null)} to view detailed information about a specific command.
Use ${this.usage('all', null, null)} to view a list of *all* commands, not just available ones.
__**${showAll ? 'All commands' : `Available commands in ${msg.guild || 'this DM'}`}**__
${groups.filter(grp => grp.commands.some(cmd => !cmd.hidden && (showAll || cmd.isUsable(msg))))
.map(grp => stripIndents`
__${grp.name}__
${grp.commands.filter(cmd => !cmd.hidden && (showAll || cmd.isUsable(msg)))
.map(cmd => `**${cmd.name}:** ${cmd.description}${cmd.nsfw ? ' (NSFW)' : ''}`).join('\n')
}
`).join('\n\n')
}
`, { split: true }));
if(msg.channel.type !== 'dm') messages.push(await msg.reply('Sent you a DM with information.'));
} catch(err) {
messages.push(await msg.reply('Unable to send you the help DM. You probably have DMs disabled.'));
}
return messages;
}
}
};

View File

@@ -0,0 +1,28 @@
const { oneLine } = require('common-tags');
const Command = require('../base');
module.exports = class PingCommand extends Command {
constructor(client) {
super(client, {
name: 'ping',
group: 'util',
memberName: 'ping',
description: 'Checks the bot\'s ping to the Discord server.',
throttling: {
usages: 5,
duration: 10
}
});
}
async run(msg) {
const pingMsg = await msg.reply('Pinging...');
return pingMsg.edit(oneLine`
${msg.channel.type !== 'dm' ? `${msg.author},` : ''}
Pong! The message round-trip took ${
(pingMsg.editedTimestamp || pingMsg.createdTimestamp) - (msg.editedTimestamp || msg.createdTimestamp)
}ms.
${this.client.ws.ping ? `The heartbeat ping is ${Math.round(this.client.ws.ping)}ms.` : ''}
`);
}
};

View File

@@ -0,0 +1,67 @@
const { stripIndents, oneLine } = require('common-tags');
const Command = require('../base');
module.exports = class PrefixCommand extends Command {
constructor(client) {
super(client, {
name: 'prefix',
group: 'util',
memberName: 'prefix',
description: 'Shows or sets the command prefix.',
format: '[prefix/"default"/"none"]',
details: oneLine`
If no prefix is provided, the current prefix will be shown.
If the prefix is "default", the prefix will be reset to the bot's default prefix.
If the prefix is "none", the prefix will be removed entirely, only allowing mentions to run commands.
Only administrators may change the prefix.
`,
examples: ['prefix', 'prefix -', 'prefix omg!', 'prefix default', 'prefix none'],
args: [
{
key: 'prefix',
prompt: 'What would you like to set the bot\'s prefix to?',
type: 'string',
max: 15,
default: ''
}
]
});
}
async run(msg, args) {
// Just output the prefix
if(!args.prefix) {
const prefix = msg.guild ? msg.guild.commandPrefix : this.client.commandPrefix;
return msg.reply(stripIndents`
${prefix ? `The command prefix is \`${prefix}\`.` : 'There is no command prefix.'}
To run commands, use ${msg.anyUsage('command')}.
`);
}
// Check the user's permission before changing anything
if(msg.guild) {
if(!msg.member.permissions.has('ADMINISTRATOR') && !this.client.isOwner(msg.author)) {
return msg.reply('Only administrators may change the command prefix.');
}
} else if(!this.client.isOwner(msg.author)) {
return msg.reply('Only the bot owner(s) may change the global command prefix.');
}
// Save the prefix
const lowercase = args.prefix.toLowerCase();
const prefix = lowercase === 'none' ? '' : args.prefix;
let response;
if(lowercase === 'default') {
if(msg.guild) msg.guild.commandPrefix = null; else this.client.commandPrefix = null;
const current = this.client.commandPrefix ? `\`${this.client.commandPrefix}\`` : 'no prefix';
response = `Reset the command prefix to the default (currently ${current}).`;
} else {
if(msg.guild) msg.guild.commandPrefix = prefix; else this.client.commandPrefix = prefix;
response = prefix ? `Set the command prefix to \`${args.prefix}\`.` : 'Removed the command prefix entirely.';
}
await msg.reply(`${response} To run commands, use ${msg.anyUsage('command')}.`);
return null;
}
};

View File

@@ -0,0 +1,25 @@
const Command = require('../base');
module.exports = class UnknownCommandCommand extends Command {
constructor(client) {
super(client, {
name: 'unknown-command',
group: 'util',
memberName: 'unknown-command',
description: 'Displays help information for when an unknown command is used.',
examples: ['unknown-command kickeverybodyever'],
unknown: true,
hidden: true
});
}
run(msg) {
return msg.reply(
`Unknown command. Use ${msg.anyUsage(
'help',
msg.guild ? undefined : null,
msg.guild ? undefined : null
)} to view the command list.`
);
}
};

306
node_modules/discord.js-commando/src/dispatcher.js generated vendored Normal file
View File

@@ -0,0 +1,306 @@
const { escapeRegex } = require('./util');
const isPromise = require('is-promise');
/** Handles parsing messages and running commands from them */
class CommandDispatcher {
/**
* @param {CommandoClient} client - Client the dispatcher is for
* @param {CommandoRegistry} registry - Registry the dispatcher will use
*/
constructor(client, registry) {
/**
* Client this dispatcher handles messages for
* @name CommandDispatcher#client
* @type {CommandoClient}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });
/**
* Registry this dispatcher uses
* @type {CommandoRegistry}
*/
this.registry = registry;
/**
* Functions that can block commands from running
* @type {Set<Function>}
*/
this.inhibitors = new Set();
/**
* Map object of {@link RegExp}s that match command messages, mapped by string prefix
* @type {Object}
* @private
*/
this._commandPatterns = {};
/**
* Old command message results, mapped by original message ID
* @type {Map<string, CommandoMessage>}
* @private
*/
this._results = new Map();
/**
* Tuples in string form of user ID and channel ID that are currently awaiting messages from a user in a channel
* @type {Set<string>}
* @private
*/
this._awaiting = new Set();
}
/**
* @typedef {Object} Inhibition
* @property {string} reason - Identifier for the reason the command is being blocked
* @property {?Promise<Message>} response - Response being sent to the user
*/
/**
* A function that decides whether the usage of a command should be blocked
* @callback Inhibitor
* @param {CommandoMessage} msg - Message triggering the command
* @return {boolean|string|Inhibition} `false` if the command should *not* be blocked.
* If the command *should* be blocked, then one of the following:
* - A single string identifying the reason the command is blocked
* - An Inhibition object
*/
/**
* Adds an inhibitor
* @param {Inhibitor} inhibitor - The inhibitor function to add
* @return {boolean} Whether the addition was successful
* @example
* client.dispatcher.addInhibitor(msg => {
* if(blacklistedUsers.has(msg.author.id)) return 'blacklisted';
* });
* @example
* client.dispatcher.addInhibitor(msg => {
* if(!coolUsers.has(msg.author.id)) return { reason: 'cool', response: msg.reply('You\'re not cool enough!') };
* });
*/
addInhibitor(inhibitor) {
if(typeof inhibitor !== 'function') throw new TypeError('The inhibitor must be a function.');
if(this.inhibitors.has(inhibitor)) return false;
this.inhibitors.add(inhibitor);
return true;
}
/**
* Removes an inhibitor
* @param {Inhibitor} inhibitor - The inhibitor function to remove
* @return {boolean} Whether the removal was successful
*/
removeInhibitor(inhibitor) {
if(typeof inhibitor !== 'function') throw new TypeError('The inhibitor must be a function.');
return this.inhibitors.delete(inhibitor);
}
/**
* Handle a new message or a message update
* @param {Message} message - The message to handle
* @param {Message} [oldMessage] - The old message before the update
* @return {Promise<void>}
* @private
*/
async handleMessage(message, oldMessage) {
/* eslint-disable max-depth */
if(!this.shouldHandleMessage(message, oldMessage)) return;
// Parse the message, and get the old result if it exists
let cmdMsg, oldCmdMsg;
if(oldMessage) {
oldCmdMsg = this._results.get(oldMessage.id);
if(!oldCmdMsg && !this.client.options.nonCommandEditable) return;
cmdMsg = this.parseMessage(message);
if(cmdMsg && oldCmdMsg) {
cmdMsg.responses = oldCmdMsg.responses;
cmdMsg.responsePositions = oldCmdMsg.responsePositions;
}
} else {
cmdMsg = this.parseMessage(message);
}
// Run the command, or reply with an error
let responses;
if(cmdMsg) {
const inhibited = this.inhibit(cmdMsg);
if(!inhibited) {
if(cmdMsg.command) {
if(!cmdMsg.command.isEnabledIn(message.guild)) {
if(!cmdMsg.command.unknown) {
responses = await cmdMsg.reply(`The \`${cmdMsg.command.name}\` command is disabled.`);
} else {
/**
* Emitted when an unknown command is triggered
* @event CommandoClient#unknownCommand
* @param {CommandoMessage} message - Command message that triggered the command
*/
this.client.emit('unknownCommand', cmdMsg);
responses = undefined;
}
} else if(!oldMessage || typeof oldCmdMsg !== 'undefined') {
responses = await cmdMsg.run();
if(typeof responses === 'undefined') responses = null;
if(Array.isArray(responses)) responses = await Promise.all(responses);
}
} else {
this.client.emit('unknownCommand', cmdMsg);
responses = undefined;
}
} else {
responses = await inhibited.response;
}
cmdMsg.finalize(responses);
} else if(oldCmdMsg) {
oldCmdMsg.finalize(null);
if(!this.client.options.nonCommandEditable) this._results.delete(message.id);
}
this.cacheCommandoMessage(message, oldMessage, cmdMsg, responses);
/* eslint-enable max-depth */
}
/**
* Check whether a message should be handled
* @param {Message} message - The message to handle
* @param {Message} [oldMessage] - The old message before the update
* @return {boolean}
* @private
*/
shouldHandleMessage(message, oldMessage) {
// Ignore partial messages
if(message.partial) return false;
if(message.author.bot) return false;
else if(message.author.id === this.client.user.id) return false;
// Ignore messages from users that the bot is already waiting for input from
if(this._awaiting.has(message.author.id + message.channel.id)) return false;
// Make sure the edit actually changed the message content
if(oldMessage && message.content === oldMessage.content) return false;
return true;
}
/**
* Inhibits a command message
* @param {CommandoMessage} cmdMsg - Command message to inhibit
* @return {?Inhibition}
* @private
*/
inhibit(cmdMsg) {
for(const inhibitor of this.inhibitors) {
let inhibit = inhibitor(cmdMsg);
if(inhibit) {
if(typeof inhibit !== 'object') inhibit = { reason: inhibit, response: undefined };
const valid = typeof inhibit.reason === 'string' && (
typeof inhibit.response === 'undefined' ||
inhibit.response === null ||
isPromise(inhibit.response)
);
if(!valid) {
throw new TypeError(
`Inhibitor "${inhibitor.name}" had an invalid result; must be a string or an Inhibition object.`
);
}
this.client.emit('commandBlock', cmdMsg, inhibit.reason, inhibit);
return inhibit;
}
}
return null;
}
/**
* Caches a command message to be editable
* @param {Message} message - Triggering message
* @param {Message} oldMessage - Triggering message's old version
* @param {CommandoMessage} cmdMsg - Command message to cache
* @param {Message|Message[]} responses - Responses to the message
* @private
*/
cacheCommandoMessage(message, oldMessage, cmdMsg, responses) {
if(this.client.options.commandEditableDuration <= 0) return;
if(!cmdMsg && !this.client.options.nonCommandEditable) return;
if(responses !== null) {
this._results.set(message.id, cmdMsg);
if(!oldMessage) {
setTimeout(() => { this._results.delete(message.id); }, this.client.options.commandEditableDuration * 1000);
}
} else {
this._results.delete(message.id);
}
}
/**
* Parses a message to find details about command usage in it
* @param {Message} message - The message
* @return {?CommandoMessage}
* @private
*/
parseMessage(message) {
// Find the command to run by patterns
for(const command of this.registry.commands.values()) {
if(!command.patterns) continue;
for(const pattern of command.patterns) {
const matches = pattern.exec(message.content);
if(matches) return message.initCommand(command, null, matches);
}
}
// Find the command to run with default command handling
const prefix = message.guild ? message.guild.commandPrefix : this.client.commandPrefix;
if(!this._commandPatterns[prefix]) this.buildCommandPattern(prefix);
let cmdMsg = this.matchDefault(message, this._commandPatterns[prefix], 2);
if(!cmdMsg && !message.guild) cmdMsg = this.matchDefault(message, /^([^\s]+)/i, 1, true);
return cmdMsg;
}
/**
* Matches a message against a guild command pattern
* @param {Message} message - The message
* @param {RegExp} pattern - The pattern to match against
* @param {number} commandNameIndex - The index of the command name in the pattern matches
* @param {boolean} prefixless - Whether the match is happening for a prefixless usage
* @return {?CommandoMessage}
* @private
*/
matchDefault(message, pattern, commandNameIndex = 1, prefixless = false) {
const matches = pattern.exec(message.content);
if(!matches) return null;
const commands = this.registry.findCommands(matches[commandNameIndex], true);
if(commands.length !== 1 || !commands[0].defaultHandling) {
return message.initCommand(this.registry.unknownCommand, prefixless ? message.content : matches[1]);
}
const argString = message.content.substring(matches[1].length + (matches[2] ? matches[2].length : 0));
return message.initCommand(commands[0], argString);
}
/**
* Creates a regular expression to match the command prefix and name in a message
* @param {?string} prefix - Prefix to build the pattern for
* @return {RegExp}
* @private
*/
buildCommandPattern(prefix) {
let pattern;
if(prefix) {
const escapedPrefix = escapeRegex(prefix);
pattern = new RegExp(
`^(<@!?${this.client.user.id}>\\s+(?:${escapedPrefix}\\s*)?|${escapedPrefix}\\s*)([^\\s]+)`, 'i'
);
} else {
pattern = new RegExp(`(^<@!?${this.client.user.id}>\\s+)([^\\s]+)`, 'i');
}
this._commandPatterns[prefix] = pattern;
this.client.emit('debug', `Built command pattern for prefix "${prefix}": ${pattern}`);
return pattern;
}
}
module.exports = CommandDispatcher;

View File

@@ -0,0 +1,27 @@
const FriendlyError = require('./friendly');
/**
* Has a descriptive message for a command not having proper format
* @extends {FriendlyError}
*/
class CommandFormatError extends FriendlyError {
/**
* @param {CommandoMessage} msg - The command message the error is for
*/
constructor(msg) {
super(
`Invalid command usage. The \`${msg.command.name}\` command's accepted format is: ${msg.usage(
msg.command.format,
msg.guild ? undefined : null,
msg.guild ? undefined : null
)}. Use ${msg.anyUsage(
`help ${msg.command.name}`,
msg.guild ? undefined : null,
msg.guild ? undefined : null
)} for more information.`
);
this.name = 'CommandFormatError';
}
}
module.exports = CommandFormatError;

View File

@@ -0,0 +1,13 @@
/**
* Has a message that can be considered user-friendly
* @extends {Error}
*/
class FriendlyError extends Error {
/** @param {string} message - The error message */
constructor(message) {
super(message);
this.name = 'FriendlyError';
}
}
module.exports = FriendlyError;

View File

@@ -0,0 +1,148 @@
const { Structures } = require('discord.js');
const Command = require('../commands/base');
const GuildSettingsHelper = require('../providers/helper');
module.exports = Structures.extend('Guild', Guild => {
/**
* A fancier Guild for fancier people.
* @extends Guild
*/
class CommandoGuild extends Guild {
constructor(...args) {
super(...args);
/**
* Shortcut to use setting provider methods for this guild
* @type {GuildSettingsHelper}
*/
this.settings = new GuildSettingsHelper(this.client, this);
/**
* Internal command prefix for the guild, controlled by the {@link CommandoGuild#commandPrefix}
* getter/setter
* @name CommandoGuild#_commandPrefix
* @type {?string}
* @private
*/
this._commandPrefix = null;
}
/**
* Command prefix in the guild. An empty string indicates that there is no prefix, and only mentions will be used.
* Setting to `null` means that the prefix from {@link CommandoClient#commandPrefix} will be used instead.
* @type {string}
* @emits {@link CommandoClient#commandPrefixChange}
*/
get commandPrefix() {
if(this._commandPrefix === null) return this.client.commandPrefix;
return this._commandPrefix;
}
set commandPrefix(prefix) {
this._commandPrefix = prefix;
/**
* Emitted whenever a guild's command prefix is changed
* @event CommandoClient#commandPrefixChange
* @param {?CommandoGuild} guild - Guild that the prefix was changed in (null for global)
* @param {?string} prefix - New command prefix (null for default)
*/
this.client.emit('commandPrefixChange', this, this._commandPrefix);
}
/**
* Sets whether a command is enabled in the guild
* @param {CommandResolvable} command - Command to set status of
* @param {boolean} enabled - Whether the command should be enabled
*/
setCommandEnabled(command, enabled) {
command = this.client.registry.resolveCommand(command);
if(command.guarded) throw new Error('The command is guarded.');
if(typeof enabled === 'undefined') throw new TypeError('Enabled must not be undefined.');
enabled = Boolean(enabled);
if(!this._commandsEnabled) {
/**
* Map object of internal command statuses, mapped by command name
* @type {Object}
* @private
*/
this._commandsEnabled = {};
}
this._commandsEnabled[command.name] = enabled;
/**
* Emitted whenever a command is enabled/disabled in a guild
* @event CommandoClient#commandStatusChange
* @param {?CommandoGuild} guild - Guild that the command was enabled/disabled in (null for global)
* @param {Command} command - Command that was enabled/disabled
* @param {boolean} enabled - Whether the command is enabled
*/
this.client.emit('commandStatusChange', this, command, enabled);
}
/**
* Checks whether a command is enabled in the guild (does not take the command's group status into account)
* @param {CommandResolvable} command - Command to check status of
* @return {boolean}
*/
isCommandEnabled(command) {
command = this.client.registry.resolveCommand(command);
if(command.guarded) return true;
if(!this._commandsEnabled || typeof this._commandsEnabled[command.name] === 'undefined') {
return command._globalEnabled;
}
return this._commandsEnabled[command.name];
}
/**
* Sets whether a command group is enabled in the guild
* @param {CommandGroupResolvable} group - Group to set status of
* @param {boolean} enabled - Whether the group should be enabled
*/
setGroupEnabled(group, enabled) {
group = this.client.registry.resolveGroup(group);
if(group.guarded) throw new Error('The group is guarded.');
if(typeof enabled === 'undefined') throw new TypeError('Enabled must not be undefined.');
enabled = Boolean(enabled);
if(!this._groupsEnabled) {
/**
* Internal map object of group statuses, mapped by group ID
* @type {Object}
* @private
*/
this._groupsEnabled = {};
}
this._groupsEnabled[group.id] = enabled;
/**
* Emitted whenever a command group is enabled/disabled in a guild
* @event CommandoClient#groupStatusChange
* @param {?CommandoGuild} guild - Guild that the group was enabled/disabled in (null for global)
* @param {CommandGroup} group - Group that was enabled/disabled
* @param {boolean} enabled - Whether the group is enabled
*/
this.client.emit('groupStatusChange', this, group, enabled);
}
/**
* Checks whether a command group is enabled in the guild
* @param {CommandGroupResolvable} group - Group to check status of
* @return {boolean}
*/
isGroupEnabled(group) {
group = this.client.registry.resolveGroup(group);
if(group.guarded) return true;
if(!this._groupsEnabled || typeof this._groupsEnabled[group.id] === 'undefined') return group._globalEnabled;
return this._groupsEnabled[group.id];
}
/**
* Creates a command usage string using the guild's prefix
* @param {string} [command] - A command + arg string
* @param {User} [user=this.client.user] - User to use for the mention command format
* @return {string}
*/
commandUsage(command, user = this.client.user) {
return Command.usage(command, this.commandPrefix, user);
}
}
return CommandoGuild;
});

View File

@@ -0,0 +1,534 @@
const { Structures, escapeMarkdown, splitMessage, resolveString } = require('discord.js');
const { oneLine } = require('common-tags');
const Command = require('../commands/base');
const FriendlyError = require('../errors/friendly');
const CommandFormatError = require('../errors/command-format');
module.exports = Structures.extend('Message', Message => {
/**
* An extension of the base Discord.js Message class to add command-related functionality.
* @extends Message
*/
class CommandoMessage extends Message {
constructor(...args) {
super(...args);
/**
* Whether the message contains a command (even an unknown one)
* @type {boolean}
*/
this.isCommand = false;
/**
* Command that the message triggers, if any
* @type {?Command}
*/
this.command = null;
/**
* Argument string for the command
* @type {?string}
*/
this.argString = null;
/**
* Pattern matches (if from a pattern trigger)
* @type {?string[]}
*/
this.patternMatches = null;
/**
* Response messages sent, mapped by channel ID (set by the dispatcher after running the command)
* @type {?Object}
*/
this.responses = null;
/**
* Index of the current response that will be edited, mapped by channel ID
* @type {?Object}
*/
this.responsePositions = null;
}
/**
* Initialises the message for a command
* @param {Command} [command] - Command the message triggers
* @param {string} [argString] - Argument string for the command
* @param {?Array<string>} [patternMatches] - Command pattern matches (if from a pattern trigger)
* @return {Message} This message
* @private
*/
initCommand(command, argString, patternMatches) {
this.isCommand = true;
this.command = command;
this.argString = argString;
this.patternMatches = patternMatches;
return this;
}
/**
* Creates a usage string for the message's command
* @param {string} [argString] - A string of arguments for the command
* @param {string} [prefix=this.guild.commandPrefix || this.client.commandPrefix] - Prefix to use for the
* prefixed command format
* @param {User} [user=this.client.user] - User to use for the mention command format
* @return {string}
*/
usage(argString, prefix, user = this.client.user) {
if(typeof prefix === 'undefined') {
if(this.guild) prefix = this.guild.commandPrefix;
else prefix = this.client.commandPrefix;
}
return this.command.usage(argString, prefix, user);
}
/**
* Creates a usage string for any command
* @param {string} [command] - A command + arg string
* @param {string} [prefix=this.guild.commandPrefix || this.client.commandPrefix] - Prefix to use for the
* prefixed command format
* @param {User} [user=this.client.user] - User to use for the mention command format
* @return {string}
*/
anyUsage(command, prefix, user = this.client.user) {
if(typeof prefix === 'undefined') {
if(this.guild) prefix = this.guild.commandPrefix;
else prefix = this.client.commandPrefix;
}
return Command.usage(command, prefix, user);
}
/**
* Parses the argString into usable arguments, based on the argsType and argsCount of the command
* @return {string|string[]}
* @see {@link Command#run}
*/
parseArgs() {
switch(this.command.argsType) {
case 'single':
return this.argString.trim().replace(
this.command.argsSingleQuotes ? /^("|')([^]*)\1$/g : /^(")([^]*)"$/g, '$2'
);
case 'multiple':
return this.constructor.parseArgs(this.argString, this.command.argsCount, this.command.argsSingleQuotes);
default:
throw new RangeError(`Unknown argsType "${this.argsType}".`);
}
}
/**
* Runs the command
* @return {Promise<?Message|?Array<Message>>}
*/
async run() { // eslint-disable-line complexity
// Obtain the member if we don't have it
if(this.channel.type === 'text' && !this.guild.members.cache.has(this.author.id) && !this.webhookID) {
this.member = await this.guild.members.fetch(this.author);
}
// Obtain the member for the ClientUser if it doesn't already exist
if(this.channel.type === 'text' && !this.guild.members.cache.has(this.client.user.id)) {
await this.guild.members.fetch(this.client.user.id);
}
// Make sure the command is usable in this context
if(this.command.guildOnly && !this.guild) {
/**
* Emitted when a command is prevented from running
* @event CommandoClient#commandBlock
* @param {CommandoMessage} message - Command message that the command is running from
* @param {string} reason - Reason that the command was blocked
* (built-in reasons are `guildOnly`, `nsfw`, `permission`, `throttling`, and `clientPermissions`)
* @param {Object} [data] - Additional data associated with the block. Built-in reason data properties:
* - guildOnly: none
* - nsfw: none
* - permission: `response` ({@link string}) to send
* - throttling: `throttle` ({@link Object}), `remaining` ({@link number}) time in seconds
* - clientPermissions: `missing` ({@link Array}<{@link string}>) permission names
*/
this.client.emit('commandBlock', this, 'guildOnly');
return this.command.onBlock(this, 'guildOnly');
}
// Ensure the channel is a NSFW one if required
if(this.command.nsfw && !this.channel.nsfw) {
this.client.emit('commandBlock', this, 'nsfw');
return this.command.onBlock(this, 'nsfw');
}
// Ensure the user has permission to use the command
const hasPermission = this.command.hasPermission(this);
if(!hasPermission || typeof hasPermission === 'string') {
const data = { response: typeof hasPermission === 'string' ? hasPermission : undefined };
this.client.emit('commandBlock', this, 'permission', data);
return this.command.onBlock(this, 'permission', data);
}
// Ensure the client user has the required permissions
if(this.channel.type === 'text' && this.command.clientPermissions) {
const missing = this.channel.permissionsFor(this.client.user).missing(this.command.clientPermissions);
if(missing.length > 0) {
const data = { missing };
this.client.emit('commandBlock', this, 'clientPermissions', data);
return this.command.onBlock(this, 'clientPermissions', data);
}
}
// Throttle the command
const throttle = this.command.throttle(this.author.id);
if(throttle && throttle.usages + 1 > this.command.throttling.usages) {
const remaining = (throttle.start + (this.command.throttling.duration * 1000) - Date.now()) / 1000;
const data = { throttle, remaining };
this.client.emit('commandBlock', this, 'throttling', data);
return this.command.onBlock(this, 'throttling', data);
}
// Figure out the command arguments
let args = this.patternMatches;
let collResult = null;
if(!args && this.command.argsCollector) {
const collArgs = this.command.argsCollector.args;
const count = collArgs[collArgs.length - 1].infinite ? Infinity : collArgs.length;
const provided = this.constructor.parseArgs(this.argString.trim(), count, this.command.argsSingleQuotes);
collResult = await this.command.argsCollector.obtain(this, provided);
if(collResult.cancelled) {
if(collResult.prompts.length === 0 || collResult.cancelled === 'promptLimit') {
const err = new CommandFormatError(this);
return this.reply(err.message);
}
/**
* Emitted when a command is cancelled (either by typing 'cancel' or not responding in time)
* @event CommandoClient#commandCancel
* @param {Command} command - Command that was cancelled
* @param {string} reason - Reason for the command being cancelled
* @param {CommandoMessage} message - Command message that the command ran from (see {@link Command#run})
* @param {?ArgumentCollectorResult} result - Result from obtaining the arguments from the collector
* (if applicable - see {@link Command#run})
*/
this.client.emit('commandCancel', this.command, collResult.cancelled, this, collResult);
return this.reply('Cancelled command.');
}
args = collResult.values;
}
if(!args) args = this.parseArgs();
const fromPattern = Boolean(this.patternMatches);
// Run the command
if(throttle) throttle.usages++;
const typingCount = this.channel.typingCount;
try {
this.client.emit('debug', `Running command ${this.command.groupID}:${this.command.memberName}.`);
const promise = this.command.run(this, args, fromPattern, collResult);
/**
* Emitted when running a command
* @event CommandoClient#commandRun
* @param {Command} command - Command that is being run
* @param {Promise} promise - Promise for the command result
* @param {CommandoMessage} message - Command message that the command is running from (see {@link Command#run})
* @param {Object|string|string[]} args - Arguments for the command (see {@link Command#run})
* @param {boolean} fromPattern - Whether the args are pattern matches (see {@link Command#run})
* @param {?ArgumentCollectorResult} result - Result from obtaining the arguments from the collector
* (if applicable - see {@link Command#run})
*/
this.client.emit('commandRun', this.command, promise, this, args, fromPattern, collResult);
const retVal = await promise;
if(!(retVal instanceof Message || retVal instanceof Array || retVal === null || retVal === undefined)) {
throw new TypeError(oneLine`
Command ${this.command.name}'s run() resolved with an unknown type
(${retVal !== null ? retVal && retVal.constructor ? retVal.constructor.name : typeof retVal : null}).
Command run methods must return a Promise that resolve with a Message, Array of Messages, or null/undefined.
`);
}
return retVal;
} catch(err) {
/**
* Emitted when a command produces an error while running
* @event CommandoClient#commandError
* @param {Command} command - Command that produced an error
* @param {Error} err - Error that was thrown
* @param {CommandoMessage} message - Command message that the command is running from (see {@link Command#run})
* @param {Object|string|string[]} args - Arguments for the command (see {@link Command#run})
* @param {boolean} fromPattern - Whether the args are pattern matches (see {@link Command#run})
* @param {?ArgumentCollectorResult} result - Result from obtaining the arguments from the collector
* (if applicable - see {@link Command#run})
*/
this.client.emit('commandError', this.command, err, this, args, fromPattern, collResult);
if(this.channel.typingCount > typingCount) this.channel.stopTyping();
if(err instanceof FriendlyError) {
return this.reply(err.message);
} else {
return this.command.onError(err, this, args, fromPattern, collResult);
}
}
}
/**
* Responds to the command message
* @param {Object} [options] - Options for the response
* @return {Message|Message[]}
* @private
*/
respond({ type = 'reply', content, options, lang, fromEdit = false }) {
const shouldEdit = this.responses && !fromEdit;
if(shouldEdit) {
if(options && options.split && typeof options.split !== 'object') options.split = {};
}
if(type === 'reply' && this.channel.type === 'dm') type = 'plain';
if(type !== 'direct') {
if(this.guild && !this.channel.permissionsFor(this.client.user).has('SEND_MESSAGES')) {
type = 'direct';
}
}
content = resolveString(content);
switch(type) {
case 'plain':
if(!shouldEdit) return this.channel.send(content, options);
return this.editCurrentResponse(channelIDOrDM(this.channel), { type, content, options });
case 'reply':
if(!shouldEdit) return super.reply(content, options);
if(options && options.split && !options.split.prepend) options.split.prepend = `${this.author}, `;
return this.editCurrentResponse(channelIDOrDM(this.channel), { type, content, options });
case 'direct':
if(!shouldEdit) return this.author.send(content, options);
return this.editCurrentResponse('dm', { type, content, options });
case 'code':
if(!shouldEdit) return this.channel.send(content, options);
if(options && options.split) {
if(!options.split.prepend) options.split.prepend = `\`\`\`${lang || ''}\n`;
if(!options.split.append) options.split.append = '\n```';
}
content = `\`\`\`${lang || ''}\n${escapeMarkdown(content, true)}\n\`\`\``;
return this.editCurrentResponse(channelIDOrDM(this.channel), { type, content, options });
default:
throw new RangeError(`Unknown response type "${type}".`);
}
}
/**
* Edits a response to the command message
* @param {Message|Message[]} response - The response message(s) to edit
* @param {Object} [options] - Options for the response
* @return {Promise<Message|Message[]>}
* @private
*/
editResponse(response, { type, content, options }) {
if(!response) return this.respond({ type, content, options, fromEdit: true });
if(options && options.split) content = splitMessage(content, options.split);
let prepend = '';
if(type === 'reply') prepend = `${this.author}, `;
if(content instanceof Array) {
const promises = [];
if(response instanceof Array) {
for(let i = 0; i < content.length; i++) {
if(response.length > i) promises.push(response[i].edit(`${prepend}${content[i]}`, options));
else promises.push(response[0].channel.send(`${prepend}${content[i]}`));
}
} else {
promises.push(response.edit(`${prepend}${content[0]}`, options));
for(let i = 1; i < content.length; i++) {
promises.push(response.channel.send(`${prepend}${content[i]}`));
}
}
return Promise.all(promises);
} else {
if(response instanceof Array) { // eslint-disable-line no-lonely-if
for(let i = response.length - 1; i > 0; i--) response[i].delete();
return response[0].edit(`${prepend}${content}`, options);
} else {
return response.edit(`${prepend}${content}`, options);
}
}
}
/**
* Edits the current response
* @param {string} id - The ID of the channel the response is in ("DM" for direct messages)
* @param {Object} [options] - Options for the response
* @return {Promise<Message|Message[]>}
* @private
*/
editCurrentResponse(id, options) {
if(typeof this.responses[id] === 'undefined') this.responses[id] = [];
if(typeof this.responsePositions[id] === 'undefined') this.responsePositions[id] = -1;
this.responsePositions[id]++;
return this.editResponse(this.responses[id][this.responsePositions[id]], options);
}
/**
* Responds with a plain message
* @param {StringResolvable} content - Content for the message
* @param {MessageOptions} [options] - Options for the message
* @return {Promise<Message|Message[]>}
*/
say(content, options) {
if(!options && typeof content === 'object' && !(content instanceof Array)) {
options = content;
content = '';
}
return this.respond({ type: 'plain', content, options });
}
/**
* Responds with a reply message
* @param {StringResolvable} content - Content for the message
* @param {MessageOptions} [options] - Options for the message
* @return {Promise<Message|Message[]>}
*/
reply(content, options) {
if(!options && typeof content === 'object' && !(content instanceof Array)) {
options = content;
content = '';
}
return this.respond({ type: 'reply', content, options });
}
/**
* Responds with a direct message
* @param {StringResolvable} content - Content for the message
* @param {MessageOptions} [options] - Options for the message
* @return {Promise<Message|Message[]>}
*/
direct(content, options) {
if(!options && typeof content === 'object' && !(content instanceof Array)) {
options = content;
content = '';
}
return this.respond({ type: 'direct', content, options });
}
/**
* Responds with a code message
* @param {string} lang - Language for the code block
* @param {StringResolvable} content - Content for the message
* @param {MessageOptions} [options] - Options for the message
* @return {Promise<Message|Message[]>}
*/
code(lang, content, options) {
if(!options && typeof content === 'object' && !(content instanceof Array)) {
options = content;
content = '';
}
if(typeof options !== 'object') options = {};
options.code = lang;
return this.respond({ type: 'code', content, options });
}
/**
* Responds with an embed
* @param {RichEmbed|Object} embed - Embed to send
* @param {StringResolvable} [content] - Content for the message
* @param {MessageOptions} [options] - Options for the message
* @return {Promise<Message|Message[]>}
*/
embed(embed, content = '', options) {
if(typeof options !== 'object') options = {};
options.embed = embed;
return this.respond({ type: 'plain', content, options });
}
/**
* Responds with a mention + embed
* @param {RichEmbed|Object} embed - Embed to send
* @param {StringResolvable} [content] - Content for the message
* @param {MessageOptions} [options] - Options for the message
* @return {Promise<Message|Message[]>}
*/
replyEmbed(embed, content = '', options) {
if(typeof options !== 'object') options = {};
options.embed = embed;
return this.respond({ type: 'reply', content, options });
}
/**
* Finalizes the command message by setting the responses and deleting any remaining prior ones
* @param {?Array<Message|Message[]>} responses - Responses to the message
* @private
*/
finalize(responses) {
if(this.responses) this.deleteRemainingResponses();
this.responses = {};
this.responsePositions = {};
if(responses instanceof Array) {
for(const response of responses) {
const channel = (response instanceof Array ? response[0] : response).channel;
const id = channelIDOrDM(channel);
if(!this.responses[id]) {
this.responses[id] = [];
this.responsePositions[id] = -1;
}
this.responses[id].push(response);
}
} else if(responses) {
const id = channelIDOrDM(responses.channel);
this.responses[id] = [responses];
this.responsePositions[id] = -1;
}
}
/**
* Deletes any prior responses that haven't been updated
* @private
*/
deleteRemainingResponses() {
for(const id of Object.keys(this.responses)) {
const responses = this.responses[id];
for(let i = this.responsePositions[id] + 1; i < responses.length; i++) {
const response = responses[i];
if(response instanceof Array) {
for(const resp of response) resp.delete();
} else {
response.delete();
}
}
}
}
/**
* Parses an argument string into an array of arguments
* @param {string} argString - The argument string to parse
* @param {number} [argCount] - The number of arguments to extract from the string
* @param {boolean} [allowSingleQuote=true] - Whether or not single quotes should be allowed to wrap arguments,
* in addition to double quotes
* @return {string[]} The array of arguments
*/
static parseArgs(argString, argCount, allowSingleQuote = true) {
const argStringModified = removeSmartQuotes(argString, allowSingleQuote);
const re = allowSingleQuote ? /\s*(?:("|')([^]*?)\1|(\S+))\s*/g : /\s*(?:(")([^]*?)"|(\S+))\s*/g;
const result = [];
let match = [];
// Large enough to get all items
argCount = argCount || argStringModified.length;
// Get match and push the capture group that is not null to the result
while(--argCount && (match = re.exec(argStringModified))) result.push(match[2] || match[3]);
// If text remains, push it to the array as-is (except for wrapping quotes, which are removed)
if(match && re.lastIndex < argStringModified.length) {
const re2 = allowSingleQuote ? /^("|')([^]*)\1$/g : /^(")([^]*)"$/g;
result.push(argStringModified.substr(re.lastIndex).replace(re2, '$2'));
}
return result;
}
}
return CommandoMessage;
});
function removeSmartQuotes(argString, allowSingleQuote = true) {
let replacementArgString = argString;
const singleSmartQuote = /[]/g;
const doubleSmartQuote = /[“”]/g;
if(allowSingleQuote) replacementArgString = argString.replace(singleSmartQuote, '\'');
return replacementArgString
.replace(doubleSmartQuote, '"');
}
function channelIDOrDM(channel) {
if(channel.type !== 'dm') return channel.id;
return 'dm';
}

127
node_modules/discord.js-commando/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,127 @@
module.exports = {
Client: require('./client'),
CommandoClient: require('./client'),
CommandoRegistry: require('./registry'),
CommandDispatcher: require('./dispatcher'),
CommandoGuild: require('./extensions/guild'),
CommandoMessage: require('./extensions/message'),
Command: require('./commands/base'),
CommandGroup: require('./commands/group'),
ArgumentCollector: require('./commands/collector'),
Argument: require('./commands/argument'),
ArgumentType: require('./types/base'),
FriendlyError: require('./errors/friendly'),
CommandFormatError: require('./errors/command-format'),
util: require('./util'),
version: require('../package').version,
SettingProvider: require('./providers/base'),
get SQLiteProvider() {
return require('./providers/sqlite');
},
get SyncSQLiteProvider() {
return require('./providers/sqlite-sync');
}
};
/**
* @external Channel
* @see {@link https://discord.js.org/#/docs/main/master/class/Channel}
*/
/**
* @external Client
* @see {@link https://discord.js.org/#/docs/main/master/class/Client}
*/
/**
* @external ClientOptions
* @see {@link https://discord.js.org/#/docs/main/master/typedef/ClientOptions}
*/
/**
* @external Collection
* @see {@link https://discord.js.org/#/docs/main/master/class/Collection}
*/
/**
* @external DMChannel
* @see {@link https://discord.js.org/#/docs/main/master/class/DMChannel}
*/
/**
* @external Guild
* @see {@link https://discord.js.org/#/docs/main/master/class/Guild}
*/
/**
* @external GuildMember
* @see {@link https://discord.js.org/#/docs/main/master/class/GuildMember}
*/
/**
* @external GuildResolvable
* @see {@link https://discord.js.org/#/docs/main/master/typedef/GuildResolvable}
*/
/**
* @external Message
* @see {@link https://discord.js.org/#/docs/main/master/class/Message}
*/
/**
* @external MessageAttachment
* @see {@link https://discord.js.org/#/docs/main/master/class/MessageAttachment}
*/
/**
* @external MessageEmbed
* @see {@link https://discord.js.org/#/docs/main/master/class/MessageEmbed}
*/
/**
* @external MessageReaction
* @see {@link https://discord.js.org/#/docs/main/master/class/MessageReaction}
*/
/**
* @external MessageOptions
* @see {@link https://discord.js.org/#/docs/main/master/typedef/MessageOptions}
*/
/**
* @external PermissionResolvable
* @see {@link https://discord.js.org/#/docs/main/master/typedef/PermissionResolvable}
*/
/**
* @external Role
* @see {@link https://discord.js.org/#/docs/main/master/class/Role}
*/
/**
* @external StringResolvable
* @see {@link https://discord.js.org/#/docs/main/master/typedef/StringResolvable}
*/
/**
* @external TextChannel
* @see {@link https://discord.js.org/#/docs/main/master/class/TextChannel}
*/
/**
* @external User
* @see {@link https://discord.js.org/#/docs/main/master/class/User}
*/
/**
* @external UserResolvable
* @see {@link https://discord.js.org/#/docs/main/master/class/UserResolvable}
*/
/**
* @external Emoji
* @see {@link https://discord.js.org/#/docs/main/master/class/Emoji}
*/
/**
* @external ReactionEmoji
* @see {@link https://discord.js.org/#/docs/main/master/class/ReactionEmoji}
*/
/**
* @external Webhook
* @see {@link https://discord.js.org/#/docs/main/master/class/Webhook}
*/
/**
* @external MessageEmbed
* @see {@link https://discord.js.org/#/docs/main/master/class/MessageEmbed}
*/
/**
* @external ShardingManager
* @see {@link https://discord.js.org/#/docs/main/master/class/ShardingManager}
*/
/**
* @external RequireAllOptions
* @see {@link https://www.npmjs.com/package/require-all}
*/

79
node_modules/discord.js-commando/src/providers/base.js generated vendored Normal file
View File

@@ -0,0 +1,79 @@
/* eslint-disable no-unused-vars */
const { Guild } = require('discord.js');
/**
* Loads and stores settings associated with guilds
* @abstract
*/
class SettingProvider {
constructor() {
if(this.constructor.name === 'SettingProvider') throw new Error('The base SettingProvider cannot be instantiated.');
}
/**
* Initialises the provider by connecting to databases and/or caching all data in memory.
* {@link CommandoClient#setProvider} will automatically call this once the client is ready.
* @param {CommandoClient} client - Client that will be using the provider
* @return {Promise<void>}
* @abstract
*/
init(client) { throw new Error(`${this.constructor.name} doesn't have an init method.`); }
/**
* Destroys the provider, removing any event listeners.
* @return {Promise<void>}
* @abstract
*/
destroy() { throw new Error(`${this.constructor.name} doesn't have a destroy method.`); }
/**
* Obtains a setting for a guild
* @param {Guild|string} guild - Guild the setting is associated with (or 'global')
* @param {string} key - Name of the setting
* @param {*} [defVal] - Value to default to if the setting isn't set on the guild
* @return {*}
* @abstract
*/
get(guild, key, defVal) { throw new Error(`${this.constructor.name} doesn't have a get method.`); }
/**
* Sets a setting for a guild
* @param {Guild|string} guild - Guild to associate the setting with (or 'global')
* @param {string} key - Name of the setting
* @param {*} val - Value of the setting
* @return {Promise<*>} New value of the setting
* @abstract
*/
set(guild, key, val) { throw new Error(`${this.constructor.name} doesn't have a set method.`); }
/**
* Removes a setting from a guild
* @param {Guild|string} guild - Guild the setting is associated with (or 'global')
* @param {string} key - Name of the setting
* @return {Promise<*>} Old value of the setting
* @abstract
*/
remove(guild, key) { throw new Error(`${this.constructor.name} doesn't have a remove method.`); }
/**
* Removes all settings in a guild
* @param {Guild|string} guild - Guild to clear the settings of
* @return {Promise<void>}
* @abstract
*/
clear(guild) { throw new Error(`${this.constructor.name} doesn't have a clear method.`); }
/**
* Obtains the ID of the provided guild, or throws an error if it isn't valid
* @param {Guild|string} guild - Guild to get the ID of
* @return {string} ID of the guild, or 'global'
*/
static getGuildID(guild) {
if(guild instanceof Guild) return guild.id;
if(guild === 'global' || guild === null) return 'global';
if(typeof guild === 'string' && !isNaN(guild)) return guild;
throw new TypeError('Invalid guild specified. Must be a Guild instance, guild ID, "global", or null.');
}
}
module.exports = SettingProvider;

View File

@@ -0,0 +1,70 @@
/** Helper class to use {@link SettingProvider} methods for a specific Guild */
class GuildSettingsHelper {
/**
* @param {CommandoClient} client - Client to use the provider of
* @param {?CommandoGuild} guild - Guild the settings are for
* @private
*/
constructor(client, guild) {
/**
* Client to use the provider of
* @name GuildSettingsHelper#client
* @type {CommandoClient}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });
/**
* Guild the settings are for
* @type {?CommandoGuild}
*/
this.guild = guild;
}
/**
* Gets a setting in the guild
* @param {string} key - Name of the setting
* @param {*} [defVal] - Value to default to if the setting isn't set
* @return {*}
* @see {@link SettingProvider#get}
*/
get(key, defVal) {
if(!this.client.provider) throw new Error('No settings provider is available.');
return this.client.provider.get(this.guild, key, defVal);
}
/**
* Sets a setting for the guild
* @param {string} key - Name of the setting
* @param {*} val - Value of the setting
* @return {Promise<*>} New value of the setting
* @see {@link SettingProvider#set}
*/
set(key, val) {
if(!this.client.provider) throw new Error('No settings provider is available.');
return this.client.provider.set(this.guild, key, val);
}
/**
* Removes a setting from the guild
* @param {string} key - Name of the setting
* @return {Promise<*>} Old value of the setting
* @see {@link SettingProvider#remove}
*/
remove(key) {
if(!this.client.provider) throw new Error('No settings provider is available.');
return this.client.provider.remove(this.guild, key);
}
/**
* Removes all settings in the guild
* @return {Promise<void>}
* @see {@link SettingProvider#clear}
*/
clear() {
if(!this.client.provider) throw new Error('No settings provider is available.');
return this.client.provider.clear(this.guild);
}
}
module.exports = GuildSettingsHelper;

View File

@@ -0,0 +1,240 @@
const SettingProvider = require('./base');
/**
* Uses an SQLite database to store settings with guilds
* @extends {SettingProvider}
*/
class SyncSQLiteProvider extends SettingProvider {
/**
* @external SyncSQLiteDatabase
* @see {@link https://www.npmjs.com/package/better-sqlite3}
*/
/**
* @param {SyncSQLiteDatabase} conn - Database Connection for the provider
*/
constructor(conn) {
super();
/**
* Database that will be used for storing/retrieving settings
* @type {SyncSQLiteDatabase}
*/
this.conn = conn;
/**
* Client that the provider is for (set once the client is ready, after using {@link CommandoClient#setProvider})
* @name SyncSQLiteProvider#client
* @type {CommandoClient}
* @readonly
*/
Object.defineProperty(this, 'client', { value: null, writable: true });
/**
* Settings cached in memory, mapped by guild ID (or 'global')
* @type {Map}
* @private
*/
this.settings = new Map();
/**
* Listeners on the Client, mapped by the event name
* @type {Map}
* @private
*/
this.listeners = new Map();
/**
* Prepared statement to insert or replace a settings row
* @type {SyncSQLiteStatement}
* @private
*/
this.insertOrReplaceStmt = null;
/**
* Prepared statement to delete an entire settings row
* @type {SyncSQLiteStatement}
* @private
*/
this.deleteStmt = null;
/**
* @external SyncSQLiteStatement
* @see {@link https://www.npmjs.com/package/better-sqlite3}
*/
}
init(client) {
this.client = client;
this.conn.prepare('CREATE TABLE IF NOT EXISTS settings (guild INTEGER PRIMARY KEY, settings TEXT)').run();
// Load all settings
const rows = this.conn.prepare('SELECT CAST(guild as TEXT) as guild, settings FROM settings').all();
for(const row of rows) {
let settings;
try {
settings = JSON.parse(row.settings);
} catch(err) {
client.emit('warn', `SyncSQLiteProvider couldn't parse the settings stored for guild ${row.guild}.`);
continue;
}
const guild = row.guild !== '0' ? row.guild : 'global';
this.settings.set(guild, settings);
if(guild !== 'global' && !client.guilds.cache.has(row.guild)) continue;
this.setupGuild(guild, settings);
}
// Prepare statements
this.insertOrReplaceStmt = this.conn.prepare('INSERT OR REPLACE INTO settings VALUES(?, ?)');
this.deleteStmt = this.conn.prepare('DELETE FROM settings WHERE guild = ?');
// Listen for changes
this.listeners
.set('commandPrefixChange', (guild, prefix) => this.set(guild, 'prefix', prefix))
.set('commandStatusChange', (guild, command, enabled) => this.set(guild, `cmd-${command.name}`, enabled))
.set('groupStatusChange', (guild, group, enabled) => this.set(guild, `grp-${group.id}`, enabled))
.set('guildCreate', guild => {
const settings = this.settings.get(guild.id);
if(!settings) return;
this.setupGuild(guild.id, settings);
})
.set('commandRegister', command => {
for(const [guild, settings] of this.settings) {
if(guild !== 'global' && !client.guilds.cache.has(guild)) continue;
this.setupGuildCommand(client.guilds.cache.get(guild), command, settings);
}
})
.set('groupRegister', group => {
for(const [guild, settings] of this.settings) {
if(guild !== 'global' && !client.guilds.cache.has(guild)) continue;
this.setupGuildGroup(client.guilds.cache.get(guild), group, settings);
}
});
for(const [event, listener] of this.listeners) client.on(event, listener);
}
destroy() {
// Remove all listeners from the client
for(const [event, listener] of this.listeners) this.client.removeListener(event, listener);
this.listeners.clear();
}
get(guild, key, defVal) {
const settings = this.settings.get(this.constructor.getGuildID(guild));
return settings ? typeof settings[key] !== 'undefined' ? settings[key] : defVal : defVal;
}
set(guild, key, val) {
guild = this.constructor.getGuildID(guild);
let settings = this.settings.get(guild);
if(!settings) {
settings = {};
this.settings.set(guild, settings);
}
settings[key] = val;
this.insertOrReplaceStmt.run(guild !== 'global' ? guild : 0, JSON.stringify(settings));
if(guild === 'global') this.updateOtherShards(key, val);
return val;
}
remove(guild, key) {
guild = this.constructor.getGuildID(guild);
const settings = this.settings.get(guild);
if(!settings || typeof settings[key] === 'undefined') return undefined;
const val = settings[key];
settings[key] = undefined;
this.insertOrReplaceStmt.run(guild !== 'global' ? guild : 0, JSON.stringify(settings));
if(guild === 'global') this.updateOtherShards(key, undefined);
return val;
}
clear(guild) {
guild = this.constructor.getGuildID(guild);
if(!this.settings.has(guild)) return;
this.settings.delete(guild);
this.deleteStmt.run(guild !== 'global' ? guild : 0);
}
/**
* Loads all settings for a guild
* @param {string} guild - Guild ID to load the settings of (or 'global')
* @param {Object} settings - Settings to load
* @private
*/
setupGuild(guild, settings) {
if(typeof guild !== 'string') throw new TypeError('The guild must be a guild ID or "global".');
guild = this.client.guilds.cache.get(guild) || null;
// Load the command prefix
if(typeof settings.prefix !== 'undefined') {
if(guild) guild._commandPrefix = settings.prefix;
else this.client._commandPrefix = settings.prefix;
}
// Load all command/group statuses
for(const command of this.client.registry.commands.values()) this.setupGuildCommand(guild, command, settings);
for(const group of this.client.registry.groups.values()) this.setupGuildGroup(guild, group, settings);
}
/**
* Sets up a command's status in a guild from the guild's settings
* @param {?CommandoGuild} guild - Guild to set the status in
* @param {Command} command - Command to set the status of
* @param {Object} settings - Settings of the guild
* @private
*/
setupGuildCommand(guild, command, settings) {
if(typeof settings[`cmd-${command.name}`] === 'undefined') return;
if(guild) {
if(!guild._commandsEnabled) guild._commandsEnabled = {};
guild._commandsEnabled[command.name] = settings[`cmd-${command.name}`];
} else {
command._globalEnabled = settings[`cmd-${command.name}`];
}
}
/**
* Sets up a command group's status in a guild from the guild's settings
* @param {?CommandoGuild} guild - Guild to set the status in
* @param {CommandGroup} group - Group to set the status of
* @param {Object} settings - Settings of the guild
* @private
*/
setupGuildGroup(guild, group, settings) {
if(typeof settings[`grp-${group.id}`] === 'undefined') return;
if(guild) {
if(!guild._groupsEnabled) guild._groupsEnabled = {};
guild._groupsEnabled[group.id] = settings[`grp-${group.id}`];
} else {
group._globalEnabled = settings[`grp-${group.id}`];
}
}
/**
* Updates a global setting on all other shards if using the {@link ShardingManager}.
* @param {string} key - Key of the setting to update
* @param {*} val - Value of the setting
* @private
*/
updateOtherShards(key, val) {
if(!this.client.shard) return;
key = JSON.stringify(key);
val = typeof val !== 'undefined' ? JSON.stringify(val) : 'undefined';
this.client.shard.broadcastEval(`
const ids = [${this.client.shard.ids.join(',')}];
if(!this.shard.ids.some(id => ids.includes(id)) && this.provider && this.provider.settings) {
let global = this.provider.settings.get('global');
if(!global) {
global = {};
this.provider.settings.set('global', global);
}
global[${key}] = ${val};
}
`);
}
}
module.exports = SyncSQLiteProvider;

View File

@@ -0,0 +1,250 @@
const SettingProvider = require('./base');
/**
* Uses an SQLite database to store settings with guilds
* @extends {SettingProvider}
*/
class SQLiteProvider extends SettingProvider {
/**
* @external SQLiteDatabase
* @see {@link https://www.npmjs.com/package/sqlite}
*/
/**
* @param {SQLiteDatabase} db - Database for the provider
*/
constructor(db) {
super();
/**
* Database that will be used for storing/retrieving settings
* @type {SQLiteDatabase}
*/
this.db = db;
/**
* Client that the provider is for (set once the client is ready, after using {@link CommandoClient#setProvider})
* @name SQLiteProvider#client
* @type {CommandoClient}
* @readonly
*/
Object.defineProperty(this, 'client', { value: null, writable: true });
/**
* Settings cached in memory, mapped by guild ID (or 'global')
* @type {Map}
* @private
*/
this.settings = new Map();
/**
* Listeners on the Client, mapped by the event name
* @type {Map}
* @private
*/
this.listeners = new Map();
/**
* Prepared statement to insert or replace a settings row
* @type {SQLiteStatement}
* @private
*/
this.insertOrReplaceStmt = null;
/**
* Prepared statement to delete an entire settings row
* @type {SQLiteStatement}
* @private
*/
this.deleteStmt = null;
/**
* @external SQLiteStatement
* @see {@link https://www.npmjs.com/package/sqlite}
*/
}
async init(client) {
this.client = client;
await this.db.run('CREATE TABLE IF NOT EXISTS settings (guild INTEGER PRIMARY KEY, settings TEXT)');
// Load all settings
const rows = await this.db.all('SELECT CAST(guild as TEXT) as guild, settings FROM settings');
for(const row of rows) {
let settings;
try {
settings = JSON.parse(row.settings);
} catch(err) {
client.emit('warn', `SQLiteProvider couldn't parse the settings stored for guild ${row.guild}.`);
continue;
}
const guild = row.guild !== '0' ? row.guild : 'global';
this.settings.set(guild, settings);
if(guild !== 'global' && !client.guilds.cache.has(row.guild)) continue;
this.setupGuild(guild, settings);
}
// Prepare statements
const statements = await Promise.all([
this.db.prepare('INSERT OR REPLACE INTO settings VALUES(?, ?)'),
this.db.prepare('DELETE FROM settings WHERE guild = ?')
]);
this.insertOrReplaceStmt = statements[0];
this.deleteStmt = statements[1];
// Listen for changes
this.listeners
.set('commandPrefixChange', (guild, prefix) => this.set(guild, 'prefix', prefix))
.set('commandStatusChange', (guild, command, enabled) => this.set(guild, `cmd-${command.name}`, enabled))
.set('groupStatusChange', (guild, group, enabled) => this.set(guild, `grp-${group.id}`, enabled))
.set('guildCreate', guild => {
const settings = this.settings.get(guild.id);
if(!settings) return;
this.setupGuild(guild.id, settings);
})
.set('commandRegister', command => {
for(const [guild, settings] of this.settings) {
if(guild !== 'global' && !client.guilds.cache.has(guild)) continue;
this.setupGuildCommand(client.guilds.cache.get(guild), command, settings);
}
})
.set('groupRegister', group => {
for(const [guild, settings] of this.settings) {
if(guild !== 'global' && !client.guilds.cache.has(guild)) continue;
this.setupGuildGroup(client.guilds.cache.get(guild), group, settings);
}
});
for(const [event, listener] of this.listeners) client.on(event, listener);
}
async destroy() {
// Finalise prepared statements
await Promise.all([
this.insertOrReplaceStmt.finalize(),
this.deleteStmt.finalize()
]);
// Remove all listeners from the client
for(const [event, listener] of this.listeners) this.client.removeListener(event, listener);
this.listeners.clear();
}
get(guild, key, defVal) {
const settings = this.settings.get(this.constructor.getGuildID(guild));
return settings ? typeof settings[key] !== 'undefined' ? settings[key] : defVal : defVal;
}
async set(guild, key, val) {
guild = this.constructor.getGuildID(guild);
let settings = this.settings.get(guild);
if(!settings) {
settings = {};
this.settings.set(guild, settings);
}
settings[key] = val;
await this.insertOrReplaceStmt.run(guild !== 'global' ? guild : 0, JSON.stringify(settings));
if(guild === 'global') this.updateOtherShards(key, val);
return val;
}
async remove(guild, key) {
guild = this.constructor.getGuildID(guild);
const settings = this.settings.get(guild);
if(!settings || typeof settings[key] === 'undefined') return undefined;
const val = settings[key];
settings[key] = undefined;
await this.insertOrReplaceStmt.run(guild !== 'global' ? guild : 0, JSON.stringify(settings));
if(guild === 'global') this.updateOtherShards(key, undefined);
return val;
}
async clear(guild) {
guild = this.constructor.getGuildID(guild);
if(!this.settings.has(guild)) return;
this.settings.delete(guild);
await this.deleteStmt.run(guild !== 'global' ? guild : 0);
}
/**
* Loads all settings for a guild
* @param {string} guild - Guild ID to load the settings of (or 'global')
* @param {Object} settings - Settings to load
* @private
*/
setupGuild(guild, settings) {
if(typeof guild !== 'string') throw new TypeError('The guild must be a guild ID or "global".');
guild = this.client.guilds.cache.get(guild) || null;
// Load the command prefix
if(typeof settings.prefix !== 'undefined') {
if(guild) guild._commandPrefix = settings.prefix;
else this.client._commandPrefix = settings.prefix;
}
// Load all command/group statuses
for(const command of this.client.registry.commands.values()) this.setupGuildCommand(guild, command, settings);
for(const group of this.client.registry.groups.values()) this.setupGuildGroup(guild, group, settings);
}
/**
* Sets up a command's status in a guild from the guild's settings
* @param {?CommandoGuild} guild - Guild to set the status in
* @param {Command} command - Command to set the status of
* @param {Object} settings - Settings of the guild
* @private
*/
setupGuildCommand(guild, command, settings) {
if(typeof settings[`cmd-${command.name}`] === 'undefined') return;
if(guild) {
if(!guild._commandsEnabled) guild._commandsEnabled = {};
guild._commandsEnabled[command.name] = settings[`cmd-${command.name}`];
} else {
command._globalEnabled = settings[`cmd-${command.name}`];
}
}
/**
* Sets up a command group's status in a guild from the guild's settings
* @param {?CommandoGuild} guild - Guild to set the status in
* @param {CommandGroup} group - Group to set the status of
* @param {Object} settings - Settings of the guild
* @private
*/
setupGuildGroup(guild, group, settings) {
if(typeof settings[`grp-${group.id}`] === 'undefined') return;
if(guild) {
if(!guild._groupsEnabled) guild._groupsEnabled = {};
guild._groupsEnabled[group.id] = settings[`grp-${group.id}`];
} else {
group._globalEnabled = settings[`grp-${group.id}`];
}
}
/**
* Updates a global setting on all other shards if using the {@link ShardingManager}.
* @param {string} key - Key of the setting to update
* @param {*} val - Value of the setting
* @private
*/
updateOtherShards(key, val) {
if(!this.client.shard) return;
key = JSON.stringify(key);
val = typeof val !== 'undefined' ? JSON.stringify(val) : 'undefined';
this.client.shard.broadcastEval(`
const ids = [${this.client.shard.ids.join(',')}];
if(!this.shard.ids.some(id => ids.includes(id)) && this.provider && this.provider.settings) {
let global = this.provider.settings.get('global');
if(!global) {
global = {};
this.provider.settings.set('global', global);
}
global[${key}] = ${val};
}
`);
}
}
module.exports = SQLiteProvider;

561
node_modules/discord.js-commando/src/registry.js generated vendored Normal file
View File

@@ -0,0 +1,561 @@
const path = require('path');
const discord = require('discord.js');
const Command = require('./commands/base');
const CommandGroup = require('./commands/group');
const CommandoMessage = require('./extensions/message');
const ArgumentType = require('./types/base');
const { isConstructor } = require('./util');
/** Handles registration and searching of commands and groups */
class CommandoRegistry {
/** @param {CommandoClient} [client] - Client to use */
constructor(client) {
/**
* The client this registry is for
* @name CommandoRegistry#client
* @type {CommandoClient}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });
/**
* Registered commands, mapped by their name
* @type {Collection<string, Command>}
*/
this.commands = new discord.Collection();
/**
* Registered command groups, mapped by their ID
* @type {Collection<string, CommandGroup>}
*/
this.groups = new discord.Collection();
/**
* Registered argument types, mapped by their ID
* @type {Collection<string, ArgumentType>}
*/
this.types = new discord.Collection();
/**
* Fully resolved path to the bot's commands directory
* @type {?string}
*/
this.commandsPath = null;
/**
* Command to run when an unknown command is used
* @type {?Command}
*/
this.unknownCommand = null;
}
/**
* Registers a single group
* @param {CommandGroup|Function|Object|string} group - A CommandGroup instance, a constructor, or the group ID
* @param {string} [name] - Name for the group (if the first argument is the group ID)
* @param {boolean} [guarded] - Whether the group should be guarded (if the first argument is the group ID)
* @return {CommandoRegistry}
* @see {@link CommandoRegistry#registerGroups}
*/
registerGroup(group, name, guarded) {
if(typeof group === 'string') {
group = new CommandGroup(this.client, group, name, guarded);
} else if(isConstructor(group, CommandGroup)) {
group = new group(this.client); // eslint-disable-line new-cap
} else if(typeof group === 'object' && !(group instanceof CommandGroup)) {
group = new CommandGroup(this.client, group.id, group.name, group.guarded);
}
const existing = this.groups.get(group.id);
if(existing) {
existing.name = group.name;
this.client.emit('debug', `Group ${group.id} is already registered; renamed it to "${group.name}".`);
} else {
this.groups.set(group.id, group);
/**
* Emitted when a group is registered
* @event CommandoClient#groupRegister
* @param {CommandGroup} group - Group that was registered
* @param {CommandoRegistry} registry - Registry that the group was registered to
*/
this.client.emit('groupRegister', group, this);
this.client.emit('debug', `Registered group ${group.id}.`);
}
return this;
}
/**
* Registers multiple groups
* @param {CommandGroup[]|Function[]|Object[]|Array<string[]>} groups - An array of CommandGroup instances,
* constructors, plain objects (with ID, name, and guarded properties),
* or arrays of {@link CommandoRegistry#registerGroup} parameters
* @return {CommandoRegistry}
* @example
* registry.registerGroups([
* ['fun', 'Fun'],
* ['mod', 'Moderation']
* ]);
* @example
* registry.registerGroups([
* { id: 'fun', name: 'Fun' },
* { id: 'mod', name: 'Moderation' }
* ]);
*/
registerGroups(groups) {
if(!Array.isArray(groups)) throw new TypeError('Groups must be an Array.');
for(const group of groups) {
if(Array.isArray(group)) this.registerGroup(...group);
else this.registerGroup(group);
}
return this;
}
/**
* Registers a single command
* @param {Command|Function} command - Either a Command instance, or a constructor for one
* @return {CommandoRegistry}
* @see {@link CommandoRegistry#registerCommands}
*/
registerCommand(command) {
/* eslint-disable new-cap */
if(isConstructor(command, Command)) command = new command(this.client);
else if(isConstructor(command.default, Command)) command = new command.default(this.client);
/* eslint-enable new-cap */
if(!(command instanceof Command)) throw new Error(`Invalid command object to register: ${command}`);
// Make sure there aren't any conflicts
if(this.commands.some(cmd => cmd.name === command.name || cmd.aliases.includes(command.name))) {
throw new Error(`A command with the name/alias "${command.name}" is already registered.`);
}
for(const alias of command.aliases) {
if(this.commands.some(cmd => cmd.name === alias || cmd.aliases.includes(alias))) {
throw new Error(`A command with the name/alias "${alias}" is already registered.`);
}
}
const group = this.groups.find(grp => grp.id === command.groupID);
if(!group) throw new Error(`Group "${command.groupID}" is not registered.`);
if(group.commands.some(cmd => cmd.memberName === command.memberName)) {
throw new Error(`A command with the member name "${command.memberName}" is already registered in ${group.id}`);
}
if(command.unknown && this.unknownCommand) throw new Error('An unknown command is already registered.');
// Add the command
command.group = group;
group.commands.set(command.name, command);
this.commands.set(command.name, command);
if(command.unknown) this.unknownCommand = command;
/**
* Emitted when a command is registered
* @event CommandoClient#commandRegister
* @param {Command} command - Command that was registered
* @param {CommandoRegistry} registry - Registry that the command was registered to
*/
this.client.emit('commandRegister', command, this);
this.client.emit('debug', `Registered command ${group.id}:${command.memberName}.`);
return this;
}
/**
* Registers multiple commands
* @param {Command[]|Function[]} commands - An array of Command instances or constructors
* @param {boolean} [ignoreInvalid=false] - Whether to skip over invalid objects without throwing an error
* @return {CommandoRegistry}
*/
registerCommands(commands, ignoreInvalid = false) {
if(!Array.isArray(commands)) throw new TypeError('Commands must be an Array.');
for(const command of commands) {
const valid = isConstructor(command, Command) || isConstructor(command.default, Command) ||
command instanceof Command || command.default instanceof Command;
if(ignoreInvalid && !valid) {
this.client.emit('warn', `Attempting to register an invalid command object: ${command}; skipping.`);
continue;
}
this.registerCommand(command);
}
return this;
}
/**
* Registers all commands in a directory. The files must export a Command class constructor or instance.
* @param {string|RequireAllOptions} options - The path to the directory, or a require-all options object
* @return {CommandoRegistry}
* @example
* const path = require('path');
* registry.registerCommandsIn(path.join(__dirname, 'commands'));
*/
registerCommandsIn(options) {
const obj = require('require-all')(options);
const commands = [];
for(const group of Object.values(obj)) {
for(let command of Object.values(group)) {
if(typeof command.default === 'function') command = command.default;
commands.push(command);
}
}
if(typeof options === 'string' && !this.commandsPath) this.commandsPath = options;
else if(typeof options === 'object' && !this.commandsPath) this.commandsPath = options.dirname;
return this.registerCommands(commands, true);
}
/**
* Registers a single argument type
* @param {ArgumentType|Function} type - Either an ArgumentType instance, or a constructor for one
* @return {CommandoRegistry}
* @see {@link CommandoRegistry#registerTypes}
*/
registerType(type) {
/* eslint-disable new-cap */
if(isConstructor(type, ArgumentType)) type = new type(this.client);
else if(isConstructor(type.default, ArgumentType)) type = new type.default(this.client);
/* eslint-enable new-cap */
if(!(type instanceof ArgumentType)) throw new Error(`Invalid type object to register: ${type}`);
// Make sure there aren't any conflicts
if(this.types.has(type.id)) throw new Error(`An argument type with the ID "${type.id}" is already registered.`);
// Add the type
this.types.set(type.id, type);
/**
* Emitted when an argument type is registered
* @event CommandoClient#typeRegister
* @param {ArgumentType} type - Argument type that was registered
* @param {CommandoRegistry} registry - Registry that the type was registered to
*/
this.client.emit('typeRegister', type, this);
this.client.emit('debug', `Registered argument type ${type.id}.`);
return this;
}
/**
* Registers multiple argument types
* @param {ArgumentType[]|Function[]} types - An array of ArgumentType instances or constructors
* @param {boolean} [ignoreInvalid=false] - Whether to skip over invalid objects without throwing an error
* @return {CommandoRegistry}
*/
registerTypes(types, ignoreInvalid = false) {
if(!Array.isArray(types)) throw new TypeError('Types must be an Array.');
for(const type of types) {
const valid = isConstructor(type, ArgumentType) || isConstructor(type.default, ArgumentType) ||
type instanceof ArgumentType || type.default instanceof ArgumentType;
if(ignoreInvalid && !valid) {
this.client.emit('warn', `Attempting to register an invalid argument type object: ${type}; skipping.`);
continue;
}
this.registerType(type);
}
return this;
}
/**
* Registers all argument types in a directory. The files must export an ArgumentType class constructor or instance.
* @param {string|RequireAllOptions} options - The path to the directory, or a require-all options object
* @return {CommandoRegistry}
*/
registerTypesIn(options) {
const obj = require('require-all')(options);
const types = [];
for(const type of Object.values(obj)) types.push(type);
return this.registerTypes(types, true);
}
/**
* Registers the default argument types, groups, and commands. This is equivalent to:
* ```js
* registry.registerDefaultTypes()
* .registerDefaultGroups()
* .registerDefaultCommands();
* ```
* @return {CommandoRegistry}
*/
registerDefaults() {
this.registerDefaultTypes();
this.registerDefaultGroups();
this.registerDefaultCommands();
return this;
}
/**
* Registers the default groups ("util" and "commands")
* @return {CommandoRegistry}
*/
registerDefaultGroups() {
return this.registerGroups([
['commands', 'Commands', true],
['util', 'Utility']
]);
}
/**
* Registers the default commands to the registry
* @param {Object} [commands] - Object specifying which commands to register
* @param {boolean} [commands.help=true] - Whether to register the built-in help command
* (requires "util" group and "string" type)
* @param {boolean} [commands.prefix=true] - Whether to register the built-in prefix command
* (requires "util" group and "string" type)
* @param {boolean} [commands.eval=true] - Whether to register the built-in eval command
* (requires "util" group and "string" type)
* @param {boolean} [commands.ping=true] - Whether to register the built-in ping command (requires "util" group)
* @param {boolean} [commands.unknownCommand=true] - Whether to register the built-in unknown command
* (requires "util" group)
* @param {boolean} [commands.commandState=true] - Whether to register the built-in command state commands
* (enable, disable, load, unload, reload, list groups - requires "commands" group, "command" type, and "group" type)
* @return {CommandoRegistry}
*/
registerDefaultCommands(commands = {}) {
commands = {
help: true, prefix: true, ping: true, eval: true,
unknownCommand: true, commandState: true, ...commands
};
if(commands.help) this.registerCommand(require('./commands/util/help'));
if(commands.prefix) this.registerCommand(require('./commands/util/prefix'));
if(commands.ping) this.registerCommand(require('./commands/util/ping'));
if(commands.eval) this.registerCommand(require('./commands/util/eval'));
if(commands.unknownCommand) this.registerCommand(require('./commands/util/unknown-command'));
if(commands.commandState) {
this.registerCommands([
require('./commands/commands/groups'),
require('./commands/commands/enable'),
require('./commands/commands/disable'),
require('./commands/commands/reload'),
require('./commands/commands/load'),
require('./commands/commands/unload')
]);
}
return this;
}
/**
* Registers the default argument types to the registry
* @param {Object} [types] - Object specifying which types to register
* @param {boolean} [types.string=true] - Whether to register the built-in string type
* @param {boolean} [types.integer=true] - Whether to register the built-in integer type
* @param {boolean} [types.float=true] - Whether to register the built-in float type
* @param {boolean} [types.boolean=true] - Whether to register the built-in boolean type
* @param {boolean} [types.user=true] - Whether to register the built-in user type
* @param {boolean} [types.member=true] - Whether to register the built-in member type
* @param {boolean} [types.role=true] - Whether to register the built-in role type
* @param {boolean} [types.channel=true] - Whether to register the built-in channel type
* @param {boolean} [types.textChannel=true] - Whether to register the built-in text-channel type
* @param {boolean} [types.voiceChannel=true] - Whether to register the built-in voice-channel type
* @param {boolean} [types.categoryChannel=true] - Whether to register the built-in category-channel type
* @param {boolean} [types.message=true] - Whether to register the built-in message type
* @param {boolean} [types.customEmoji=true] - Whether to register the built-in custom-emoji type
* @param {boolean} [types.defaultEmoji=true] - Whether to register the built-in default-emoji type
* @param {boolean} [types.command=true] - Whether to register the built-in command type
* @param {boolean} [types.group=true] - Whether to register the built-in group type
* @return {CommandoRegistry}
*/
registerDefaultTypes(types = {}) {
types = {
string: true, integer: true, float: true, boolean: true,
user: true, member: true, role: true, channel: true, textChannel: true,
voiceChannel: true, categoryChannel: true, message: true, customEmoji: true,
defaultEmoji: true, command: true, group: true, ...types
};
if(types.string) this.registerType(require('./types/string'));
if(types.integer) this.registerType(require('./types/integer'));
if(types.float) this.registerType(require('./types/float'));
if(types.boolean) this.registerType(require('./types/boolean'));
if(types.user) this.registerType(require('./types/user'));
if(types.member) this.registerType(require('./types/member'));
if(types.role) this.registerType(require('./types/role'));
if(types.channel) this.registerType(require('./types/channel'));
if(types.textChannel) this.registerType(require('./types/text-channel'));
if(types.voiceChannel) this.registerType(require('./types/voice-channel'));
if(types.categoryChannel) this.registerType(require('./types/category-channel'));
if(types.message) this.registerType(require('./types/message'));
if(types.customEmoji) this.registerType(require('./types/custom-emoji'));
if(types.defaultEmoji) this.registerType(require('./types/default-emoji'));
if(types.command) this.registerType(require('./types/command'));
if(types.group) this.registerType(require('./types/group'));
return this;
}
/**
* Reregisters a command (does not support changing name, group, or memberName)
* @param {Command|Function} command - New command
* @param {Command} oldCommand - Old command
*/
reregisterCommand(command, oldCommand) {
/* eslint-disable new-cap */
if(isConstructor(command, Command)) command = new command(this.client);
else if(isConstructor(command.default, Command)) command = new command.default(this.client);
/* eslint-enable new-cap */
if(command.name !== oldCommand.name) throw new Error('Command name cannot change.');
if(command.groupID !== oldCommand.groupID) throw new Error('Command group cannot change.');
if(command.memberName !== oldCommand.memberName) throw new Error('Command memberName cannot change.');
if(command.unknown && this.unknownCommand !== oldCommand) {
throw new Error('An unknown command is already registered.');
}
command.group = this.resolveGroup(command.groupID);
command.group.commands.set(command.name, command);
this.commands.set(command.name, command);
if(this.unknownCommand === oldCommand) this.unknownCommand = null;
if(command.unknown) this.unknownCommand = command;
/**
* Emitted when a command is reregistered
* @event CommandoClient#commandReregister
* @param {Command} newCommand - New command
* @param {Command} oldCommand - Old command
*/
this.client.emit('commandReregister', command, oldCommand);
this.client.emit('debug', `Reregistered command ${command.groupID}:${command.memberName}.`);
}
/**
* Unregisters a command
* @param {Command} command - Command to unregister
*/
unregisterCommand(command) {
this.commands.delete(command.name);
command.group.commands.delete(command.name);
if(this.unknownCommand === command) this.unknownCommand = null;
/**
* Emitted when a command is unregistered
* @event CommandoClient#commandUnregister
* @param {Command} command - Command that was unregistered
*/
this.client.emit('commandUnregister', command);
this.client.emit('debug', `Unregistered command ${command.groupID}:${command.memberName}.`);
}
/**
* Finds all groups that match the search string
* @param {string} [searchString] - The string to search for
* @param {boolean} [exact=false] - Whether the search should be exact
* @return {CommandGroup[]} All groups that are found
*/
findGroups(searchString = null, exact = false) {
if(!searchString) return this.groups;
// Find all matches
const lcSearch = searchString.toLowerCase();
const matchedGroups = Array.from(this.groups.filter(
exact ? groupFilterExact(lcSearch) : groupFilterInexact(lcSearch)
).values());
if(exact) return matchedGroups;
// See if there's an exact match
for(const group of matchedGroups) {
if(group.name.toLowerCase() === lcSearch || group.id === lcSearch) return [group];
}
return matchedGroups;
}
/**
* A CommandGroupResolvable can be:
* * A CommandGroup
* * A group ID
* @typedef {CommandGroup|string} CommandGroupResolvable
*/
/**
* Resolves a CommandGroupResolvable to a CommandGroup object
* @param {CommandGroupResolvable} group - The group to resolve
* @return {CommandGroup} The resolved CommandGroup
*/
resolveGroup(group) {
if(group instanceof CommandGroup) return group;
if(typeof group === 'string') {
const groups = this.findGroups(group, true);
if(groups.length === 1) return groups[0];
}
throw new Error('Unable to resolve group.');
}
/**
* Finds all commands that match the search string
* @param {string} [searchString] - The string to search for
* @param {boolean} [exact=false] - Whether the search should be exact
* @param {Message} [message] - The message to check usability against
* @return {Command[]} All commands that are found
*/
findCommands(searchString = null, exact = false, message = null) {
if(!searchString) {
return message ?
Array.from(this.commands.filter(cmd => cmd.isUsable(message)).values()) :
Array.from(this.commands);
}
// Find all matches
const lcSearch = searchString.toLowerCase();
const matchedCommands = Array.from(this.commands.filter(
exact ? commandFilterExact(lcSearch) : commandFilterInexact(lcSearch)
).values());
if(exact) return matchedCommands;
// See if there's an exact match
for(const command of matchedCommands) {
if(command.name === lcSearch || (command.aliases && command.aliases.some(ali => ali === lcSearch))) {
return [command];
}
}
return matchedCommands;
}
/**
* A CommandResolvable can be:
* * A Command
* * A command name
* * A CommandoMessage
* @typedef {Command|string} CommandResolvable
*/
/**
* Resolves a CommandResolvable to a Command object
* @param {CommandResolvable} command - The command to resolve
* @return {Command} The resolved Command
*/
resolveCommand(command) {
if(command instanceof Command) return command;
if(command instanceof CommandoMessage && command.command) return command.command;
if(typeof command === 'string') {
const commands = this.findCommands(command, true);
if(commands.length === 1) return commands[0];
}
throw new Error('Unable to resolve command.');
}
/**
* Resolves a command file path from a command's group ID and memberName
* @param {string} group - ID of the command's group
* @param {string} memberName - Member name of the command
* @return {string} Fully-resolved path to the corresponding command file
*/
resolveCommandPath(group, memberName) {
return path.join(this.commandsPath, group, `${memberName}.js`);
}
}
function groupFilterExact(search) {
return grp => grp.id === search || grp.name.toLowerCase() === search;
}
function groupFilterInexact(search) {
return grp => grp.id.includes(search) || grp.name.toLowerCase().includes(search);
}
function commandFilterExact(search) {
return cmd => cmd.name === search ||
(cmd.aliases && cmd.aliases.some(ali => ali === search)) ||
`${cmd.groupID}:${cmd.memberName}` === search;
}
function commandFilterInexact(search) {
return cmd => cmd.name.includes(search) ||
`${cmd.groupID}:${cmd.memberName}` === search ||
(cmd.aliases && cmd.aliases.some(ali => ali.includes(search)));
}
module.exports = CommandoRegistry;

70
node_modules/discord.js-commando/src/types/base.js generated vendored Normal file
View File

@@ -0,0 +1,70 @@
/** A type for command arguments */
class ArgumentType {
/**
* @param {CommandoClient} client - The client the argument type is for
* @param {string} id - The argument type ID (this is what you specify in {@link ArgumentInfo#type})
*/
constructor(client, id) {
if(!client) throw new Error('A client must be specified.');
if(typeof id !== 'string') throw new Error('Argument type ID must be a string.');
if(id !== id.toLowerCase()) throw new Error('Argument type ID must be lowercase.');
/**
* Client that this argument type is for
* @name ArgumentType#client
* @type {CommandoClient}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });
/**
* ID of this argument type (this is what you specify in {@link ArgumentInfo#type})
* @type {string}
*/
this.id = id;
}
// eslint-disable-next-line valid-jsdoc
/**
* Validates a value string against the type
* @param {string} val - Value to validate
* @param {CommandoMessage} originalMsg - Message that triggered the command
* @param {Argument} arg - Argument the value was obtained from
* @param {?CommandoMessage} [currentMsg=originalMsg] - Current response message
* @return {boolean|string|Promise<boolean|string>} Whether the value is valid, or an error message
* @abstract
*/
validate(val, originalMsg, arg, currentMsg = originalMsg) { // eslint-disable-line no-unused-vars
throw new Error(`${this.constructor.name} doesn't have a validate() method.`);
}
// eslint-disable-next-line valid-jsdoc
/**
* Parses the raw value string into a usable value
* @param {string} val - Value to parse
* @param {CommandoMessage} originalMsg - Message that triggered the command
* @param {Argument} arg - Argument the value was obtained from
* @param {?CommandoMessage} [currentMsg=originalMsg] - Current response message
* @return {*|Promise<*>} Usable value
* @abstract
*/
parse(val, originalMsg, arg, currentMsg = originalMsg) { // eslint-disable-line no-unused-vars
throw new Error(`${this.constructor.name} doesn't have a parse() method.`);
}
/**
* Checks whether a value is considered to be empty. This determines whether the default value for an argument
* should be used and changes the response to the user under certain circumstances.
* @param {string} val - Value to check for emptiness
* @param {CommandoMessage} originalMsg - Message that triggered the command
* @param {Argument} arg - Argument the value was obtained from
* @param {?CommandoMessage} [currentMsg=originalMsg] - Current response message
* @return {boolean} Whether the value is empty
*/
isEmpty(val, originalMsg, arg, currentMsg = originalMsg) { // eslint-disable-line no-unused-vars
if(Array.isArray(val)) return val.length === 0;
return !val;
}
}
module.exports = ArgumentType;

23
node_modules/discord.js-commando/src/types/boolean.js generated vendored Normal file
View File

@@ -0,0 +1,23 @@
const ArgumentType = require('./base');
class BooleanArgumentType extends ArgumentType {
constructor(client) {
super(client, 'boolean');
this.truthy = new Set(['true', 't', 'yes', 'y', 'on', 'enable', 'enabled', '1', '+']);
this.falsy = new Set(['false', 'f', 'no', 'n', 'off', 'disable', 'disabled', '0', '-']);
}
validate(val) {
const lc = val.toLowerCase();
return this.truthy.has(lc) || this.falsy.has(lc);
}
parse(val) {
const lc = val.toLowerCase();
if(this.truthy.has(lc)) return true;
if(this.falsy.has(lc)) return false;
throw new RangeError('Unknown boolean value.');
}
}
module.exports = BooleanArgumentType;

View File

@@ -0,0 +1,65 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class CategoryChannelArgumentType extends ArgumentType {
constructor(client) {
super(client, 'category-channel');
}
validate(val, msg, arg) {
const matches = val.match(/^([0-9]+)$/);
if(matches) {
try {
const channel = msg.client.channels.resolve(matches[1]);
if(!channel || channel.type !== 'category') return false;
if(arg.oneOf && !arg.oneOf.includes(channel.id)) return false;
return true;
} catch(err) {
return false;
}
}
if(!msg.guild) return false;
const search = val.toLowerCase();
let channels = msg.guild.channels.cache.filter(channelFilterInexact(search));
if(channels.size === 0) return false;
if(channels.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(channels.first().id)) return false;
return true;
}
const exactChannels = channels.filter(channelFilterExact(search));
if(exactChannels.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(exactChannels.first().id)) return false;
return true;
}
if(exactChannels.size > 0) channels = exactChannels;
return channels.size <= 15 ?
`${disambiguation(
channels.map(chan => escapeMarkdown(chan.name)), 'categories', null
)}\n` :
'Multiple categories found. Please be more specific.';
}
parse(val, msg) {
const matches = val.match(/^([0-9]+)$/);
if(matches) return msg.client.channels.cache.get(matches[1]) || null;
if(!msg.guild) return null;
const search = val.toLowerCase();
const channels = msg.guild.channels.cache.filter(channelFilterInexact(search));
if(channels.size === 0) return null;
if(channels.size === 1) return channels.first();
const exactChannels = channels.filter(channelFilterExact(search));
if(exactChannels.size === 1) return exactChannels.first();
return null;
}
}
function channelFilterExact(search) {
return chan => chan.type === 'category' && chan.name.toLowerCase() === search;
}
function channelFilterInexact(search) {
return chan => chan.type === 'category' && chan.name.toLowerCase().includes(search);
}
module.exports = CategoryChannelArgumentType;

52
node_modules/discord.js-commando/src/types/channel.js generated vendored Normal file
View File

@@ -0,0 +1,52 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class ChannelArgumentType extends ArgumentType {
constructor(client) {
super(client, 'channel');
}
validate(val, msg, arg) {
const matches = val.match(/^(?:<#)?([0-9]+)>?$/);
if(matches) return msg.guild.channels.cache.has(matches[1]);
const search = val.toLowerCase();
let channels = msg.guild.channels.cache.filter(nameFilterInexact(search));
if(channels.size === 0) return false;
if(channels.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(channels.first().id)) return false;
return true;
}
const exactChannels = channels.filter(nameFilterExact(search));
if(exactChannels.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(exactChannels.first().id)) return false;
return true;
}
if(exactChannels.size > 0) channels = exactChannels;
return channels.size <= 15 ?
`${disambiguation(channels.map(chan => escapeMarkdown(chan.name)), 'channels', null)}\n` :
'Multiple channels found. Please be more specific.';
}
parse(val, msg) {
const matches = val.match(/^(?:<#)?([0-9]+)>?$/);
if(matches) return msg.guild.channels.cache.get(matches[1]) || null;
const search = val.toLowerCase();
const channels = msg.guild.channels.cache.filter(nameFilterInexact(search));
if(channels.size === 0) return null;
if(channels.size === 1) return channels.first();
const exactChannels = channels.filter(nameFilterExact(search));
if(exactChannels.size === 1) return exactChannels.first();
return null;
}
}
function nameFilterExact(search) {
return thing => thing.name.toLowerCase() === search;
}
function nameFilterInexact(search) {
return thing => thing.name.toLowerCase().includes(search);
}
module.exports = ChannelArgumentType;

24
node_modules/discord.js-commando/src/types/command.js generated vendored Normal file
View File

@@ -0,0 +1,24 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class CommandArgumentType extends ArgumentType {
constructor(client) {
super(client, 'command');
}
validate(val) {
const commands = this.client.registry.findCommands(val);
if(commands.length === 1) return true;
if(commands.length === 0) return false;
return commands.length <= 15 ?
`${disambiguation(commands.map(cmd => escapeMarkdown(cmd.name)), 'commands', null)}\n` :
'Multiple commands found. Please be more specific.';
}
parse(val) {
return this.client.registry.findCommands(val)[0];
}
}
module.exports = CommandArgumentType;

View File

@@ -0,0 +1,47 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class CustomEmojiArgumentType extends ArgumentType {
constructor(client) {
super(client, 'custom-emoji');
}
validate(value, msg) {
const matches = value.match(/^(?:<a?:([a-zA-Z0-9_]+):)?([0-9]+)>?$/);
if(matches && msg.client.emojis.cache.has(matches[2])) return true;
if(!msg.guild) return false;
const search = value.toLowerCase();
let emojis = msg.guild.emojis.cache.filter(nameFilterInexact(search));
if(!emojis.size) return false;
if(emojis.size === 1) return true;
const exactEmojis = emojis.filter(nameFilterExact(search));
if(exactEmojis.size === 1) return true;
if(exactEmojis.size > 0) emojis = exactEmojis;
return emojis.size <= 15 ?
`${disambiguation(emojis.map(emoji => escapeMarkdown(emoji.name)), 'emojis', null)}\n` :
'Multiple emojis found. Please be more specific.';
}
parse(value, msg) {
const matches = value.match(/^(?:<a?:([a-zA-Z0-9_]+):)?([0-9]+)>?$/);
if(matches) return msg.client.emojis.cache.get(matches[2]) || null;
const search = value.toLowerCase();
const emojis = msg.guild.emojis.cache.filter(nameFilterInexact(search));
if(!emojis.size) return null;
if(emojis.size === 1) return emojis.first();
const exactEmojis = emojis.filter(nameFilterExact(search));
if(exactEmojis.size === 1) return exactEmojis.first();
return null;
}
}
function nameFilterExact(search) {
return emoji => emoji.name.toLowerCase() === search;
}
function nameFilterInexact(search) {
return emoji => emoji.name.toLowerCase().includes(search);
}
module.exports = CustomEmojiArgumentType;

View File

@@ -0,0 +1,22 @@
const ArgumentType = require('./base');
const emojiRegex = require('emoji-regex/RGI_Emoji.js');
class DefaultEmojiArgumentType extends ArgumentType {
constructor(client) {
super(client, 'default-emoji');
}
validate(value, msg, arg) {
if(!new RegExp(`^(?:${emojiRegex().source})$`).test(value)) return false;
if(arg.oneOf && !arg.oneOf.includes(value)) {
return `Please enter one of the following options: ${arg.oneOf.join(' | ')}`;
}
return true;
}
parse(value) {
return value;
}
}
module.exports = DefaultEmojiArgumentType;

28
node_modules/discord.js-commando/src/types/float.js generated vendored Normal file
View File

@@ -0,0 +1,28 @@
const ArgumentType = require('./base');
class FloatArgumentType extends ArgumentType {
constructor(client) {
super(client, 'float');
}
validate(val, msg, arg) {
const float = Number.parseFloat(val);
if(Number.isNaN(float)) return false;
if(arg.oneOf && !arg.oneOf.includes(float)) {
return `Please enter one of the following options: ${arg.oneOf.map(opt => `\`${opt}\``).join(', ')}`;
}
if(arg.min !== null && typeof arg.min !== 'undefined' && float < arg.min) {
return `Please enter a number above or exactly ${arg.min}.`;
}
if(arg.max !== null && typeof arg.max !== 'undefined' && float > arg.max) {
return `Please enter a number below or exactly ${arg.max}.`;
}
return true;
}
parse(val) {
return Number.parseFloat(val);
}
}
module.exports = FloatArgumentType;

24
node_modules/discord.js-commando/src/types/group.js generated vendored Normal file
View File

@@ -0,0 +1,24 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class GroupArgumentType extends ArgumentType {
constructor(client) {
super(client, 'group');
}
validate(val) {
const groups = this.client.registry.findGroups(val);
if(groups.length === 1) return true;
if(groups.length === 0) return false;
return groups.length <= 15 ?
`${disambiguation(groups.map(grp => escapeMarkdown(grp.name)), 'groups', null)}\n` :
'Multiple groups found. Please be more specific.';
}
parse(val) {
return this.client.registry.findGroups(val)[0];
}
}
module.exports = GroupArgumentType;

28
node_modules/discord.js-commando/src/types/integer.js generated vendored Normal file
View File

@@ -0,0 +1,28 @@
const ArgumentType = require('./base');
class IntegerArgumentType extends ArgumentType {
constructor(client) {
super(client, 'integer');
}
validate(val, msg, arg) {
const int = Number.parseInt(val);
if(Number.isNaN(int)) return false;
if(arg.oneOf && !arg.oneOf.includes(int)) {
return `Please enter one of the following options: ${arg.oneOf.map(opt => `\`${opt}\``).join(', ')}`;
}
if(arg.min !== null && typeof arg.min !== 'undefined' && int < arg.min) {
return `Please enter a number above or exactly ${arg.min}.`;
}
if(arg.max !== null && typeof arg.max !== 'undefined' && int > arg.max) {
return `Please enter a number below or exactly ${arg.max}.`;
}
return true;
}
parse(val) {
return Number.parseInt(val);
}
}
module.exports = IntegerArgumentType;

67
node_modules/discord.js-commando/src/types/member.js generated vendored Normal file
View File

@@ -0,0 +1,67 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class MemberArgumentType extends ArgumentType {
constructor(client) {
super(client, 'member');
}
async validate(val, msg, arg) {
const matches = val.match(/^(?:<@!?)?([0-9]+)>?$/);
if(matches) {
try {
const member = await msg.guild.members.fetch(await msg.client.users.fetch(matches[1]));
if(!member) return false;
if(arg.oneOf && !arg.oneOf.includes(member.id)) return false;
return true;
} catch(err) {
return false;
}
}
const search = val.toLowerCase();
let members = msg.guild.members.cache.filter(memberFilterInexact(search));
if(members.size === 0) return false;
if(members.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(members.first().id)) return false;
return true;
}
const exactMembers = members.filter(memberFilterExact(search));
if(exactMembers.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(exactMembers.first().id)) return false;
return true;
}
if(exactMembers.size > 0) members = exactMembers;
return members.size <= 15 ?
`${disambiguation(
members.map(mem => `${escapeMarkdown(mem.user.username)}#${mem.user.discriminator}`), 'members', null
)}\n` :
'Multiple members found. Please be more specific.';
}
parse(val, msg) {
const matches = val.match(/^(?:<@!?)?([0-9]+)>?$/);
if(matches) return msg.guild.member(matches[1]) || null;
const search = val.toLowerCase();
const members = msg.guild.members.cache.filter(memberFilterInexact(search));
if(members.size === 0) return null;
if(members.size === 1) return members.first();
const exactMembers = members.filter(memberFilterExact(search));
if(exactMembers.size === 1) return exactMembers.first();
return null;
}
}
function memberFilterExact(search) {
return mem => mem.user.username.toLowerCase() === search ||
(mem.nickname && mem.nickname.toLowerCase() === search) ||
`${mem.user.username.toLowerCase()}#${mem.user.discriminator}` === search;
}
function memberFilterInexact(search) {
return mem => mem.user.username.toLowerCase().includes(search) ||
(mem.nickname && mem.nickname.toLowerCase().includes(search)) ||
`${mem.user.username.toLowerCase()}#${mem.user.discriminator}`.includes(search);
}
module.exports = MemberArgumentType;

18
node_modules/discord.js-commando/src/types/message.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
const ArgumentType = require('./base');
class MessageArgumentType extends ArgumentType {
constructor(client) {
super(client, 'message');
}
async validate(val, msg) {
if(!/^[0-9]+$/.test(val)) return false;
return Boolean(await msg.channel.messages.fetch(val).catch(() => null));
}
parse(val, msg) {
return msg.channel.messages.cache.get(val);
}
}
module.exports = MessageArgumentType;

52
node_modules/discord.js-commando/src/types/role.js generated vendored Normal file
View File

@@ -0,0 +1,52 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class RoleArgumentType extends ArgumentType {
constructor(client) {
super(client, 'role');
}
validate(val, msg, arg) {
const matches = val.match(/^(?:<@&)?([0-9]+)>?$/);
if(matches) return msg.guild.roles.cache.has(matches[1]);
const search = val.toLowerCase();
let roles = msg.guild.roles.cache.filter(nameFilterInexact(search));
if(roles.size === 0) return false;
if(roles.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(roles.first().id)) return false;
return true;
}
const exactRoles = roles.filter(nameFilterExact(search));
if(exactRoles.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(exactRoles.first().id)) return false;
return true;
}
if(exactRoles.size > 0) roles = exactRoles;
return roles.size <= 15 ?
`${disambiguation(roles.map(role => `${escapeMarkdown(role.name)}`), 'roles', null)}\n` :
'Multiple roles found. Please be more specific.';
}
parse(val, msg) {
const matches = val.match(/^(?:<@&)?([0-9]+)>?$/);
if(matches) return msg.guild.roles.cache.get(matches[1]) || null;
const search = val.toLowerCase();
const roles = msg.guild.roles.cache.filter(nameFilterInexact(search));
if(roles.size === 0) return null;
if(roles.size === 1) return roles.first();
const exactRoles = roles.filter(nameFilterExact(search));
if(exactRoles.size === 1) return exactRoles.first();
return null;
}
}
function nameFilterExact(search) {
return thing => thing.name.toLowerCase() === search;
}
function nameFilterInexact(search) {
return thing => thing.name.toLowerCase().includes(search);
}
module.exports = RoleArgumentType;

26
node_modules/discord.js-commando/src/types/string.js generated vendored Normal file
View File

@@ -0,0 +1,26 @@
const ArgumentType = require('./base');
class StringArgumentType extends ArgumentType {
constructor(client) {
super(client, 'string');
}
validate(val, msg, arg) {
if(arg.oneOf && !arg.oneOf.includes(val.toLowerCase())) {
return `Please enter one of the following options: ${arg.oneOf.map(opt => `\`${opt}\``).join(', ')}`;
}
if(arg.min !== null && typeof arg.min !== 'undefined' && val.length < arg.min) {
return `Please keep the ${arg.label} above or exactly ${arg.min} characters.`;
}
if(arg.max !== null && typeof arg.max !== 'undefined' && val.length > arg.max) {
return `Please keep the ${arg.label} below or exactly ${arg.max} characters.`;
}
return true;
}
parse(val) {
return val;
}
}
module.exports = StringArgumentType;

View File

@@ -0,0 +1,65 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class TextChannelArgumentType extends ArgumentType {
constructor(client) {
super(client, 'text-channel');
}
validate(val, msg, arg) {
const matches = val.match(/^(?:<#)?([0-9]+)>?$/);
if(matches) {
try {
const channel = msg.client.channels.resolve(matches[1]);
if(!channel || channel.type !== 'text') return false;
if(arg.oneOf && !arg.oneOf.includes(channel.id)) return false;
return true;
} catch(err) {
return false;
}
}
if(!msg.guild) return false;
const search = val.toLowerCase();
let channels = msg.guild.channels.cache.filter(channelFilterInexact(search));
if(channels.size === 0) return false;
if(channels.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(channels.first().id)) return false;
return true;
}
const exactChannels = channels.filter(channelFilterExact(search));
if(exactChannels.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(exactChannels.first().id)) return false;
return true;
}
if(exactChannels.size > 0) channels = exactChannels;
return channels.size <= 15 ?
`${disambiguation(
channels.map(chan => escapeMarkdown(chan.name)), 'text channels', null
)}\n` :
'Multiple text channels found. Please be more specific.';
}
parse(val, msg) {
const matches = val.match(/^(?:<#)?([0-9]+)>?$/);
if(matches) return msg.client.channels.resolve(matches[1]) || null;
if(!msg.guild) return null;
const search = val.toLowerCase();
const channels = msg.guild.channels.cache.filter(channelFilterInexact(search));
if(channels.size === 0) return null;
if(channels.size === 1) return channels.first();
const exactChannels = channels.filter(channelFilterExact(search));
if(exactChannels.size === 1) return exactChannels.first();
return null;
}
}
function channelFilterExact(search) {
return chan => chan.type === 'text' && chan.name.toLowerCase() === search;
}
function channelFilterInexact(search) {
return chan => chan.type === 'text' && chan.name.toLowerCase().includes(search);
}
module.exports = TextChannelArgumentType;

47
node_modules/discord.js-commando/src/types/union.js generated vendored Normal file
View File

@@ -0,0 +1,47 @@
const ArgumentType = require('./base');
/**
* A type for command arguments that handles multiple other types
* @extends {ArgumentType}
*/
class ArgumentUnionType extends ArgumentType {
constructor(client, id) {
super(client, id);
/**
* Types to handle, in order of priority
* @type {ArgumentType[]}
*/
this.types = [];
const typeIDs = id.split('|');
for(const typeID of typeIDs) {
const type = client.registry.types.get(typeID);
if(!type) throw new Error(`Argument type "${typeID}" is not registered.`);
this.types.push(type);
}
}
async validate(val, msg, arg) {
let results = this.types.map(type => !type.isEmpty(val, msg, arg) && type.validate(val, msg, arg));
results = await Promise.all(results);
if(results.some(valid => valid && typeof valid !== 'string')) return true;
const errors = results.filter(valid => typeof valid === 'string');
if(errors.length > 0) return errors.join('\n');
return false;
}
async parse(val, msg, arg) {
let results = this.types.map(type => !type.isEmpty(val, msg, arg) && type.validate(val, msg, arg));
results = await Promise.all(results);
for(let i = 0; i < results.length; i++) {
if(results[i] && typeof results[i] !== 'string') return this.types[i].parse(val, msg, arg);
}
throw new Error(`Couldn't parse value "${val}" with union type ${this.id}.`);
}
isEmpty(val, msg, arg) {
return !this.types.some(type => !type.isEmpty(val, msg, arg));
}
}
module.exports = ArgumentUnionType;

69
node_modules/discord.js-commando/src/types/user.js generated vendored Normal file
View File

@@ -0,0 +1,69 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class UserArgumentType extends ArgumentType {
constructor(client) {
super(client, 'user');
}
async validate(val, msg, arg) {
const matches = val.match(/^(?:<@!?)?([0-9]+)>?$/);
if(matches) {
try {
const user = await msg.client.users.fetch(matches[1]);
if(!user) return false;
if(arg.oneOf && !arg.oneOf.includes(user.id)) return false;
return true;
} catch(err) {
return false;
}
}
if(!msg.guild) return false;
const search = val.toLowerCase();
let members = msg.guild.members.cache.filter(memberFilterInexact(search));
if(members.size === 0) return false;
if(members.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(members.first().id)) return false;
return true;
}
const exactMembers = members.filter(memberFilterExact(search));
if(exactMembers.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(exactMembers.first().id)) return false;
return true;
}
if(exactMembers.size > 0) members = exactMembers;
return members.size <= 15 ?
`${disambiguation(
members.map(mem => `${escapeMarkdown(mem.user.username)}#${mem.user.discriminator}`), 'users', null
)}\n` :
'Multiple users found. Please be more specific.';
}
parse(val, msg) {
const matches = val.match(/^(?:<@!?)?([0-9]+)>?$/);
if(matches) return msg.client.users.cache.get(matches[1]) || null;
if(!msg.guild) return null;
const search = val.toLowerCase();
const members = msg.guild.members.cache.filter(memberFilterInexact(search));
if(members.size === 0) return null;
if(members.size === 1) return members.first().user;
const exactMembers = members.filter(memberFilterExact(search));
if(exactMembers.size === 1) return exactMembers.first().user;
return null;
}
}
function memberFilterExact(search) {
return mem => mem.user.username.toLowerCase() === search ||
(mem.nickname && mem.nickname.toLowerCase() === search) ||
`${mem.user.username.toLowerCase()}#${mem.user.discriminator}` === search;
}
function memberFilterInexact(search) {
return mem => mem.user.username.toLowerCase().includes(search) ||
(mem.nickname && mem.nickname.toLowerCase().includes(search)) ||
`${mem.user.username.toLowerCase()}#${mem.user.discriminator}`.includes(search);
}
module.exports = UserArgumentType;

View File

@@ -0,0 +1,65 @@
const ArgumentType = require('./base');
const { disambiguation } = require('../util');
const { escapeMarkdown } = require('discord.js');
class VoiceChannelArgumentType extends ArgumentType {
constructor(client) {
super(client, 'voice-channel');
}
validate(val, msg, arg) {
const matches = val.match(/^([0-9]+)$/);
if(matches) {
try {
const channel = msg.client.channels.resolve(matches[1]);
if(!channel || channel.type !== 'voice') return false;
if(arg.oneOf && !arg.oneOf.includes(channel.id)) return false;
return true;
} catch(err) {
return false;
}
}
if(!msg.guild) return false;
const search = val.toLowerCase();
let channels = msg.guild.channels.cache.filter(channelFilterInexact(search));
if(channels.size === 0) return false;
if(channels.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(channels.first().id)) return false;
return true;
}
const exactChannels = channels.filter(channelFilterExact(search));
if(exactChannels.size === 1) {
if(arg.oneOf && !arg.oneOf.includes(exactChannels.first().id)) return false;
return true;
}
if(exactChannels.size > 0) channels = exactChannels;
return channels.size <= 15 ?
`${disambiguation(
channels.map(chan => escapeMarkdown(chan.name)), 'voice channels', null
)}\n` :
'Multiple voice channels found. Please be more specific.';
}
parse(val, msg) {
const matches = val.match(/^([0-9]+)$/);
if(matches) return msg.client.channels.cache.get(matches[1]) || null;
if(!msg.guild) return null;
const search = val.toLowerCase();
const channels = msg.guild.channels.cache.filter(channelFilterInexact(search));
if(channels.size === 0) return null;
if(channels.size === 1) return channels.first();
const exactChannels = channels.filter(channelFilterExact(search));
if(exactChannels.size === 1) return exactChannels.first();
return null;
}
}
function channelFilterExact(search) {
return chan => chan.type === 'voice' && chan.name.toLowerCase() === search;
}
function channelFilterInexact(search) {
return chan => chan.type === 'voice' && chan.name.toLowerCase().includes(search);
}
module.exports = VoiceChannelArgumentType;

78
node_modules/discord.js-commando/src/util.js generated vendored Normal file
View File

@@ -0,0 +1,78 @@
// This returns Object.prototype in order to return a valid object
// without creating a new one each time this is called just to discard it the moment after.
const isConstructorProxyHandler = { construct() { return Object.prototype; } };
function escapeRegex(str) {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
}
function disambiguation(items, label, property = 'name') {
const itemList = items.map(item => `"${(property ? item[property] : item).replace(/ /g, '\xa0')}"`).join(', ');
return `Multiple ${label} found, please be more specific: ${itemList}`;
}
function isConstructor(func, _class) {
try {
// eslint-disable-next-line no-new
new new Proxy(func, isConstructorProxyHandler)();
if(!_class) return true;
return func.prototype instanceof _class;
} catch(err) {
return false;
}
}
function paginate(items, page = 1, pageLength = 10) {
const maxPage = Math.ceil(items.length / pageLength);
if(page < 1) page = 1;
if(page > maxPage) page = maxPage;
const startIndex = (page - 1) * pageLength;
return {
items: items.length > pageLength ? items.slice(startIndex, startIndex + pageLength) : items,
page,
maxPage,
pageLength
};
}
const permissions = {
ADMINISTRATOR: 'Administrator',
VIEW_AUDIT_LOG: 'View audit log',
MANAGE_GUILD: 'Manage server',
MANAGE_ROLES: 'Manage roles',
MANAGE_CHANNELS: 'Manage channels',
KICK_MEMBERS: 'Kick members',
BAN_MEMBERS: 'Ban members',
CREATE_INSTANT_INVITE: 'Create instant invite',
CHANGE_NICKNAME: 'Change nickname',
MANAGE_NICKNAMES: 'Manage nicknames',
MANAGE_EMOJIS: 'Manage emojis',
MANAGE_WEBHOOKS: 'Manage webhooks',
VIEW_CHANNEL: 'View channels',
SEND_MESSAGES: 'Send messages',
SEND_TTS_MESSAGES: 'Send TTS messages',
MANAGE_MESSAGES: 'Manage messages',
EMBED_LINKS: 'Embed links',
ATTACH_FILES: 'Attach files',
READ_MESSAGE_HISTORY: 'Read message history',
MENTION_EVERYONE: 'Mention everyone',
USE_EXTERNAL_EMOJIS: 'Use external emojis',
ADD_REACTIONS: 'Add reactions',
CONNECT: 'Connect',
SPEAK: 'Speak',
MUTE_MEMBERS: 'Mute members',
DEAFEN_MEMBERS: 'Deafen members',
MOVE_MEMBERS: 'Move members',
USE_VAD: 'Use voice activity',
PRIORITY_SPEAKER: 'Priority speaker',
VIEW_GUILD_INSIGHTS: 'View server insights',
STREAM: 'Video'
};
module.exports = {
escapeRegex,
disambiguation,
paginate,
permissions,
isConstructor
};

19
node_modules/discord.js-commando/tsconfig.json generated vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"lib": [
"es7"
],
"noImplicitAny": false,
"removeComments": true,
"sourceMap": true,
"allowJs": true
},
"include": [
"typings/**/*"
],
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,33 @@
/// <reference path='index.d.ts' />
import { Message } from 'discord.js';
import { Command, CommandoMessage, CommandoClient } from 'discord.js-commando';
const client = new CommandoClient();
client.on('message', (message: Message) => {
if (message.content === 'hello') {
message.channel.send('o/');
}
});
class TestCommand extends Command {
constructor(client: CommandoClient) {
super(client, {
name: 'test',
group: 'test',
memberName: 'test',
description: 'test'
});
}
public hasPermission(message: CommandoMessage): boolean {
return true;
}
public async run(message: CommandoMessage, args: {} | string | string[]): Promise<Message | Message[]> {
return message.say('test');
}
}
client.login('aefsrgbr6t7u68i6t7ikjtz.sdfdsujhfisudhfsd.dufhsdufh8ehf8hw8ehf83h4thushdg');

500
node_modules/discord.js-commando/typings/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,500 @@
declare module 'discord.js-commando' {
import { Client, ClientEvents, ClientOptions, Collection, Guild, GuildResolvable, Message, MessageAttachment, MessageEditOptions, MessageEmbed, MessageOptions, MessageAdditions, MessageReaction, PermissionResolvable, PermissionString, StringResolvable, User, UserResolvable } from 'discord.js';
export class Argument {
private constructor(client: CommandoClient, info: ArgumentInfo);
private obtainInfinite(msg: CommandoMessage, vals?: string[], promptLimit?: number): Promise<ArgumentResult>;
private static validateInfo(client: CommandoClient, info: ArgumentInfo): void;
public default: ArgumentDefault;
public emptyChecker: Function;
public error: string;
public infinite: boolean;
public key: string;
public label: string;
public max: number;
public min: number;
public oneOf: string[];
public parser: Function;
public prompt: string;
public type: ArgumentType;
public validator: Function;
public wait: number;
public isEmpty(val: string, msg: CommandoMessage): boolean;
public obtain(msg: CommandoMessage, val?: string, promptLimit?: number): Promise<ArgumentResult>;
public parse(val: string, msg: CommandoMessage): any | Promise<any>;
public validate(val: string, msg: CommandoMessage): boolean | string | Promise<boolean | string>;
}
export class ArgumentCollector {
public constructor(client: CommandoClient, args: ArgumentInfo[], promptLimit?: number);
public args: Argument[];
public readonly client: CommandoClient;
public promptLimit: number;
public obtain(msg: CommandoMessage, provided?: any[], promptLimit?: number): Promise<ArgumentCollectorResult>;
}
export abstract class ArgumentType {
public constructor(client: CommandoClient, id: string);
public readonly client: CommandoClient;
public id: string;
public isEmpty(val: string, msg: CommandoMessage, arg: Argument): boolean;
public abstract parse(val: string, msg: CommandoMessage, arg: Argument): any | Promise<any>;
public abstract validate(val: string, msg: CommandoMessage, arg: Argument): boolean | string | Promise<boolean | string>;
}
export class ArgumentUnionType extends ArgumentType {
public types: ArgumentType[];
public parse(val: string, msg: CommandoMessage, arg: Argument): any | Promise<any>;
public validate(val: string, msg: CommandoMessage, arg: Argument): string | boolean | Promise<string | boolean>;
}
export abstract class Command {
public constructor(client: CommandoClient, info: CommandInfo);
private _globalEnabled: boolean;
private _throttles: Map<string, object>;
private throttle(userID: string): object;
private static validateInfo(client: CommandoClient, info: CommandInfo);
public aliases: string[];
public argsCollector: ArgumentCollector;
public argsCount: number;
public argsSingleQuotes: boolean;
public argsType: string;
public readonly client: CommandoClient;
public clientPermissions: PermissionResolvable[];
public defaultHandling: boolean;
public description: string;
public details: string;
public examples: string[];
public format: string;
public group: CommandGroup;
public groupID: string;
public guarded: boolean;
public guildOnly: boolean;
public hidden: boolean;
public memberName: string;
public name: string;
public nsfw: boolean;
public ownerOnly: boolean;
public patterns: RegExp[];
public throttling: ThrottlingOptions;
public unknown: boolean;
public userPermissions: PermissionResolvable[];
public hasPermission(message: CommandoMessage, ownerOverride?: boolean): boolean | string;
public isEnabledIn(guild: GuildResolvable, bypassGroup?: boolean): boolean;
public isUsable(message?: Message): boolean;
public onBlock(message: CommandoMessage, reason: string, data?: object): Promise<Message | Message[]>;
public onBlock(message: CommandoMessage, reason: 'guildOnly' | 'nsfw'): Promise<Message | Message[]>;
public onBlock(message: CommandoMessage, reason: 'permission', data: { response?: string }): Promise<Message | Message[]>;
public onBlock(message: CommandoMessage, reason: 'clientPermissions', data: { missing: PermissionString[] }): Promise<Message | Message[]>;
public onBlock(message: CommandoMessage, reason: 'throttling', data: { throttle: object, remaining: number }): Promise<Message | Message[]>;
public onError(err: Error, message: CommandoMessage, args: object | string | string[], fromPattern: false, result?: ArgumentCollectorResult): Promise<Message | Message[]>;
public onError(err: Error, message: CommandoMessage, args: string[], fromPattern: true, result?: ArgumentCollectorResult): Promise<Message | Message[]>;
public reload(): void;
public abstract run(message: CommandoMessage, args: object | string | string[], fromPattern: boolean, result?: ArgumentCollectorResult): Promise<Message | Message[] | null> | null;
public setEnabledIn(guild: GuildResolvable, enabled: boolean): void;
public unload(): void;
public usage(argString?: string, prefix?: string, user?: User): string;
public static usage(command: string, prefix?: string, user?: User): string;
}
export class CommandDispatcher {
public constructor(client: CommandoClient, registry: CommandoRegistry);
private _awaiting: Set<string>;
private _commandPatterns: object;
private _results: Map<string, CommandoMessage>;
private buildCommandPattern(prefix: string): RegExp;
private cacheCommandoMessage(message: Message, oldMessage: Message, cmdMsg: CommandoMessage, responses: Message | Message[]): void;
private handleMessage(message: Message, oldMessage?: Message): Promise<void>;
private inhibit(cmdMsg: CommandoMessage): Inhibition;
private matchDefault(message: Message, pattern: RegExp, commandNameIndex?: number, prefixless?: boolean): CommandoMessage;
private parseMessage(message: Message): CommandoMessage;
private shouldHandleMessage(message: Message, oldMessage?: Message): boolean;
public readonly client: CommandoClient;
public inhibitors: Set<Function>;
public registry: CommandoRegistry;
public addInhibitor(inhibitor: Inhibitor): boolean;
public removeInhibitor(inhibitor: Inhibitor): boolean;
}
export class CommandFormatError extends FriendlyError {
public constructor(msg: CommandoMessage);
}
export class CommandGroup {
public constructor(client: CommandoClient, id: string, name?: string, guarded?: boolean);
public readonly client: CommandoClient;
public commands: Collection<string, Command>
public guarded: boolean;
public id: string;
public name: string;
public isEnabledIn(guild: GuildResolvable): boolean;
public reload(): void;
public setEnabledIn(guild: GuildResolvable, enabled: boolean): void;
}
export class CommandoMessage extends Message {
public argString: string | null;
public command: Command | null;
public isCommand: boolean;
public patternMatches: string[] | null;
public responsePositions: { [key: string]: number } | null;
public responses: { [key: string]: CommandoMessage[] } | null;
public readonly guild: CommandoGuild;
private deleteRemainingResponses(): void;
private editCurrentResponse(id: string, options: MessageEditOptions | Exclude<MessageAdditions, MessageAttachment>): Promise<CommandoMessage | CommandoMessage[]>;
private editResponse(response: CommandoMessage | CommandoMessage[], options: RespondEditOptions): Promise<CommandoMessage | CommandoMessage[]>;
private finalize(responses: (CommandoMessage | CommandoMessage[])[]): void;
private respond(options: RespondOptions): Promise<CommandoMessage | CommandoMessage[]>;
public anyUsage(argString?: string, prefix?: string, user?: User): string;
public code: CommandoMessage['say'];
public direct: CommandoMessage['say'];
public embed(embed: MessageEmbed, content?: StringResolvable, options?: (MessageOptions & { split?: false }) | MessageAdditions): Promise<CommandoMessage>;
public embed(embed: MessageEmbed, content?: StringResolvable, options?: (MessageOptions & { split: true | Exclude<MessageOptions['split'], boolean> }) | MessageAdditions): Promise<CommandoMessage[]>;
public initCommand(command?: Command, argString?: string[], patternMatches?: string[]): this;
public parseArgs(): string | string[];
public replyEmbed: CommandoMessage['embed'];
public run(): Promise<null | CommandoMessage | CommandoMessage[]>;
public say(
content: StringResolvable | (MessageOptions & { split?: false }) | MessageAdditions,
options?: (MessageOptions & { split?: false }) | MessageAdditions
): Promise<CommandoMessage>;
public say(
content: StringResolvable | (MessageOptions & { split: true | Exclude<MessageOptions['split'], boolean> }) | MessageAdditions,
options?: (MessageOptions & { split: true | Exclude<MessageOptions['split'], boolean> }) | MessageAdditions
): Promise<CommandoMessage[]>;
public usage(argString?: string, prefix?: string, user?: User): string;
public static parseArgs(argString: string, argCount?: number, allowSingleQuote?: boolean): string[];
}
export class CommandoClient extends Client {
public constructor(options?: CommandoClientOptions);
private _commandPrefix: string;
public commandPrefix: string;
public dispatcher: CommandDispatcher;
public options: CommandoClientOptions;
public readonly owners: User[];
public provider: SettingProvider;
public registry: CommandoRegistry;
public settings: GuildSettingsHelper;
public isOwner(user: UserResolvable): boolean;
public setProvider(provider: SettingProvider | Promise<SettingProvider>): Promise<void>;
public on<K extends keyof CommandoClientEvents>(event: K, listener: (...args: CommandoClientEvents[K]) => void): this;
public once<K extends keyof CommandoClientEvents>(event: K, listener: (...args: CommandoClientEvents[K]) => void): this;
public emit<K extends keyof CommandoClientEvents>(event: K, ...args: CommandoClientEvents[K]): boolean;
}
export { CommandoClient as Client };
export class CommandoGuild extends Guild {
private _commandPrefix: string;
private _commandsEnabled: object;
private _groupsEnabled: object;
private _settings: GuildSettingsHelper;
public commandPrefix: string;
public readonly settings: GuildSettingsHelper;
public commandUsage(command?: string, user?: User): string;
public isCommandEnabled(command: CommandResolvable): boolean;
public isGroupEnabled(group: CommandGroupResolvable): boolean;
public setCommandEnabled(command: CommandResolvable, enabled: boolean): void;
public setGroupEnabled(group: CommandGroupResolvable, enabled: boolean): void;
}
export class CommandoRegistry {
public constructor(client?: CommandoClient);
public readonly client: CommandoClient;
public commands: Collection<string, Command>;
public commandsPath: string;
public groups: Collection<string, CommandGroup>;
public types: Collection<string, ArgumentType>;
public unknownCommand?: Command;
public findCommands(searchString?: string, exact?: boolean, message?: Message | CommandoMessage): Command[];
public findGroups(searchString?: string, exact?: boolean): CommandGroup[];
public registerCommand(command: Command | Function): CommandoRegistry;
public registerCommands(commands: Command[] | Function[], ignoreInvalid?: boolean): CommandoRegistry;
public registerCommandsIn(options: string | {}): CommandoRegistry;
public registerDefaultCommands(commands?: DefaultCommandsOptions): CommandoRegistry;
public registerDefaultGroups(): CommandoRegistry;
public registerDefaults(): CommandoRegistry;
public registerDefaultTypes(types?: DefaultTypesOptions): CommandoRegistry;
public registerGroup(group: CommandGroup | Function | { id: string, name?: string, guarded?: boolean } | string, name?: string, guarded?: boolean): CommandoRegistry;
public registerGroups(groups: CommandGroup[] | Function[] | { id: string, name?: string, guarded?: boolean }[] | string[][]): CommandoRegistry;
public registerType(type: ArgumentType | Function): CommandoRegistry;
public registerTypes(type: ArgumentType[] | Function[], ignoreInvalid?: boolean): CommandoRegistry;
public registerTypesIn(options: string | {}): CommandoRegistry;
public reregisterCommand(command: Command | Function, oldCommand: Command): void;
public resolveCommand(command: CommandResolvable): Command;
public resolveCommandPath(group: string, memberName: string): string;
public resolveGroup(group: CommandGroupResolvable): CommandGroup;
public unregisterCommand(command: Command): void;
}
export class FriendlyError extends Error {
public constructor(message: string);
}
export class GuildSettingsHelper {
public constructor(client: CommandoClient, guild: CommandoGuild);
public readonly client: CommandoClient;
public guild: CommandoGuild;
public clear(): Promise<void>;
public get(key: string, defVal?: any): any;
public remove(key: string): Promise<any>;
public set(key: string, val: any): Promise<any>;
}
export abstract class SettingProvider {
public abstract clear(guild: Guild | string): Promise<void>;
public abstract destroy(): Promise<void>;
public abstract get(guild: Guild | string, key: string, defVal?: any): any;
public abstract init(client: CommandoClient): Promise<void>;
public abstract remove(guild: Guild | string, key: string): Promise<any>;
public abstract set(guild: Guild | string, key: string, val: any): Promise<any>;
public static getGuildID(guild: Guild | string): string;
}
export class SQLiteProvider extends SettingProvider {
public constructor(db: any | Promise<any>);
public readonly client: CommandoClient;
public db: any;
private deleteStmt: any;
private insertOrReplaceStmt: any;
private listeners: Map<any, any>;
private settings: Map<any, any>;
public clear(guild: Guild | string): Promise<void>;
public destroy(): Promise<void>;
public get(guild: Guild | string, key: string, defVal?: any): any;
public init(client: CommandoClient): Promise<void>;
public remove(guild: Guild | string, key: string): Promise<any>;
public set(guild: Guild | string, key: string, val: any): Promise<any>;
private setupGuild(guild: string, settings: {}): void;
private setupGuildCommand(guild: CommandoGuild, command: Command, settings: {}): void;
private setupGuildGroup(guild: CommandoGuild, group: CommandGroup, settings: {}): void;
private updateOtherShards(key: string, val: any): void;
}
export class SyncSQLiteProvider extends SettingProvider {
public constructor(db: any | Promise<any>);
public readonly client: CommandoClient;
public db: any;
private deleteStmt: any;
private insertOrReplaceStmt: any;
private listeners: Map<any, any>;
private settings: Map<any, any>;
public clear(guild: Guild | string): Promise<void>;
public destroy(): Promise<void>;
public get(guild: Guild | string, key: string, defVal?: any): any;
public init(client: CommandoClient): Promise<void>;
public remove(guild: Guild | string, key: string): Promise<any>;
public set(guild: Guild | string, key: string, val: any): Promise<any>;
private setupGuild(guild: string, settings: {}): void;
private setupGuildCommand(guild: CommandoGuild, command: Command, settings: {}): void;
private setupGuildGroup(guild: CommandoGuild, group: CommandGroup, settings: {}): void;
private updateOtherShards(key: string, val: any): void;
}
export class util {
public static disambiguation(items: any[], label: string, property?: string): string;
public static escapeRegex(str: string): string;
public static paginate<T>(items: T[], page?: number, pageLength?: number): {
items: T[],
page: number,
maxPage: number,
pageLength: number
};
public static readonly permissions: { [K in PermissionString]: string };
}
export const version: string;
export interface ArgumentCollectorResult<T = object> {
values: T | null;
cancelled?: 'user' | 'time' | 'promptLimit';
prompts: Message[];
answers: Message[];
}
type ArgumentDefault = any | Function;
export interface ArgumentInfo {
key: string;
label?: string;
prompt: string;
error?: string;
type?: string;
max?: number;
min?: number;
oneOf?: string[];
default?: ArgumentDefault;
infinite?: boolean;
validate?: Function;
parse?: Function;
isEmpty?: Function;
wait?: number;
}
export interface ArgumentResult {
value: any | any[];
cancelled?: 'user' | 'time' | 'promptLimit';
prompts: Message[];
answers: Message[];
}
type CommandGroupResolvable = CommandGroup | string;
export interface CommandInfo {
name: string;
aliases?: string[];
autoAliases?: boolean;
group: string;
memberName: string;
description: string;
format?: string;
details?: string;
examples?: string[];
nsfw?: boolean;
guildOnly?: boolean;
ownerOnly?: boolean;
clientPermissions?: PermissionResolvable[];
userPermissions?: PermissionResolvable[];
defaultHandling?: boolean;
throttling?: ThrottlingOptions;
args?: ArgumentInfo[];
argsPromptLimit?: number;
argsType?: string;
argsCount?: number;
argsSingleQuotes?: boolean;
patterns?: RegExp[];
guarded?: boolean;
hidden?: boolean;
unknown?: boolean;
}
interface CommandoClientEvents extends ClientEvents {
commandBlock:
| [CommandoMessage, string, object?]
| [CommandoMessage, 'guildOnly' | 'nsfw']
| [CommandoMessage, 'permission', { response?: string }]
| [CommandoMessage, 'throttling', { throttle: object, remaining: number }]
| [CommandoMessage, 'clientPermissions', { missing: string }];
commandCancel: [Command, string, CommandoMessage];
commandError:
| [Command, Error, CommandoMessage, object | string | string[], false]
| [Command, Error, CommandoMessage, string[], true];
commandPrefixChange: [CommandoGuild, string];
commandRegister: [Command, CommandoRegistry];
commandReregister: [Command, Command];
commandRun: [Command, Promise<any>, CommandoMessage, object | string | string[], boolean];
commandStatusChange: [CommandoGuild, Command, boolean];
commandUnregister: [Command];
groupRegister: [CommandGroup, CommandoRegistry];
groupStatusChange: [CommandoGuild, CommandGroup, boolean];
typeRegister: [ArgumentType, CommandoRegistry];
unknownCommand: [CommandoMessage];
providerReady: [SettingProvider];
}
export interface CommandoClientOptions extends ClientOptions {
commandPrefix?: string;
commandEditableDuration?: number;
nonCommandEditable?: boolean;
owner?: string | string[] | Set<string>;
invite?: string;
}
type CommandResolvable = Command | string;
interface DefaultCommandsOptions {
help?: boolean;
prefix?: boolean;
eval?: boolean;
ping?: boolean;
unknownCommand?: boolean;
commandState?: boolean;
}
interface DefaultTypesOptions {
string?: boolean;
integer?: boolean;
float?: boolean;
boolean?: boolean;
user?: boolean;
member?: boolean;
role?: boolean;
channel?: boolean;
textChannel?: boolean;
voiceChannel?: boolean;
categoryChannel?: boolean;
message?: boolean;
customEmoji?: boolean;
defaultEmoji?: boolean;
command?: boolean;
group?: boolean;
}
type Inhibitor = (msg: CommandoMessage) => false | string | Inhibition;
export interface Inhibition {
reason: string;
response?: Promise<Message>;
}
export interface ThrottlingOptions {
usages: number;
duration: number;
}
type ResponseType = 'reply' | 'plain' | 'direct' | 'code';
interface RespondOptions {
content: StringResolvable | MessageOptions;
fromEdit?: boolean;
options?: MessageOptions;
lang?: string;
type?: ResponseType;
}
interface RespondEditOptions {
content: StringResolvable | MessageEditOptions | Exclude<MessageAdditions, MessageAttachment>;
options?: MessageEditOptions | Exclude<MessageAdditions, MessageAttachment>;
type?: ResponseType;
}
}