1 | <!DOCTYPE html>
|
2 | <html lang="en">
|
3 | <head>
|
4 | <meta charset="utf-8">
|
5 | <title>JSDoc: Source: Handler.js</title>
|
6 |
|
7 | <script src="scripts/prettify/prettify.js"> </script>
|
8 | <script src="scripts/prettify/lang-css.js"> </script>
|
9 | |
10 |
|
11 |
|
12 | <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
13 | <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
14 | </head>
|
15 |
|
16 | <body>
|
17 |
|
18 | <div id="main">
|
19 |
|
20 | <h1 class="page-title">Source: Handler.js</h1>
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | <section>
|
28 | <article>
|
29 | <pre class="prettyprint source linenums"><code>"use strict";
|
30 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
31 | if (k2 === undefined) k2 = k;
|
32 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
33 | }) : (function(o, m, k, k2) {
|
34 | if (k2 === undefined) k2 = k;
|
35 | o[k2] = m[k];
|
36 | }));
|
37 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
38 | Object.defineProperty(o, "default", { enumerable: true, value: v });
|
39 | }) : function(o, v) {
|
40 | o["default"] = v;
|
41 | });
|
42 | var __importStar = (this && this.__importStar) || function (mod) {
|
43 | if (mod && mod.__esModule) return mod;
|
44 | var result = {};
|
45 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
46 | __setModuleDefault(result, mod);
|
47 | return result;
|
48 | };
|
49 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
50 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
51 | };
|
52 | Object.defineProperty(exports, "__esModule", { value: true });
|
53 | exports.Handler = void 0;
|
54 | const discord_js_1 = __importDefault(require("discord.js"));
|
55 | const events_1 = __importDefault(require("events"));
|
56 | const mongoose_1 = __importDefault(require("mongoose"));
|
57 | const path_1 = require("path");
|
58 | const readdirp_1 = __importDefault(require("readdirp"));
|
59 | const yargs_1 = __importDefault(require("yargs"));
|
60 | const HelpCommand_1 = require("./HelpCommand");
|
61 | const Logging_1 = require("./Logging");
|
62 | const models = __importStar(require("./Models"));
|
63 | const Reaction_1 = require("./Reaction");
|
64 | const Utils_1 = require("./Utils");
|
65 | class Handler extends events_1.default {
|
66 | /**
|
67 | * Create a new command handler
|
68 | * @param {HandlerConstructor} opts - Put all options in this object. Only the client, prefix and commands directory are requred, everthing else is optional.
|
69 | */
|
70 | constructor({ client, prefix, commandsDir, verbose = false, admins = [], testServers = [], triggers = [], helpCommand, logging: loggerOptions, mongodb, blacklist, pauseCommand, ignoreBots = false, }) {
|
71 | super();
|
72 | this.listening = false;
|
73 | this.paused = false;
|
74 | if (client.readyAt === null)
|
75 | throw new Error("The client must be ready when you create the handler.");
|
76 | this.client = client;
|
77 | this.commandsDir = path_1.join(path_1.dirname(process.argv[1]), commandsDir);
|
78 | this.commands = new discord_js_1.default.Collection();
|
79 | this.v = verbose;
|
80 | this.opts = {
|
81 | prefix,
|
82 | admins: new Set(admins),
|
83 | testServers: new Set(testServers),
|
84 | triggers: new discord_js_1.default.Collection(),
|
85 | helpCommand,
|
86 | blacklist: blacklist || [],
|
87 | pauseCommand,
|
88 | ignoreBots,
|
89 | };
|
90 | //* setting up built-in modules
|
91 | // triggers
|
92 | triggers.forEach(item => this.opts.triggers.set(item[0], item[1]));
|
93 | // help command
|
94 | if (helpCommand)
|
95 | HelpCommand_1.init(this);
|
96 | // logging
|
97 | if (loggerOptions)
|
98 | this.logger = new Logging_1.Logger(client, loggerOptions);
|
99 | this.listening = false;
|
100 | this.paused = false;
|
101 | this.db = false;
|
102 | if (this.v)
|
103 | console.log("Command handler launching in verbose mode");
|
104 | // load the commands
|
105 | this.loadCommands(this.commandsDir);
|
106 | // connect to db and set up sync
|
107 | if (mongodb)
|
108 | this.dbConnect(mongodb);
|
109 | }
|
110 | get isPaused() {
|
111 | return this.paused;
|
112 | }
|
113 | set pause(v) {
|
114 | this.paused = v;
|
115 | }
|
116 | get getOpts() {
|
117 | return this.opts;
|
118 | }
|
119 | get getCommands() {
|
120 | return this.commands;
|
121 | }
|
122 | get getLogger() {
|
123 | return this.logger;
|
124 | }
|
125 | /**
|
126 | * Recursively reads a directory and loads all .js and .ts files
|
127 | * (if these files don't export a command they will just be ignored)
|
128 | * @param {string} dir - The directory to use
|
129 | * @param {boolean} reload - Whether to clear the command list before reading (useful to reload the commands)
|
130 | */
|
131 | async loadCommands(dir, reload = false) {
|
132 | if (reload)
|
133 | this.commands.clear();
|
134 | if (this.v)
|
135 | console.log(`Loading commands from: ${dir}`);
|
136 | for await (const entry of readdirp_1.default(dir, {
|
137 | fileFilter: ["*.js", "*.ts"],
|
138 | })) {
|
139 | if (this.v)
|
140 | console.log(`Loading command: ${entry.basename}`);
|
141 | // import the actual file
|
142 | const command = (await Promise.resolve().then(() => __importStar(require(entry.fullPath)))).command;
|
143 | if (!command)
|
144 | continue;
|
145 | if (!command.opts.category) {
|
146 | const r = new RegExp(/\\|\//, "g");
|
147 | command.opts.category =
|
148 | entry.path.split(r).length > 1
|
149 | ? entry.path.split(r).shift()
|
150 | : "No category";
|
151 | }
|
152 | // error checking
|
153 | if (command === undefined)
|
154 | throw new Error(`Couldn't import command from ${entry.path}. Make sure you are exporting a command variable that is a new Command`);
|
155 | if (this.getCommand(command.opts.names) !== undefined)
|
156 | throw new Error(`Command name ${command.opts.names[0]} is being used twice!`);
|
157 | if (command.opts.adminOnly && this.opts.admins.size == 0)
|
158 | throw new Error(`Command ${entry.path} is set to admin only, but no admins were defined.`);
|
159 | // add the command to the collection
|
160 | this.commands.set(command.opts.names[0], command);
|
161 | }
|
162 | if (!reload || this.v)
|
163 | console.log(`Finished loading ${this.commands.size} commands.`);
|
164 | if (this.v)
|
165 | console.log("Commands:", this.commands.map(item => item.opts.names[0]));
|
166 | // start listening to messages
|
167 | this.listen();
|
168 | this.emit("ready");
|
169 | }
|
170 | /**
|
171 | * Listen for messages
|
172 | */
|
173 | listen() {
|
174 | // listen only once
|
175 | if (this.listening)
|
176 | return;
|
177 | this.listening = true;
|
178 | this.client.on("message", async (message) => {
|
179 | var _a;
|
180 | if ((this.paused &&
|
181 | message.content !=
|
182 | this.opts.prefix + this.opts.pauseCommand) ||
|
183 | message.author.id === ((_a = this.client.user) === null || _a === void 0 ? void 0 : _a.id))
|
184 | return;
|
185 | //* saving guild to db
|
186 | if (this.db && !(await models.guild.findById(message.guild.id))) {
|
187 | const g = new models.guild({
|
188 | _id: message.guild.id,
|
189 | cooldowns: [],
|
190 | globalCooldowns: [],
|
191 | });
|
192 | await g.save();
|
193 | }
|
194 | //* reaction triggers
|
195 | for (const item of this.opts.triggers.keyArray()) {
|
196 | if (message.content.toLowerCase().includes(item)) {
|
197 | const emoji = this.opts.triggers.get(item);
|
198 | Reaction_1.React(message, emoji);
|
199 | }
|
200 | }
|
201 | //* prep to execute actual command
|
202 | if (!message.content.startsWith(this.opts.prefix))
|
203 | return;
|
204 | const args = message.content
|
205 | .slice(this.opts.prefix.length)
|
206 | .trim()
|
207 | .split(/\s+/);
|
208 | // removes first item of args and that is the command name
|
209 | const commandName = args.shift().toLowerCase();
|
210 | const command = this.getCommand(commandName);
|
211 | await this.executeCommand(message, command);
|
212 | });
|
213 | }
|
214 | /**
|
215 | * Execute a command.
|
216 | * (this is the function used internally for launching the commands)
|
217 | * @param {Discord.Message} message - The message that contains the command
|
218 | * @param {Command} command - The command to execute. (pro tip: combine with handler.getCommand)
|
219 | * @returns void
|
220 | */
|
221 | async executeCommand(message, command) {
|
222 | var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
223 | // not a command
|
224 | if (!command)
|
225 | return;
|
226 | const args = message.content
|
227 | .slice(this.opts.prefix.length)
|
228 | .trim()
|
229 | .split(/\s+/);
|
230 | // removes first item of args and that is the command name
|
231 | const commandName = args.shift().toLowerCase();
|
232 | // text is just all the args without the command name
|
233 | const text = message.content.replace(this.opts.prefix + commandName, "");
|
234 | //* error checking
|
235 | // command is test servers only
|
236 | if (command.opts.test &&
|
237 | !this.opts.testServers.has(message.guild.id)) {
|
238 | console.log(`${message.author.tag} tried to use test command: ${command.opts.names[0]}`);
|
239 | return;
|
240 | }
|
241 | // command is admins only
|
242 | if (command.opts.adminOnly &&
|
243 | !this.opts.admins.has(message.author.id)) {
|
244 | message.channel.send(((_a = this.opts.errMsg) === null || _a === void 0 ? void 0 : _a.noAdmin) || "You can't run this command!");
|
245 | return;
|
246 | }
|
247 | // command not allowed in dms
|
248 | if (message.channel.type === "dm" && command.opts.noDM) {
|
249 | message.channel.send(((_b = this.opts.errMsg) === null || _b === void 0 ? void 0 : _b.noDM) ||
|
250 | "You can't use this command in the dms");
|
251 | return;
|
252 | }
|
253 | // user or guild is on blacklist
|
254 | if (this.opts.blacklist.includes(message.author.id) ||
|
255 | ((_c = command.opts.blacklist) === null || _c === void 0 ? void 0 : _c.includes(message.author.id)) ||
|
256 | this.opts.blacklist.includes((_d = message.guild) === null || _d === void 0 ? void 0 : _d.id) ||
|
257 | ((_e = command.opts.blacklist) === null || _e === void 0 ? void 0 : _e.includes((_f = message.guild) === null || _f === void 0 ? void 0 : _f.id))) {
|
258 | message.channel.send(((_g = this.opts.errMsg) === null || _g === void 0 ? void 0 : _g.blacklist) ||
|
259 | "You've been blacklisted from using this command");
|
260 | return;
|
261 | }
|
262 | // too many args
|
263 | if (command.opts.maxArgs &&
|
264 | args.length > command.opts.maxArgs &&
|
265 | command.opts.maxArgs > 0) {
|
266 | message.channel.send(((_h = this.opts.errMsg) === null || _h === void 0 ? void 0 : _h.tooManyArgs) ||
|
267 | `Too many args. For more info, see: ${this.opts.prefix}help ${commandName}`);
|
268 | return;
|
269 | }
|
270 | // not enough args
|
271 | if (command.opts.minArgs && args.length < command.opts.minArgs) {
|
272 | message.channel.send(((_j = this.opts.errMsg) === null || _j === void 0 ? void 0 : _j.tooFewArgs) ||
|
273 | `Not enough args. For more info, see: ${this.opts.prefix}help ${commandName}`);
|
274 | return;
|
275 | }
|
276 | //* command is on cooldown
|
277 | if (message.channel.type != "dm" &&
|
278 | (command.opts.cooldown > 0 ||
|
279 | command.opts.globalCooldown > 0)) {
|
280 | // const guild = this.cache.get(message.guild!.id);
|
281 | const guild = (await models.guild.findById(message.guild.id));
|
282 | if (guild) {
|
283 | const CD = guild === null || guild === void 0 ? void 0 : guild.cooldowns.find(cd => cd.user == message.author.id &&
|
284 | cd.command == command.opts.names[0]);
|
285 | if (CD && CD.expires > Date.now()) {
|
286 | const t = Utils_1.toTime(CD.expires - Date.now(), true);
|
287 | message.channel.send(((_k = this.opts.errMsg) === null || _k === void 0 ? void 0 : _k.cooldown) ||
|
288 | `This command is on cooldown for another ${t}.`);
|
289 | return;
|
290 | }
|
291 | const globalCD = guild === null || guild === void 0 ? void 0 : guild.globalCooldowns.find(cd => cd.command == command.opts.names[0]);
|
292 | if (globalCD && globalCD.expires > Date.now()) {
|
293 | const t = Utils_1.toTime(globalCD.expires - Date.now(), true);
|
294 | message.channel.send(((_l = this.opts.errMsg) === null || _l === void 0 ? void 0 : _l.globalCooldown) ||
|
295 | `This command is on cooldown for the entire server for another ${t}.`);
|
296 | return;
|
297 | }
|
298 | }
|
299 | }
|
300 | //* running the actual command
|
301 | // coming soon
|
302 | // const argv = arg(command.opts.argv || {}, { argv: args });
|
303 | const argv = yargs_1.default(args).argv;
|
304 | const res = await command.run({
|
305 | client: this.client,
|
306 | message,
|
307 | args,
|
308 | argv,
|
309 | prefix: this.opts.prefix,
|
310 | handler: this,
|
311 | text,
|
312 | logger: this.logger,
|
313 | });
|
314 | if (command.opts.react && res !== false)
|
315 | Reaction_1.React(message, command.opts.react);
|
316 | //* log the command
|
317 | if (this.logger)
|
318 | this.logger.log(message);
|
319 | //* apply the cooldown (not if command falied)
|
320 | if (res !== false &&
|
321 | (command.opts.cooldown || command.opts.globalCooldown)) {
|
322 | const guild = (await models.guild.findById(message.guild.id));
|
323 | if (command.opts.cooldown) {
|
324 | // adding the cooldown
|
325 | guild === null || guild === void 0 ? void 0 : guild.cooldowns.push({
|
326 | user: message.author.id,
|
327 | command: command.opts.names[0],
|
328 | expires: Date.now() + command.opts.cooldown,
|
329 | });
|
330 | // removing the cooldown after it expired
|
331 | this.client.setTimeout(async () => {
|
332 | const g = (await models.guild.findById(message.guild.id));
|
333 | const i = g === null || g === void 0 ? void 0 : g.cooldowns.findIndex(cd => cd.user == message.author.id &&
|
334 | cd.command == command.opts.names[0]);
|
335 | if (i === -1)
|
336 | return;
|
337 | g === null || g === void 0 ? void 0 : g.cooldowns.splice(i, 1);
|
338 | await g.updateOne({ cooldowns: g.cooldowns });
|
339 | }, command.opts.cooldown);
|
340 | }
|
341 | if (command.opts.globalCooldown) {
|
342 | guild === null || guild === void 0 ? void 0 : guild.globalCooldowns.push({
|
343 | command: command.opts.names[0],
|
344 | expires: Date.now() + command.opts.globalCooldown,
|
345 | });
|
346 | this.client.setTimeout(async () => {
|
347 | const g = (await models.guild.findById(message.guild.id));
|
348 | const i = g === null || g === void 0 ? void 0 : g.globalCooldowns.findIndex(cd => cd.command == command.opts.names[0]);
|
349 | if (i === -1)
|
350 | return;
|
351 | g === null || g === void 0 ? void 0 : g.globalCooldowns.splice(i, 1);
|
352 | await g.updateOne({
|
353 | globalCooldowns: g.globalCooldowns,
|
354 | });
|
355 | }, command.opts.globalCooldown);
|
356 | }
|
357 | await guild.updateOne({
|
358 | cooldowns: guild.cooldowns,
|
359 | globalCooldowns: guild.globalCooldowns,
|
360 | });
|
361 | }
|
362 | return res;
|
363 | }
|
364 | /**
|
365 | * Find a command from any of its aliases
|
366 | * (this is the function used internally for finding commands)
|
367 | * @param {string} name - Name or names of a command
|
368 | * @returns The command or undefined if no command was found
|
369 | */
|
370 | getCommand(name) {
|
371 | if (typeof name === "string")
|
372 | return (this.commands.get(name) ||
|
373 | this.commands.find(c => c.opts.names.includes(name)));
|
374 | let found = undefined;
|
375 | name.forEach(item => {
|
376 | const res = this.getCommand(item);
|
377 | if (res !== undefined)
|
378 | found = res;
|
379 | });
|
380 | return found;
|
381 | }
|
382 | /**
|
383 | * Connect to the database (for cooldowns)
|
384 | * @param {string} uri - MongoDB connection string
|
385 | */
|
386 | async dbConnect(uri) {
|
387 | try {
|
388 | await mongoose_1.default.connect(uri, {
|
389 | useNewUrlParser: true,
|
390 | useUnifiedTopology: true,
|
391 | useFindAndModify: false,
|
392 | useCreateIndex: true,
|
393 | });
|
394 | console.log("Handler connected to DB");
|
395 | this.db = true;
|
396 | this.emit("dbConnected");
|
397 | await Utils_1.cleanDB();
|
398 | }
|
399 | catch (err) {
|
400 | console.error("Handler failed to connect to MongoDB: ", err.message);
|
401 | this.emit("dbConnectFailed", err);
|
402 | }
|
403 | }
|
404 | /**
|
405 | * A utility function to create nice embeds.
|
406 | * @param title
|
407 | * @param desc
|
408 | * @param color
|
409 | * @param thumbnail
|
410 | */
|
411 | makeEmbed(title, desc, color, thumbnail) {
|
412 | var _a, _b;
|
413 | const emb = new discord_js_1.default.MessageEmbed({
|
414 | title,
|
415 | description: desc,
|
416 | })
|
417 | .setAuthor((_a = this.client.user) === null || _a === void 0 ? void 0 : _a.username, (_b = this.client.user) === null || _b === void 0 ? void 0 : _b.displayAvatarURL({ dynamic: true }))
|
418 | .setColor(color || "BLURPLE");
|
419 | if (thumbnail)
|
420 | emb.setThumbnail(thumbnail);
|
421 | return emb;
|
422 | }
|
423 | }
|
424 | exports.Handler = Handler;
|
425 | exports.default = Handler;
|
426 | //# sourceMappingURL=Handler.js.map</code></pre>
|
427 | </article>
|
428 | </section>
|
429 |
|
430 |
|
431 |
|
432 |
|
433 | </div>
|
434 |
|
435 | <nav>
|
436 | <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Command.html">Command</a></li><li><a href="Handler.html">Handler</a></li></ul>
|
437 | </nav>
|
438 |
|
439 | <br class="clear">
|
440 |
|
441 | <footer>
|
442 | Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.7</a> on Thu Jun 03 2021 13:46:08 GMT+0200 (Central European Summer Time)
|
443 | </footer>
|
444 |
|
445 | <script> prettyPrint(); </script>
|
446 | <script src="scripts/linenumber.js"> </script>
|
447 | </body>
|
448 | </html>
|