├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .travis.yml
├── .vscode
├── launch.json
└── tasks.json
├── LICENSE
├── README.md
├── docs
├── .nojekyll
├── CNAME
├── assets
│ ├── css
│ │ ├── main.css
│ │ └── main.css.map
│ ├── images
│ │ ├── icons.png
│ │ ├── icons@2x.png
│ │ ├── widgets.png
│ │ └── widgets@2x.png
│ └── js
│ │ ├── main.js
│ │ └── search.js
├── classes
│ ├── _core_client_.handlesclient.html
│ ├── _core_client_.handlesclient.internal.eventemitter.html
│ ├── _core_commandhandler_.commandhandler.html
│ ├── _core_commandregistry_.commandregistry.html
│ ├── _errors_argumenterror_.argumenterror.html
│ ├── _errors_baseerror_.baseerror.html
│ ├── _errors_validationerror_.validationerror.html
│ ├── _middleware_argument_.argument.html
│ ├── _middleware_validator_.validator.html
│ ├── _structures_command_.command.html
│ ├── _structures_response_.response.html
│ └── _util_queue_.queue.html
├── globals.html
├── index.html
├── interfaces
│ ├── _interfaces_config_.iconfig.html
│ ├── _middleware_argument_.ioptions.html
│ ├── _structures_command_.icommandoptions.html
│ └── _structures_response_.iresponseoptions.html
└── modules
│ ├── _core_client_.handlesclient.internal.html
│ ├── _core_client_.html
│ ├── _core_commandhandler_.html
│ ├── _core_commandregistry_.html
│ ├── _errors_argumenterror_.html
│ ├── _errors_baseerror_.html
│ ├── _errors_validationerror_.html
│ ├── _index_.html
│ ├── _interfaces_config_.html
│ ├── _middleware_argument_.html
│ ├── _middleware_validator_.html
│ ├── _structures_command_.html
│ ├── _structures_response_.html
│ └── _util_queue_.html
├── examples
├── commands
│ ├── ayy.js
│ ├── ban.js
│ └── ping.js
└── index.js
├── gulpfile.js
├── package-lock.json
├── package.json
├── src
├── core
│ ├── Client.ts
│ ├── CommandHandler.ts
│ └── CommandRegistry.ts
├── errors
│ ├── ArgumentError.ts
│ ├── BaseError.ts
│ └── ValidationError.ts
├── index.ts
├── interfaces
│ └── Config.ts
├── middleware
│ ├── Argument.ts
│ └── Validator.ts
├── structures
│ ├── Command.ts
│ └── Response.ts
└── util
│ └── Queue.ts
├── test
├── commands
│ ├── add.js
│ ├── dm.js
│ ├── error.js
│ ├── eval.js
│ ├── invalid.js
│ ├── ping.js
│ └── spam.js
└── index.js
├── tsconfig.json
└── tslint.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "es6": true
5 | },
6 | "extends": "eslint:recommended",
7 | "parserOptions": {
8 | "sourceType": "module",
9 | "ecmaVersion": 2017
10 | },
11 | "rules": {
12 | "indent": ["error", 2, { "SwitchCase": 1 }],
13 | "linebreak-style": ["error", "unix"],
14 | "quotes": ["error", "single"],
15 | "semi": ["error", "always"],
16 | "brace-style": ["error", "1tbs"],
17 | "no-multi-spaces": "error",
18 | "no-unused-vars": "warn",
19 | "no-console": "warn",
20 | "eqeqeq": ["warn", "always"],
21 | "no-invalid-this": "warn",
22 | "require-await": "error",
23 | "block-spacing": ["error", "always"],
24 | "prefer-const": ["warn", { "destructuring": "any" }],
25 | "keyword-spacing": "error",
26 | "object-curly-spacing": ["error", "always"],
27 | "prefer-template": "warn"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.idea
3 | /test/.env
4 | /dist
5 | /typings
6 | .DS_Store
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /src
2 | /test
3 | /docs
4 | /examples
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | - "7"
5 | - "8"
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible Node.js debug attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Program",
11 | "program": "${workspaceRoot}/test/index.js",
12 | "preLaunchTask": "compile",
13 | "sourceMaps": true
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "taskName": "compile",
8 | "type": "shell",
9 | "command": "gulp"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 William Nelson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Handles
2 |
3 | [](https://discord.gg/DPuaDvP)
4 | [](https://travis-ci.org/appellation/handles)
5 | 
6 |
7 | For those of us who get frustrated with writing command handlers but don't quite want to use a full framework. Intended for use with [Discord.js](https://github.com/hydrabolt/discord.js).
8 |
9 | Documentation is available at [handles.topkek.pw](http://handles.topkek.pw).
10 |
11 | ## Getting started
12 |
13 | ### Installation
14 |
15 | ```xl
16 | npm install --save discord-handles
17 | ```
18 |
19 | Or, if you want to risk cutting yourself, install the bleeding edge version:
20 |
21 | ```xl
22 | npm install --save appellation/handles#master
23 | ```
24 |
25 | Usually I try to avoid pushing broken code, but sometimes I move a little too fast.
26 |
27 | ### The basics
28 |
29 | ```js
30 | const discord = require('discord.js');
31 | const handles = require('discord-handles');
32 |
33 | const client = new discord.Client();
34 | const handler = new handles.Client(client);
35 |
36 | client.login('token');
37 | ```
38 |
39 | This will automatically load all commands in the `./commands` directory and handle incoming messages. See [`Command`](https://handles.topkek.pw/modules/_structures_command_.html) in the docs for information on how to format the exports of the files you place in `./commands`. Particularly of interest are the `pre`, `exec`, and `post` methods. The loader and handler can be configured according to [`Config`](https://handles.topkek.pw/modules/_interfaces_config_.html) options passed to the constructor.
40 |
41 | ```js
42 | const handler = new handles.Client(client, {
43 | directory: './some/other/awesome/directory',
44 | prefixes: new Set(['dank', 'memes'])
45 | });
46 | ```
47 |
48 | Here's an example of what you might place in the `./commands` directory.
49 | ```js
50 | const { MessageMentions, Permissions } = require('discord.js');
51 | const { Command, Argument, Validator } = require('discord-handles');
52 |
53 | module.exports = class extends Command {
54 | static get triggers() {
55 | return ['banne', 'ban'];
56 | }
57 |
58 | async pre() {
59 | await this.guild.fetchMembers();
60 |
61 | await new Validator(this)
62 | .apply(this.guild.me.permissions.has(Permissions.FLAGS.BAN_MEMBERS), 'I don\'t have permission to ban people.')
63 | .apply(this.member.permissions.has(Permissions.FLAGS.BAN_MEMBERS), 'You don\'t have permission to ban people.');
64 |
65 | const member = await new Argument(this, 'member')
66 | .setResolver(c => {
67 | const member = this.guild.members.get(c);
68 |
69 | // if they provided a raw user ID
70 | if (member) return member;
71 | // if they mentioned someone
72 | else if (MessageMentions.USERS_PATTERN.test(c)) return this.guild.members.get(c.match(MessageMentions.USERS_PATTERN)[1]);
73 | // if they provided a user tag
74 | else if (this.guild.members.exists(u => u.tag === c)) return this.guild.members.find(u => u.tag === c);
75 | else return null;
76 | })
77 | .setPrompt('Who would you like to ban?')
78 | .setRePrompt('You provided an invalid user. Please try again.');
79 |
80 | await new Validator(this)
81 | .apply(member.bannable, 'I cannot ban this person.');
82 | .apply(member.highestRole.position < this.member.highestRole.position, 'You cannot ban this person.')
83 |
84 | await new Argument(this, 'days')
85 | .setResolver(c => parseInt(c) || null);
86 | .setOptional();
87 | }
88 |
89 | async exec() {
90 | await this.args.member.ban(this.args.days);
91 | return this.response.success(`banned ${this.args.member.user.tag}`);
92 | }
93 | };
94 | ```
95 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appellation/handles/807e3af14e6a6b1739e69ffa69432d8144671a13/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | handles.topkek.pw
--------------------------------------------------------------------------------
/docs/assets/images/icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appellation/handles/807e3af14e6a6b1739e69ffa69432d8144671a13/docs/assets/images/icons.png
--------------------------------------------------------------------------------
/docs/assets/images/icons@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appellation/handles/807e3af14e6a6b1739e69ffa69432d8144671a13/docs/assets/images/icons@2x.png
--------------------------------------------------------------------------------
/docs/assets/images/widgets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appellation/handles/807e3af14e6a6b1739e69ffa69432d8144671a13/docs/assets/images/widgets.png
--------------------------------------------------------------------------------
/docs/assets/images/widgets@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appellation/handles/807e3af14e6a6b1739e69ffa69432d8144671a13/docs/assets/images/widgets@2x.png
--------------------------------------------------------------------------------
/docs/assets/js/search.js:
--------------------------------------------------------------------------------
1 | var typedoc = typedoc || {};
2 | typedoc.search = typedoc.search || {};
3 | typedoc.search.data = {"kinds":{"1":"External module","65536":"Type literal"},"rows":[{"id":0,"kind":65536,"name":"__type","url":"modules/_core_commandregistry_.html#readdir.__type","classes":"tsd-kind-type-literal tsd-parent-kind-variable tsd-is-not-exported","parent":"\"core/CommandRegistry\".readdir"},{"id":1,"kind":65536,"name":"__type","url":"modules/_core_commandregistry_.html#stat.__type-1","classes":"tsd-kind-type-literal tsd-parent-kind-variable tsd-is-not-exported","parent":"\"core/CommandRegistry\".stat"},{"id":2,"kind":65536,"name":"__type","url":"modules/_core_commandhandler_.html#messagevalidator.__type","classes":"tsd-kind-type-literal tsd-parent-kind-type-alias tsd-is-not-exported","parent":"\"core/CommandHandler\".MessageValidator"},{"id":3,"kind":65536,"name":"__type","url":"modules/_util_queue_.html#queuefunction.__type","classes":"tsd-kind-type-literal tsd-parent-kind-type-alias tsd-is-not-exported","parent":"\"util/Queue\".QueueFunction"},{"id":4,"kind":65536,"name":"__type","url":"modules/_structures_response_.html#send.__type","classes":"tsd-kind-type-literal tsd-parent-kind-type-alias tsd-is-not-exported","parent":"\"structures/Response\".Send"},{"id":5,"kind":65536,"name":"__type","url":"modules/_middleware_validator_.html#validationfunction.__type","classes":"tsd-kind-type-literal tsd-parent-kind-type-alias tsd-is-not-exported","parent":"\"middleware/Validator\".ValidationFunction"},{"id":6,"kind":65536,"name":"__type","url":"modules/_middleware_argument_.html#resolver.__type-1","classes":"tsd-kind-type-literal tsd-parent-kind-type-alias tsd-is-not-exported","parent":"\"middleware/Argument\".Resolver"},{"id":7,"kind":65536,"name":"__type","url":"modules/_middleware_argument_.html#matcher.__type","classes":"tsd-kind-type-literal tsd-parent-kind-type-alias tsd-is-not-exported","parent":"\"middleware/Argument\".Matcher"},{"id":8,"kind":1,"name":"\"index\"","url":"modules/_index_.html","classes":"tsd-kind-external-module"}]};
--------------------------------------------------------------------------------
/docs/globals.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
59 |
discord-handles
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Index
68 |
69 |
70 |
71 | External modules
72 |
87 |
88 |
89 |
90 |
91 |
92 |
150 |
151 |
152 |
211 |
212 |
Generated using TypeDoc
213 |
214 |
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/docs/interfaces/_structures_command_.icommandoptions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ICommandOptions | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
65 |
Interface ICommandOptions
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | Hierarchy
74 |
75 | -
76 | ICommandOptions
77 |
78 |
79 |
80 |
81 | Index
82 |
83 |
84 |
85 | Properties
86 |
91 |
92 |
93 |
94 |
95 |
96 | Properties
97 |
98 |
99 | body
100 | body: string
101 |
106 |
107 |
108 |
109 | message
110 | message: Message
111 |
116 |
117 |
118 |
119 | trigger
120 |
121 |
126 |
127 |
128 |
129 |
211 |
212 |
213 |
272 |
273 |
Generated using TypeDoc
274 |
275 |
276 |
277 |
278 |
279 |
--------------------------------------------------------------------------------
/docs/modules/_core_client_.handlesclient.internal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | internal | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
68 |
Module internal
69 |
70 |
71 |
72 |
107 |
166 |
167 |
Generated using TypeDoc
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/docs/modules/_core_client_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "core/Client" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "core/Client"
63 |
64 |
65 |
66 |
146 |
205 |
206 |
Generated using TypeDoc
207 |
208 |
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/docs/modules/_core_commandhandler_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "core/CommandHandler" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "core/CommandHandler"
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Index
71 |
72 |
73 |
79 |
80 | Type aliases
81 |
84 |
85 |
86 |
87 |
88 |
89 | Type aliases
90 |
91 |
92 | MessageValidator
93 | MessageValidator: function
94 |
99 |
100 |
Type declaration
101 |
102 | -
103 |
104 | - (m: Message): string | null | Promise<string | null>
105 |
106 |
107 | -
108 |
Parameters
109 |
110 | -
111 |
m: Message
112 |
113 |
114 | Returns string
115 | |
116 | null
117 | |
118 | Promise<string | null>
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
192 |
193 |
194 |
253 |
254 |
Generated using TypeDoc
255 |
256 |
257 |
258 |
259 |
260 |
--------------------------------------------------------------------------------
/docs/modules/_errors_argumenterror_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "errors/ArgumentError" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "errors/ArgumentError"
63 |
64 |
65 |
66 |
146 |
205 |
206 |
Generated using TypeDoc
207 |
208 |
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/docs/modules/_errors_baseerror_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "errors/BaseError" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "errors/BaseError"
63 |
64 |
65 |
66 |
146 |
205 |
206 |
Generated using TypeDoc
207 |
208 |
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/docs/modules/_errors_validationerror_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "errors/ValidationError" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "errors/ValidationError"
63 |
64 |
65 |
66 |
146 |
205 |
206 |
Generated using TypeDoc
207 |
208 |
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/docs/modules/_index_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "index" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "index"
63 |
64 |
65 |
66 |
130 |
189 |
190 |
Generated using TypeDoc
191 |
192 |
193 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/docs/modules/_interfaces_config_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "interfaces/Config" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "interfaces/Config"
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Index
71 |
72 |
73 |
74 | Interfaces
75 |
78 |
79 |
80 |
81 |
82 |
83 |
144 |
145 |
146 |
205 |
206 |
Generated using TypeDoc
207 |
208 |
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/docs/modules/_middleware_validator_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "middleware/Validator" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "middleware/Validator"
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Index
71 |
72 |
73 |
79 |
80 | Type aliases
81 |
84 |
85 |
86 |
87 |
88 |
89 | Type aliases
90 |
91 |
92 | ValidationFunction
93 | ValidationFunction: function
94 |
99 |
107 |
108 |
Type declaration
109 |
110 | -
111 |
114 |
115 | -
116 |
Parameters
117 |
122 | Returns boolean
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
195 |
196 |
197 |
256 |
257 |
Generated using TypeDoc
258 |
259 |
260 |
261 |
262 |
263 |
--------------------------------------------------------------------------------
/docs/modules/_structures_command_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "structures/Command" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "structures/Command"
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Index
71 |
72 |
73 |
79 |
80 | Interfaces
81 |
84 |
85 |
86 | Type aliases
87 |
90 |
91 |
92 |
93 |
94 |
95 | Type aliases
96 |
97 |
98 | Trigger
99 | Trigger: string | RegExp
100 |
105 |
106 |
107 |
108 |
175 |
176 |
177 |
236 |
237 |
Generated using TypeDoc
238 |
239 |
240 |
241 |
242 |
243 |
--------------------------------------------------------------------------------
/docs/modules/_util_queue_.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | "util/Queue" | discord-handles
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | - Preparing search index...
23 | - The search index is not available
24 |
25 |
discord-handles
26 |
27 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
External module "util/Queue"
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Index
71 |
72 |
73 |
79 |
80 | Type aliases
81 |
84 |
85 |
86 |
87 |
88 |
89 | Type aliases
90 |
91 |
92 | QueueFunction
93 | QueueFunction: function
94 |
99 |
100 |
Type declaration
101 |
102 | -
103 |
104 | - (...args: any[]): Promise<void>
105 |
106 |
107 | -
108 |
Parameters
109 |
110 | -
111 |
Rest ...args: any[]
112 |
113 |
114 | Returns Promise<void>
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
187 |
188 |
189 |
248 |
249 |
Generated using TypeDoc
250 |
251 |
252 |
253 |
254 |
255 |
--------------------------------------------------------------------------------
/examples/commands/ayy.js:
--------------------------------------------------------------------------------
1 | exports.exec = cmd => cmd.response.send('lmao');
2 | exports.triggers = /^ay+$/i;
3 |
--------------------------------------------------------------------------------
/examples/commands/ban.js:
--------------------------------------------------------------------------------
1 | exports.exec = (command) => {
2 | return command.args.member.ban();
3 | };
4 |
5 | exports.validator = (validator, command) => {
6 | // const mention = /<@!?([0-9]+)>/;
7 | return validator.apply(
8 | command.message.channel.type === 'text',
9 | 'This command cannot be run outside of a guild.'
10 | ) &&
11 | validator.apply(
12 | command.message.member.hasPermission('BAN_MEMBERS'),
13 | 'You do not have permission to run this command.'
14 | ) &&
15 | validator.apply(
16 | command.message.guild.me.permissions.has('BAN_MEMBERS'),
17 | 'I cannot ban members in this guild.'
18 | );
19 | };
20 |
21 | exports.arguments = function* (Argument) {
22 | const member = new Argument('member')
23 | .setPrompt('Who would you like to ban?')
24 | .setRePrompt('Please provide a valid member.')
25 | .setResolver((c, msg) => {
26 | if (!msg.mentions.users.size) return null;
27 |
28 | let toBan = msg.mentions.users.filter(u => u.id !== u.client.user.id);
29 | if (toBan.size > 1) {
30 | member.rePrompt = `Found multiple users: \`${toBan.map(u => `${u.username}#${u.discriminator}`).join(', ')}\``;
31 | return null;
32 | }
33 | if (toBan.size < 1) return null;
34 |
35 | toBan = toBan.first();
36 | if (!toBan.bannable) return null;
37 | return msg.guild.member(toBan) || null;
38 | });
39 |
40 | yield member;
41 | };
42 |
43 | exports.triggers = [
44 | 'ban',
45 | 'banne',
46 | 'b&',
47 | '🔨'
48 | ];
49 |
--------------------------------------------------------------------------------
/examples/commands/ping.js:
--------------------------------------------------------------------------------
1 | exports.exec = command => command.response.success('pong');
2 |
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | const discord = require('discord.js');
2 | const handles = require('../src/index');
3 |
4 | const client = new discord.Client();
5 | client.once('ready', () => {
6 | const handler = new handles.Client({
7 | // config options go here
8 | });
9 | client.on('message', handler.handle);
10 | });
11 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const del = require('del');
3 | const ts = require('gulp-typescript');
4 | const sourcemaps = require('gulp-sourcemaps');
5 | const typedoc = require('gulp-typedoc');
6 | const project = ts.createProject('tsconfig.json');
7 |
8 | gulp.task('default', ['build']);
9 |
10 | gulp.task('build', () => {
11 | del.sync(['dist/**', '!dist']);
12 | del.sync(['typings/**', '!typings']);
13 |
14 | const result = project.src()
15 | .pipe(sourcemaps.init())
16 | .pipe(project());
17 |
18 | result.dts.pipe(gulp.dest('typings'));
19 | result.js.pipe(sourcemaps.write('.', { sourceRoot: '../src' })).pipe(gulp.dest('dist'));
20 | });
21 |
22 | gulp.task('docs', () => {
23 | del.sync(['docs/**']);
24 | return gulp.src(['src/*.ts'])
25 | .pipe(typedoc({
26 | module: 'commonjs',
27 | target: 'es2017',
28 | out: './docs',
29 | }));
30 | });
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "discord-handles",
3 | "version": "7.3.5",
4 | "description": "A simple Discord command handler.",
5 | "main": "dist/index.js",
6 | "typings": "typings/index.d.ts",
7 | "scripts": {
8 | "prepare": "npm run build",
9 | "build": "gulp build",
10 | "test": "tslint --project tsconfig.json src/**",
11 | "docs": "gulp docs"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/appellation/handles.git"
16 | },
17 | "keywords": [
18 | "discord",
19 | "discord.js",
20 | "command handler"
21 | ],
22 | "author": "Will Nelson ",
23 | "license": "ISC",
24 | "bugs": {
25 | "url": "https://github.com/appellation/handles/issues"
26 | },
27 | "homepage": "https://github.com/appellation/handles#readme",
28 | "dependencies": {
29 | "tsubaki": "^1.2.0"
30 | },
31 | "devDependencies": {
32 | "@types/node": "^8.0.19",
33 | "del": "^3.0.0",
34 | "discord.js": "^11.2.1",
35 | "dotenv": "^4.0.0",
36 | "gulp": "^3.9.1",
37 | "gulp-sourcemaps": "^2.6.1",
38 | "gulp-typedoc": "^2.0.3",
39 | "gulp-typescript": "^3.2.1",
40 | "raven": "^2.1.0",
41 | "tslint": "^5.5.0",
42 | "typedoc": "^0.8.0",
43 | "typescript": "^2.4.1"
44 | },
45 | "peerDependencies": {
46 | "discord.js": "^11.1.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/core/Client.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter = require('events');
2 | import BaseError from '../errors/BaseError';
3 | import Validator from '../middleware/Validator';
4 |
5 | import Command from '../structures/Command';
6 | import Response from '../structures/Response';
7 |
8 | import CommandHandler from './CommandHandler';
9 | import CommandRegistry from './CommandRegistry';
10 |
11 | import { IConfig } from '../interfaces/Config';
12 |
13 | import { Client, Message } from 'discord.js';
14 |
15 | /**
16 | * The starting point for using handles.
17 | *
18 | * ```js
19 | * const discord = require('discord.js');
20 | * const handles = require('discord-handles');
21 | *
22 | * const client = new discord.Client();
23 | * const handler = new handles.Client();
24 | *
25 | * client.on('message', handler.handle);
26 | * client.login('token');
27 | * ```
28 | */
29 | export default class HandlesClient extends EventEmitter {
30 | public readonly registry: CommandRegistry;
31 | public readonly handler: CommandHandler;
32 |
33 | public Response: typeof Response;
34 | public argsSuffix?: string;
35 | public readonly prefixes: Set;
36 |
37 | constructor(client: Client, config: IConfig = {}) {
38 | super();
39 |
40 | this.Response = Response;
41 | this.argsSuffix = config.argsSuffix;
42 | this.prefixes = config.prefixes || new Set();
43 |
44 | this.registry = new CommandRegistry(this, config);
45 | this.handler = new CommandHandler(this, config);
46 |
47 | this.handle = this.handle.bind(this);
48 |
49 | client.once('ready', () => this.prefixes.add(`<@${client.user.id}>`).add(`<@!${client.user.id}>`));
50 | if (!('autoListen' in config) || !config.autoListen) client.on('message', this.handle);
51 | }
52 |
53 | /**
54 | * Handle a message as a command.
55 | *
56 | * ```js
57 | * const client = new discord.Client();
58 | * const handler = new handles.Client();
59 | *
60 | * client.on('message', handler.handle);
61 | *
62 | * // or
63 | *
64 | * const client = new discord.Client();
65 | * const handler = new handles.Client();
66 | *
67 | * client.on('message', message => {
68 | * // do other stuff
69 | * handler.handle(message);
70 | * });
71 | * ```
72 | */
73 | public async handle(msg: Message) {
74 | if (
75 | msg.webhookID ||
76 | msg.system ||
77 | msg.author.bot ||
78 | (!msg.client.user.bot && msg.author.id !== msg.client.user.id)
79 | ) return null;
80 |
81 | const cmd = await this.handler.resolve(msg);
82 | if (!cmd) {
83 | this.emit('commandUnknown', msg);
84 | return null;
85 | }
86 |
87 | return this.handler.exec(cmd);
88 | }
89 |
90 | public on(event: 'commandStarted' | 'commandUnknown', listener: (cmd: Command) => void): this;
91 |
92 | public on(event: 'commandError', listener:
93 | ({ command, error }: { command: Command, error: Error | BaseError }) => void): this;
94 |
95 | public on(event: 'commandFinished', listener:
96 | ({ command, result }: { command: Command, result: any }) => void): this;
97 |
98 | public on(event: 'commandFailed', listener:
99 | ({ command, error }: { command: Command, error: BaseError }) => void): this;
100 |
101 | public on(event: 'commandsLoaded', listener:
102 | ({ commands, failed, time }: { commands: Map, failed: string[], time: number }) => void): this;
103 |
104 | public on(event: string, listener: (...args: any[]) => void): this {
105 | return super.on(event, listener);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/core/CommandHandler.ts:
--------------------------------------------------------------------------------
1 | import BaseError from '../errors/BaseError';
2 |
3 | import Command from '../structures/Command';
4 |
5 | import HandlesClient from './Client';
6 | import CommandRegistry from './CommandRegistry';
7 |
8 | import { IConfig } from '../interfaces/Config';
9 |
10 | import { Message } from 'discord.js';
11 |
12 | export type MessageValidator = (m: Message) => string | null | Promise;
13 |
14 | /**
15 | * Class for handling a command.
16 | */
17 | export default class CommandHandler {
18 |
19 | /**
20 | * The client.
21 | */
22 | public readonly handles: HandlesClient;
23 |
24 | /**
25 | * Sessions to ignore.
26 | */
27 | public readonly ignore: string[] = [];
28 |
29 | /**
30 | * Whether the [[handle]] method should always resolve with void (use when relying on events to catch errors).
31 | */
32 | public silent: boolean;
33 |
34 | /**
35 | * The message validator from config.
36 | */
37 | public validator: MessageValidator;
38 |
39 | /**
40 | * Methods to run before each command. Executed in sequence before the command's `pre` method.
41 | * **Deprecated:** the command parameter will be removed.
42 | */
43 | public pre: Array<(this: Command, cmd: Command) => any> = [];
44 |
45 | /**
46 | * Methods to run after each command. Executed in sequence after the command's `post` method.
47 | * **Deprecated:** the command parameter will be removed.
48 | */
49 | public post: Array<(this: Command, cmd: Command) => any> = [];
50 |
51 | /**
52 | * Recently executed commands. Stored regardless of success or failure.
53 | */
54 | public executed: Command[] = [];
55 |
56 | constructor(handles: HandlesClient, config: IConfig) {
57 | this.handles = handles;
58 | this.silent = typeof config.silent === 'undefined' ? true : config.silent;
59 |
60 | if (
61 | typeof config.validator !== 'function' &&
62 | (!this.handles.prefixes || !this.handles.prefixes.size)
63 | ) throw new Error('Unable to validate commands: no validator or prefixes were provided.');
64 |
65 | this.validator = config.validator || ((message) => {
66 | for (const p of this.handles.prefixes) {
67 | if (message.content.startsWith(p)) {
68 | return message.content.substring(p.length).trim();
69 | }
70 | }
71 |
72 | return null;
73 | });
74 | }
75 |
76 | /**
77 | * Resolve a command from a message.
78 | */
79 | public async resolve(message: Message): Promise {
80 | const content = await this.validator(message);
81 | if (typeof content !== 'string' || !content) return null;
82 |
83 | const match = content.match(/^([^\s]+)(.*)/);
84 | if (match) {
85 | const [, cmd, commandContent] = match;
86 | const mod = this.handles.registry.get(cmd);
87 |
88 | if (mod) {
89 | return new mod(this.handles, {
90 | body: commandContent.trim(),
91 | message,
92 | trigger: cmd,
93 | });
94 | }
95 | }
96 |
97 | for (const [trigger, command] of this.handles.registry) {
98 | let body = null;
99 | if (trigger instanceof RegExp) {
100 | const match = content.match(trigger);
101 | if (match) body = match[0].trim();
102 | } else if (typeof trigger === 'string') {
103 | // if the trigger is lowercase, make the command case-insensitive
104 | if ((trigger.toLowerCase() === trigger ? content.toLowerCase() : content).startsWith(trigger)) {
105 | body = content.substring(trigger.length).trim();
106 | }
107 | }
108 |
109 | if (body !== null) {
110 | const cmd = new command(this.handles, {
111 | body,
112 | message,
113 | trigger,
114 | });
115 |
116 | if (!this.ignore.includes(cmd.session)) return cmd;
117 | }
118 | }
119 |
120 | return null;
121 | }
122 |
123 | /**
124 | * Execute a command message.
125 | */
126 | public async exec(cmd: Command): Promise {
127 | this._ignore(cmd.session);
128 | this.handles.emit('commandStarted', cmd);
129 |
130 | try {
131 | // TODO: Remove the param on global pre and post for v8
132 | for (const fn of this.pre) await fn.call(cmd, cmd);
133 | await cmd.pre.call(cmd);
134 | const result = await cmd.exec.call(cmd);
135 | await cmd.post.call(cmd);
136 | for (const fn of this.post) await fn.call(cmd, cmd);
137 |
138 | this.handles.emit('commandFinished', { command: cmd, result });
139 | return this.silent ? result : undefined;
140 | } catch (e) {
141 | try {
142 | await cmd.error();
143 | } catch (e) {
144 | // do nothing
145 | }
146 |
147 | if (e instanceof BaseError) {
148 | this.handles.emit('commandFailed', { command: cmd, error: e });
149 | if (!this.silent) return e;
150 | } else {
151 | this.handles.emit('commandError', { command: cmd, error: e });
152 | if (!this.silent) throw e;
153 | }
154 | } finally {
155 | this.executed.push(cmd);
156 | setTimeout(() => this.executed.splice(this.executed.indexOf(cmd), 1), 60 * 60 * 1000);
157 |
158 | this._unignore(cmd.session);
159 | }
160 | }
161 |
162 | /**
163 | * Ignore something (designed for [[CommandMessage#session]]).
164 | * @param session The data to ignore.
165 | */
166 | private _ignore(session: string) {
167 | this.ignore.push(session);
168 | }
169 |
170 | /**
171 | * Stop ignoring something (designed for [[CommandMessage#session]]).
172 | * @param session The data to unignore.
173 | */
174 | private _unignore(session: string) {
175 | const index = this.ignore.indexOf(session);
176 | if (index > -1) this.ignore.splice(index, 1);
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/core/CommandRegistry.ts:
--------------------------------------------------------------------------------
1 | import { promisify } from 'tsubaki';
2 |
3 | import { IConfig } from '../interfaces/Config';
4 | import Command, { Trigger } from '../structures/Command';
5 | import HandlesClient from './Client';
6 |
7 | import fs = require('fs');
8 | import path = require('path');
9 |
10 | const readdir: (dir: string) => Promise = promisify(fs.readdir);
11 | const stat: (path: string) => Promise = promisify(fs.stat);
12 |
13 | /**
14 | * Manage command loading.
15 | */
16 | export default class CommandRegistry extends Map {
17 |
18 | /**
19 | * Get all the file paths recursively in a directory.
20 | * @param dir The directory to start at.
21 | */
22 | private static async _loadDir(dir: string): Promise {
23 | const files = await readdir(dir);
24 | const list: string[] = [];
25 |
26 | await Promise.all(files.map(async (f) => {
27 | const currentPath = path.join(dir, f);
28 | const stats = await stat(currentPath);
29 |
30 | if (stats.isFile() && path.extname(currentPath) === '.js') {
31 | list.push(currentPath);
32 | } else if (stats.isDirectory()) {
33 | const files = await this._loadDir(currentPath);
34 | list.push(...files);
35 | }
36 | }));
37 |
38 | return list;
39 | }
40 |
41 | /**
42 | * Handles client.
43 | */
44 | public readonly handles: HandlesClient;
45 |
46 | /**
47 | * The directory from which to load commands.
48 | */
49 | public directory: string;
50 |
51 | constructor(handles: HandlesClient, config: IConfig) {
52 | super();
53 |
54 | this.handles = handles;
55 | this.directory = config.directory || './commands';
56 |
57 | this.load();
58 | }
59 |
60 | /**
61 | * Load all commands into memory. Use when reloading commands.
62 | */
63 | public async load(): Promise {
64 | const start = Date.now();
65 |
66 | this.clear();
67 | const files = await CommandRegistry._loadDir(this.directory);
68 |
69 | const failed = [];
70 | for (const file of files) {
71 | let mod;
72 | const location = path.resolve(process.cwd(), file);
73 |
74 | try {
75 | delete require.cache[require.resolve(location)];
76 | mod = require(location);
77 | } catch (e) {
78 | failed.push(file);
79 | console.error(e); // tslint:disable-line no-console
80 | continue;
81 | }
82 |
83 | if (typeof mod.default !== 'undefined') mod = mod.default;
84 |
85 | // if triggers are iterable
86 | if (Array.isArray(mod.triggers)) {
87 | for (const trigger of mod.triggers) this.set(trigger, mod);
88 | } else if (typeof mod.triggers === 'undefined') { // if no triggers are provided
89 | this.set(path.basename(file, '.js'), mod);
90 | } else {
91 | this.set(mod.triggers, mod);
92 | }
93 | }
94 |
95 | this.handles.emit('commandsLoaded', { commands: this, failed, time: Date.now() - start });
96 | return this;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/errors/ArgumentError.ts:
--------------------------------------------------------------------------------
1 | import Argument from '../middleware/Argument';
2 | import BaseError from './BaseError';
3 |
4 | /**
5 | * Used to represent a user error with collecting arguments.
6 | */
7 | export default class ArgumentError extends BaseError {
8 | /**
9 | * The argument that has errored.
10 | */
11 | public argument: Argument;
12 |
13 | constructor(arg: Argument, reason: string) {
14 | super(reason);
15 | this.argument = arg;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/errors/BaseError.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The base error class. Used to represent user input errors as opposed to logic failures.
3 | */
4 | export default class BaseError {
5 | /**
6 | * The error message.
7 | */
8 | public message: string;
9 |
10 | constructor(message: string) {
11 | this.message = message;
12 | }
13 |
14 | public toString() {
15 | return this.message;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/errors/ValidationError.ts:
--------------------------------------------------------------------------------
1 | import Validator from '../middleware/Validator';
2 | import BaseError from './BaseError';
3 |
4 | /**
5 | * Used to represent a user error with validation (e.g. the command was invalid).
6 | */
7 | export default class ValidationError extends BaseError {
8 | /**
9 | * The validator that errored.
10 | */
11 | public validator: Validator;
12 |
13 | constructor(validator: Validator) {
14 | super(validator.reason || 'Validation failed.');
15 | this.validator = validator;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import Client from './core/Client';
2 | import CommandHandler from './core/CommandHandler';
3 | import CommandRegistry from './core/CommandRegistry';
4 |
5 | import Command from './structures/Command';
6 | import Response from './structures/Response';
7 |
8 | import Argument from './middleware/Argument';
9 | import Validator from './middleware/Validator';
10 |
11 | export * from './interfaces/Config';
12 |
13 | export {
14 | Argument,
15 | Client,
16 | Command,
17 | CommandHandler,
18 | CommandRegistry,
19 | Response,
20 | Validator,
21 | };
22 |
--------------------------------------------------------------------------------
/src/interfaces/Config.ts:
--------------------------------------------------------------------------------
1 | import { ClientOptions } from 'discord.js';
2 | import { MessageValidator } from '../core/CommandHandler';
3 | import Response from '../structures/Response';
4 |
5 | export interface IConfig {
6 | /**
7 | * Command prefixes (will not be used if a [[validator]] is provided).
8 | */
9 | prefixes?: Set;
10 |
11 | /**
12 | * A global setting for configuring the argument suffix.
13 | */
14 | argsSuffix?: string;
15 |
16 | /**
17 | * Directory to load commands from. Default to `./commands` relative to the cwd.
18 | */
19 | directory?: string;
20 |
21 | /**
22 | * This will get run on every message. Use to manually determine whether a message is a command.
23 | */
24 | validator?: MessageValidator;
25 |
26 | /**
27 | * Set to false to resolve/reject the command handling process properly. When true,
28 | * the command handler promise will always resolve with void (you should use the events
29 | * [[HandlesClient#commandFailed]] and [[HandlesClient#commandFinished]] to check completion status in this
30 | * case).
31 | */
32 | silent?: boolean;
33 |
34 | /**
35 | * Whether to automatically listen for commands. If you specify this as false, you'll have to
36 | * `handles.handle(message)` in your message listener.
37 | */
38 | autoListen?: boolean;
39 | }
40 |
--------------------------------------------------------------------------------
/src/middleware/Argument.ts:
--------------------------------------------------------------------------------
1 | import HandlesClient from '../core/Client';
2 | import ArgumentError from '../errors/ArgumentError';
3 | import Command from '../structures/Command';
4 | import Response from '../structures/Response';
5 |
6 | import { Message } from 'discord.js';
7 |
8 | /**
9 | * This is called every time new potential argument data is received, either in the body of
10 | * the original command or in subsequent prompts.
11 | */
12 | export type Resolver = (content: string, message: Message, arg: Argument) => T | null;
13 |
14 | export interface IOptions {
15 | prompt?: string;
16 | rePrompt?: string;
17 | optional?: boolean;
18 | resolver?: Resolver;
19 | timeout?: number;
20 | pattern?: RegExp;
21 | suffix?: string | null;
22 | }
23 |
24 | /**
25 | * This function takes a string which contains any number of arguments and returns the first of them.
26 | * The return should be a substring of the input, which will then be removed from the input string. The remaining
27 | * input will be fed back into this function for the next argument, etc. until no more arguments remain. Use this
28 | * to determine whether an argument *exists*; use [[Argument#resolver]] to determine if the argument is *valid*.
29 | */
30 | export type Matcher = (content: string) => string;
31 |
32 | /**
33 | * Represents a command argument.
34 | */
35 | export default class Argument implements IOptions, Promise {
36 | public readonly command: Command;
37 |
38 | /**
39 | * The key that this arg will be set to.
40 | *
41 | * ```js
42 | * // in args definition
43 | * new Argument('thing');
44 | *
45 | * // in command execution
46 | * const thingData = command.args.thing;
47 | * ```
48 | */
49 | public key: string;
50 |
51 | /**
52 | * The initial prompt text of this argument.
53 | */
54 | public prompt: string;
55 |
56 | /**
57 | * Text sent for re-prompting to provide correct input when provided input is not resolved
58 | * (ie. the resolver returns null).
59 | */
60 | public rePrompt: string;
61 |
62 | /**
63 | * Whether this argument is optional.
64 | */
65 | public optional: boolean = false;
66 |
67 | /**
68 | * The argument resolver for this argument.
69 | */
70 | public resolver: Resolver;
71 |
72 | /**
73 | * How long to wait for a response to a prompt, in seconds.
74 | */
75 | public timeout: number;
76 |
77 | /**
78 | * Text to append to each prompt. Defaults to global setting or built-in text.
79 | */
80 | public suffix: string | null;
81 |
82 | /**
83 | * The matcher for this argument.
84 | */
85 | public matcher: Matcher;
86 |
87 | /**
88 | * The raw content matching regex.
89 | */
90 | private _pattern: RegExp;
91 |
92 | constructor(command: Command, key: string, {
93 | prompt = '',
94 | rePrompt = '',
95 | optional = false,
96 | timeout = 30,
97 | suffix = null,
98 | pattern = /^\S+/,
99 | }: IOptions = {}) {
100 | this.command = command;
101 | this.key = key;
102 |
103 | this.prompt = prompt;
104 | this.rePrompt = rePrompt;
105 | this.optional = optional;
106 | this.timeout = timeout;
107 | this.suffix = suffix;
108 | this.pattern = pattern;
109 | }
110 |
111 | get handles(): HandlesClient {
112 | return this.command.handles;
113 | }
114 |
115 | /**
116 | * A regex describing the pattern of arguments. Defaults to single words. If more advanced matching
117 | * is required, set a custom [[matcher]] instead. Can pull arguments from anywhere in the unresolved
118 | * content, so make sure to specify `^` if you want to pull from the front.
119 | */
120 | get pattern() {
121 | return this._pattern;
122 | }
123 |
124 | set pattern(regex) {
125 | this._pattern = regex;
126 |
127 | this.matcher = (content) => {
128 | const m = content.match(regex);
129 | return m === null ? '' : m[0];
130 | };
131 | }
132 |
133 | /**
134 | * Make this argument take up the rest of the words in the command. Any remaining required arguments
135 | * will be prompted for.
136 | */
137 | public setInfinite() {
138 | return this.setPattern(/.*/);
139 | }
140 |
141 | /**
142 | * Set the pattern for matching args strings.
143 | */
144 | public setPattern(pattern: RegExp) {
145 | this.pattern = pattern;
146 | return this;
147 | }
148 |
149 | /**
150 | * Set the prompt for the argument.
151 | */
152 | public setPrompt(prompt: string = '') {
153 | this.prompt = prompt;
154 | return this;
155 | }
156 |
157 | /**
158 | * Set the re-prompt for the argument.
159 | */
160 | public setRePrompt(rePrompt: string = '') {
161 | this.rePrompt = rePrompt;
162 | return this;
163 | }
164 |
165 | /**
166 | * Set whether the argument is optional.
167 | */
168 | public setOptional(optional: boolean = true) {
169 | this.optional = optional;
170 | return this;
171 | }
172 |
173 | /**
174 | * Set the argument resolver function for this argument.
175 | */
176 | public setResolver(resolver: Resolver) {
177 | this.resolver = resolver;
178 | return this;
179 | }
180 |
181 | /**
182 | * Set the time to wait for a prompt response (in seconds).
183 | */
184 | public setTimeout(time: number = 30) {
185 | this.timeout = time;
186 | return this;
187 | }
188 |
189 | /**
190 | * Set the suffix for all prompts.
191 | */
192 | public setSuffix(text: string = '') {
193 | this.suffix = text;
194 | return this;
195 | }
196 |
197 | public then(
198 | resolver?: ((value: T | null) => TResult1 | PromiseLike),
199 | rejector?: ((value: Error) => TResult2 | PromiseLike),
200 | ): Promise {
201 | return new Promise(async (resolve, reject) => {
202 | const matched = this.matcher(this.command.body);
203 | this.command.body = this.command.body.replace(matched, '').trim();
204 |
205 | const resolver = this.resolver || ((c: string) => c);
206 | let resolved = !matched ? null : await resolver(matched, this.command.message, this);
207 |
208 | // if there is no matched content and the argument is not optional, collect a prompt
209 | if (resolved === null && !this.optional) {
210 | try {
211 | resolved = await this.collectPrompt(matched.length === 0);
212 | } catch (e) {
213 | this.command.response.send('Command cancelled.');
214 | if (e instanceof Map && !e.size) e = new ArgumentError(this, 'time');
215 | return reject(e);
216 | }
217 | }
218 |
219 | if (!this.command.args) this.command.args = {};
220 | this.command.args[this.key] = resolved;
221 |
222 | return resolve(resolved);
223 | }).then(resolver, rejector);
224 | }
225 |
226 | public catch(rejector?: ((value: Error) => TResult2 | PromiseLike)) {
227 | return this.then(undefined, rejector);
228 | }
229 |
230 | public get [Symbol.toStringTag](): 'Promise' {
231 | return 'Promise';
232 | }
233 |
234 | private async collectPrompt(first = true): Promise {
235 | const text = first ? this.prompt : this.rePrompt;
236 | const suffix = this.suffix || this.handles.argsSuffix ||
237 | `\nCommand will be cancelled in **${this.timeout} seconds**. Type \`cancel\` to cancel immediately.`;
238 |
239 | // get first response
240 | const prompt = new this.handles.Response(this.command.message);
241 | await prompt.send(text + suffix);
242 | const responses = await prompt.channel.awaitMessages(
243 | (m: Message) => m.author.id === this.command.author.id,
244 | { time: this.timeout * 1000, max: 1, errors: ['time'] },
245 | );
246 | const response = responses.first();
247 |
248 | // cancel
249 | if (response.content === 'cancel') throw new ArgumentError(this, 'cancelled');
250 |
251 | // resolve: if not, prompt again
252 | const resolved = await this.resolver(response.content, response, this);
253 | if (resolved === null) return this.collectPrompt(false);
254 |
255 | return resolved;
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/src/middleware/Validator.ts:
--------------------------------------------------------------------------------
1 | import ValidationError from '../errors/ValidationError';
2 | import Command from '../structures/Command';
3 |
4 | /**
5 | * ```js
6 | * validator.apply(cmd => cmd.message.author.id === 'some id', 'uh oh'); // executed at runtime
7 | * // or
8 | * validator.apply(cmd.message.author.id === 'some id', 'uh oh'); // executed immediately
9 | * ```
10 | */
11 | export type ValidationFunction = (v: Validator) => boolean;
12 |
13 | /**
14 | * Passed as a parameter to command validators. Arguments will not be available in this class,
15 | * as this is run before arguments are resolved from the command. Use for permissions checks and
16 | * other pre-command validations.
17 | *
18 | * ```js
19 | * // Using a custom validator.
20 | * class CustomValidator extends Validator {
21 | * ensureGuild() {
22 | * return this.apply(this.command.message.channel.type === 'text', 'Command must be run in a guild channel.');
23 | * }
24 | * }
25 | *
26 | * // Usage in command
27 | * exports.validator = processor => {
28 | * return processor.ensureGuild();
29 | * }
30 | * ```
31 | *
32 | * ```js
33 | * // Usage without a custom validator
34 | * exports.validator = (processor, command) => {
35 | * return processor.apply(command.message.channel.type === 'text', 'Command must be run in a guild channel.');
36 | * }
37 | * ```
38 | */
39 | export default class Validator {
40 | public command: Command;
41 |
42 | /**
43 | * The reason this validator is invalid.
44 | */
45 | public reason: string | null = null;
46 |
47 | /**
48 | * Whether to automatically respond with reason when invalid.
49 | */
50 | public respond: boolean = true;
51 |
52 | /**
53 | * Whether this validator is valid.
54 | */
55 | public valid = true;
56 |
57 | /**
58 | * Functions to execute when determining validity. Maps validation functions to reasons.
59 | */
60 | private exec: Map = new Map();
61 |
62 | constructor(cmd: Command) {
63 | this.command = cmd;
64 | }
65 |
66 | /**
67 | * Test a new boolean for validity.
68 | *
69 | * ```js
70 | * const validator = new Validator();
71 | * validator.apply(aCondition, 'borke') || validator.apply(otherCondition, 'different borke');
72 | * yield validator;
73 | * ```
74 | */
75 | public apply(test: ValidationFunction | boolean, reason: string | null = null) {
76 | this.exec.set(typeof test === 'function' ? test : () => test, reason);
77 | return this;
78 | }
79 |
80 | public then(
81 | resolver?: ((value: void) => TResult1 | PromiseLike),
82 | rejector?: ((value: Error) => TResult2 | PromiseLike),
83 | ): Promise {
84 | return new Promise((resolve, reject) => {
85 | for (const [test, reason] of this.exec) {
86 | try {
87 | if (!test(this)) {
88 | this.reason = reason;
89 | this.valid = false;
90 | throw new ValidationError(this);
91 | }
92 | } catch (e) {
93 | if (this.respond) this.command.response.error(e);
94 | return reject(e);
95 | }
96 | }
97 |
98 | return resolve();
99 | }).then(resolver, rejector);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/structures/Command.ts:
--------------------------------------------------------------------------------
1 | import HandlesClient from '../core/Client';
2 |
3 | import { IConfig } from '../interfaces/Config';
4 | import Response, { TextBasedChannel } from './Response';
5 |
6 | import { Client, Guild, GuildMember, Message, User } from 'discord.js';
7 |
8 | export type Trigger = string | RegExp;
9 |
10 | export interface ICommandOptions {
11 | trigger: Trigger;
12 | message: Message;
13 | body: string;
14 | }
15 |
16 | /**
17 | * A command.
18 | * ```js
19 | * const { Command } = require('discord-handles');
20 | * module.exports = class extends Command {
21 | * static get triggers() {
22 | * return ['ping', 'pung', 'poing', 'pong'];
23 | * }
24 | *
25 | * exec() {
26 | * return this.response.success(`${this.trigger} ${Date.now() - this.message.createdTimestamp}ms`);
27 | * }
28 | * };
29 | */
30 | export default class Command {
31 | public static triggers?: Trigger | Trigger[];
32 |
33 | /**
34 | * The handles client.
35 | */
36 | public readonly handles: HandlesClient;
37 |
38 | /**
39 | * The command trigger that caused the message to run this command.
40 | */
41 | public readonly trigger: Trigger;
42 |
43 | /**
44 | * The message that triggered this command.
45 | */
46 | public readonly message: Message;
47 |
48 | /**
49 | * The body of the command (without prefix or command), as provided in the original message.
50 | */
51 | public body: string;
52 |
53 | /**
54 | * Client config.
55 | */
56 | public config: IConfig;
57 |
58 | /**
59 | * The command arguments as set by arguments in executor.
60 | */
61 | public args?: any;
62 |
63 | /**
64 | * The response object for this command.
65 | */
66 | public response: Response;
67 |
68 | constructor(client: HandlesClient, { trigger, message, body }: ICommandOptions) {
69 | this.handles = client;
70 | this.trigger = trigger;
71 | this.message = message;
72 | this.body = body;
73 | this.args = null;
74 | this.response = new this.handles.Response(this.message);
75 | }
76 |
77 | /**
78 | * The Discord.js client.
79 | */
80 | get client(): Client {
81 | return this.message.client;
82 | }
83 |
84 | /**
85 | * The guild this command is in.
86 | */
87 | get guild(): Guild {
88 | return this.message.guild;
89 | }
90 |
91 | /**
92 | * The channel this command is in.
93 | */
94 | get channel(): TextBasedChannel {
95 | return this.message.channel;
96 | }
97 |
98 | /**
99 | * The author of this command.
100 | */
101 | get author(): User {
102 | return this.message.author;
103 | }
104 |
105 | get member(): GuildMember {
106 | return this.message.member;
107 | }
108 |
109 | /**
110 | * Ensure unique commands for an author in a channel.
111 | * Format: "authorID:channelID"
112 | */
113 | get session() {
114 | return `${this.message.author.id}:${this.message.channel.id}`;
115 | }
116 |
117 | /**
118 | * Executed prior to {@link Command#exec}. Should be used for middleware/validation.
119 | * ```js
120 | * async pre() {
121 | * await new handles.Argument(this, 'someArgument')
122 | * .setResolver(c => c === 'dank memes' ? 'top kek' : null);
123 | * }
124 | */
125 | public pre() {
126 | // implemented by command
127 | }
128 |
129 | /**
130 | * The command execution method
131 | */
132 | public exec() {
133 | // implemented by command
134 | }
135 |
136 | /**
137 | * Executed after {@link Command#exec}. Can be used for responses.
138 | */
139 | public post() {
140 | // implemented by command
141 | }
142 |
143 | /**
144 | * Executed when any of the command execution methods error. Any errors here will be discarded.
145 | */
146 | public error() {
147 | // implemented by command
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/structures/Response.ts:
--------------------------------------------------------------------------------
1 | import Queue from '../util/Queue';
2 |
3 | import { DMChannel, GroupDMChannel, Message, MessageOptions, TextBasedChannel, TextChannel } from 'discord.js';
4 |
5 | export type TextBasedChannel = TextChannel | DMChannel | GroupDMChannel;
6 |
7 | export type SentResponse = Message | Message[];
8 | export interface IResponseOptions extends MessageOptions {
9 | /**
10 | * Whether to catch all rejections when sending. The promise will always resolve when this option
11 | * is enabled; if there is an error, the resolution will be undefined.
12 | */
13 | catchall?: boolean;
14 |
15 | /**
16 | * Whether to send a new message regardless of any prior responses.
17 | */
18 | force?: boolean;
19 | }
20 |
21 | export type Send = (
22 | data: string | IResponseOptions,
23 | options?: IResponseOptions,
24 | ) => Promise;
25 |
26 | /**
27 | * Send responses to a message.
28 | */
29 | export default class Response {
30 |
31 | /**
32 | * The message to respond to.
33 | */
34 | public message: Message;
35 |
36 | /**
37 | * Whether to edit previous responses.
38 | */
39 | public edit: boolean = true;
40 |
41 | /**
42 | * The channel to send responses in.
43 | */
44 | public channel: TextBasedChannel;
45 |
46 | /**
47 | * The message to edit, if enabled.
48 | */
49 | public responseMessage?: Message | null;
50 |
51 | /**
52 | * The queue of response jobs.
53 | */
54 | private readonly _q: Queue;
55 |
56 | /**
57 | * @param message The message to respond to.
58 | * @param edit Whether to edit previous responses.
59 | */
60 | constructor(message: Message, edit: boolean = true) {
61 | this.message = message;
62 | this.channel = message.channel;
63 | this.edit = edit;
64 | this.responseMessage = null;
65 | this._q = new Queue();
66 | }
67 |
68 | /**
69 | * Send a message using the Discord.js `Message.send` method. If a prior
70 | * response has been sent, it will edit that unless the `force` parameter
71 | * is set. Automatically attempts to fallback to DM responses. You can
72 | * send responses without waiting for prior responses to succeed.
73 | * @param data The data to send
74 | * @param options Message options.
75 | * @param messageOptions Discord.js message options.
76 | */
77 | public send: Send = (data, options = {}, ...extra: IResponseOptions[]) => {
78 | options = Object.assign(options, ...extra);
79 | return new Promise((resolve, reject) => {
80 | this._q.push(async (): Promise => {
81 | function success(m?: SentResponse): void {
82 | resolve(m);
83 | }
84 |
85 | function error(e: Error): void {
86 | if (options.catchall) return success();
87 | reject(e);
88 | }
89 |
90 | if (this.responseMessage && this.edit && !options.force) {
91 | await this.responseMessage.edit(data, options).then(success, error);
92 | } else {
93 | await this.channel.send(data, options).then((m) => {
94 | if (Array.isArray(m)) this.responseMessage = m[0];
95 | else this.responseMessage = m;
96 | return success(m);
97 | }, () => {
98 | if (this.channel.type === 'text') {
99 | return this.message.author.send(data, options).then(success, error);
100 | }
101 | });
102 | }
103 | });
104 | });
105 | }
106 |
107 | public error: Send = (data, ...options: IResponseOptions[]) => {
108 | return this.send(`\`❌\` | ${data}`, ...options);
109 | }
110 |
111 | public success: Send = (data, ...options: IResponseOptions[]) => {
112 | return this.send(`\`✅\` | ${data}`, ...options);
113 | }
114 |
115 | public dm: Send = async (data, ...options: IResponseOptions[]) => {
116 | this.channel = this.message.author.dmChannel || await this.message.author.createDM();
117 | return this.send(data, ...options);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/util/Queue.ts:
--------------------------------------------------------------------------------
1 | export type QueueFunction = (...args: any[]) => Promise;
2 |
3 | export default class Queue extends Array {
4 | private _started: boolean;
5 |
6 | constructor() {
7 | super();
8 | Object.defineProperty(this, '_started', { writable: true, value: false });
9 |
10 | return new Proxy(this, {
11 | set(target, prop: any, value: QueueFunction) {
12 | target[prop] = value;
13 | if (!isNaN(prop)) target.start();
14 | return true;
15 | },
16 | });
17 | }
18 |
19 | public async start() {
20 | if (this._started) return;
21 | this._started = true;
22 |
23 | while (this.length) {
24 | const func = this.shift();
25 | if (func) await func();
26 | }
27 |
28 | this._started = false;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test/commands/add.js:
--------------------------------------------------------------------------------
1 | const Handles = require('../../dist/index');
2 |
3 | class Command extends Handles.Command {
4 | async pre() {
5 | // const val1 = new Handles.Validator(this)
6 | // .apply(false, 'kek');
7 | const val2 = new Handles.Validator(this)
8 | .apply(true, 'lol');
9 |
10 | // await val1;
11 |
12 | await new Handles.Argument(this, 'first')
13 | .setPrompt('Please provide the first digit.')
14 | .setRePrompt('xd1')
15 | .setResolver(c => isNaN(c) ? null : parseInt(c));
16 |
17 | await new Handles.Argument(this, 'second')
18 | .setPrompt('Please provide the second digit.')
19 | .setRePrompt('xd2')
20 | .setResolver(c => isNaN(c) ? null : parseInt(c));
21 | }
22 |
23 | exec() {
24 | return this.response.send(this.args.first + this.args.second);
25 | }
26 | }
27 |
28 | module.exports = Command;
29 |
--------------------------------------------------------------------------------
/test/commands/dm.js:
--------------------------------------------------------------------------------
1 | exports.exec = (cmd) => cmd.response.dm('stuff');
2 |
--------------------------------------------------------------------------------
/test/commands/error.js:
--------------------------------------------------------------------------------
1 | exports.exec = () => {
2 | throw new Error('failure!');
3 | };
4 |
--------------------------------------------------------------------------------
/test/commands/eval.js:
--------------------------------------------------------------------------------
1 | const util = require('util');
2 | const { Command, Argument } = require('../../dist');
3 |
4 | module.exports = class extends Command {
5 | async pre() {
6 | await new Argument(this, 'code')
7 | .setResolver(c => c || null)
8 | .setPrompt('What would you like to eval?')
9 | .setRePrompt('That\'s not valid.');
10 | }
11 |
12 | async exec() {
13 | try {
14 | this.response.send(util.inspect(await eval(this.args.code)), undefined, { code: 'js' });
15 | } catch (e) {
16 | this.response.error(e);
17 | }
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/test/commands/invalid.js:
--------------------------------------------------------------------------------
1 | const handles = require('../../dist/index');
2 |
3 | exports.middleware = function* () {
4 | yield new handles.Validator()
5 | .apply(false);
6 | };
7 |
--------------------------------------------------------------------------------
/test/commands/ping.js:
--------------------------------------------------------------------------------
1 | const Argument = require('../../dist/middleware/Argument').default;
2 |
3 | // const HTTPPing = require('node-http-ping')
4 |
5 | class PingCommand
6 | {
7 | exec(command)
8 | {
9 | if (!command.args.site) {
10 | return command.response.success(`${Math.round(command.message.client.ping)}ms 💓`)
11 | }
12 |
13 | HTTPPing(command.args[0]).then((time) => {
14 | return command.response.success(`Website **${command.args[0]}** responded in ${time}ms.`)
15 | }).catch(console.error)
16 | }
17 |
18 | * middleware()
19 | {
20 | yield new Argument('site')
21 | .setPrompt('plz provide websit')
22 | .setRePrompt('this is no link my fRIEND')
23 | .setOptional()
24 | .setResolver((content) => {
25 | if (!content.match(new RegExp(/[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi))) {
26 | return null
27 | }
28 |
29 | return content
30 | })
31 | }
32 | }
33 |
34 | module.exports = new PingCommand
35 |
--------------------------------------------------------------------------------
/test/commands/spam.js:
--------------------------------------------------------------------------------
1 | exports.exec = cmd => {
2 | const s = cmd.response.send;
3 | s('d');
4 | s('a');
5 | s('n');
6 | s('k');
7 | };
8 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | // process.on('unhandledRejection', console.error);
2 |
3 | const path = require('path');
4 | require('dotenv').config({ path: path.join(__dirname, '.env') });
5 | // const raven = require('raven');
6 | const discord = require('discord.js');
7 | const handles = require('../dist/index');
8 |
9 | // raven.config(process.env.raven, {
10 | // captureUnhandledRejections: true,
11 | // }).install();
12 |
13 | const client = new discord.Client();
14 | const handler = new handles.Client(client, {
15 | directory: path.join('test', 'commands'),
16 | prefixes: new Set(['x!'])
17 | });
18 |
19 | handler.on('commandError', ({ command, error }) => {
20 | // const extra = {
21 | // message: {
22 | // content: command.message.content,
23 | // id: command.message.id,
24 | // type: command.message.type,
25 | // },
26 | // channel: {
27 | // id: command.message.channel.id,
28 | // type: command.message.channel.type,
29 | // },
30 | // guild: {},
31 | // client: {
32 | // shard: command.client.shard ? command.client.shard.id : null,
33 | // ping: command.client.ping,
34 | // status: command.client.status,
35 | // },
36 | // };
37 |
38 | // if (command.message.channel.type === 'text') {
39 | // extra.guild = {
40 | // id: command.guild.id,
41 | // name: command.guild.name,
42 | // owner: command.guild.ownerID,
43 | // };
44 | // }
45 | console.error(error);
46 | // console.error(extra);
47 |
48 | // console.log(raven.captureException(error, {
49 | // user: {
50 | // id: command.message.author.id,
51 | // username: command.message.author.tag,
52 | // },
53 | // extra
54 | // }));
55 | });
56 |
57 | client.once('ready', () => console.log('ready'));
58 |
59 | client.login(process.env.BOT_TOKEN);
60 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "target": "es2015",
7 | "lib": [
8 | "es2017"
9 | ],
10 | "declaration": true,
11 | "sourceMap": true,
12 | "removeComments": false
13 | },
14 | "include": [
15 | "./src/"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "jsRules": {},
7 | "rules": {
8 | "indent": [true, "spaces", 2],
9 | "linebreak-style": [true, "LF"],
10 | "quotemark": [true, "single"],
11 | "semicolon": [true, "always"],
12 | "curly": [true, "ignore-same-line"],
13 | "whitespace": [true, "check-branch", "check-decl", "check-module", "check-separator", "check-type", "check-typecast", "check-preblock"],
14 | // "no-unused-variable": [true], broken for some reason
15 | "no-console": [true],
16 | "triple-equals": true,
17 | "no-invalid-this": true,
18 | "no-trailing-whitespace": true,
19 | "no-unused-variable": true,
20 | "prefer-const": [true],
21 | "prefer-template": true,
22 | "variable-name": [
23 | "check-format",
24 | "allow-leading-underscore",
25 | "ban-keywords"
26 | ],
27 | "no-shadowed-variable": false
28 | },
29 | "rulesDirectory": []
30 | }
31 |
--------------------------------------------------------------------------------
105 |validator.apply(cmd => cmd.message.author.id === 'some id', 'uh oh'); // executed at runtime 102 | // or 103 | validator.apply(cmd.message.author.id === 'some id', 'uh oh'); // executed immediately 104 |