UNPKG

19.2 kBHTMLView Raw
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 <!--[if lt IE 9]>
10 <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
11 <![endif]-->
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";
30var __createBinding = (this &amp;&amp; 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}));
37var __setModuleDefault = (this &amp;&amp; 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});
42var __importStar = (this &amp;&amp; this.__importStar) || function (mod) {
43 if (mod &amp;&amp; mod.__esModule) return mod;
44 var result = {};
45 if (mod != null) for (var k in mod) if (k !== "default" &amp;&amp; Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
46 __setModuleDefault(result, mod);
47 return result;
48};
49var __importDefault = (this &amp;&amp; this.__importDefault) || function (mod) {
50 return (mod &amp;&amp; mod.__esModule) ? mod : { "default": mod };
51};
52Object.defineProperty(exports, "__esModule", { value: true });
53exports.Handler = void 0;
54const discord_js_1 = __importDefault(require("discord.js"));
55const events_1 = __importDefault(require("events"));
56const mongoose_1 = __importDefault(require("mongoose"));
57const path_1 = require("path");
58const readdirp_1 = __importDefault(require("readdirp"));
59const yargs_1 = __importDefault(require("yargs"));
60const HelpCommand_1 = require("./HelpCommand");
61const Logging_1 = require("./Logging");
62const models = __importStar(require("./Models"));
63const Reaction_1 = require("./Reaction");
64const Utils_1 = require("./Utils");
65class 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 &amp;&amp; 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 &amp;&amp;
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 &amp;&amp; !(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 &amp;&amp;
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 &amp;&amp;
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" &amp;&amp; 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 &amp;&amp;
264 args.length > command.opts.maxArgs &amp;&amp;
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 &amp;&amp; args.length &lt; 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" &amp;&amp;
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 &amp;&amp;
284 cd.command == command.opts.names[0]);
285 if (CD &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp;
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 &amp;&amp;
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}
424exports.Handler = Handler;
425exports.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>