@open-discord-bots/framework 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +713 -0
- package/README.md +104 -0
- package/dist/api/api.d.ts +26 -0
- package/dist/api/api.js +44 -0
- package/dist/api/main.d.ts +133 -0
- package/dist/api/main.js +87 -0
- package/dist/api/modules/action.d.ts +34 -0
- package/dist/api/modules/action.js +58 -0
- package/dist/api/modules/base.d.ts +329 -0
- package/dist/api/modules/base.js +804 -0
- package/dist/api/modules/builder.d.ts +647 -0
- package/dist/api/modules/builder.js +1441 -0
- package/dist/api/modules/checker.d.ts +648 -0
- package/dist/api/modules/checker.js +1324 -0
- package/dist/api/modules/client.d.ts +768 -0
- package/dist/api/modules/client.js +1859 -0
- package/dist/api/modules/code.d.ts +33 -0
- package/dist/api/modules/code.js +57 -0
- package/dist/api/modules/config.d.ts +70 -0
- package/dist/api/modules/config.js +206 -0
- package/dist/api/modules/console.d.ts +305 -0
- package/dist/api/modules/console.js +598 -0
- package/dist/api/modules/cooldown.d.ts +138 -0
- package/dist/api/modules/cooldown.js +359 -0
- package/dist/api/modules/database.d.ts +135 -0
- package/dist/api/modules/database.js +271 -0
- package/dist/api/modules/event.d.ts +43 -0
- package/dist/api/modules/event.js +100 -0
- package/dist/api/modules/flag.d.ts +40 -0
- package/dist/api/modules/flag.js +72 -0
- package/dist/api/modules/fuse.d.ts +218 -0
- package/dist/api/modules/fuse.js +123 -0
- package/dist/api/modules/helpmenu.d.ts +106 -0
- package/dist/api/modules/helpmenu.js +167 -0
- package/dist/api/modules/language.d.ts +85 -0
- package/dist/api/modules/language.js +195 -0
- package/dist/api/modules/permission.d.ts +121 -0
- package/dist/api/modules/permission.js +314 -0
- package/dist/api/modules/plugin.d.ts +128 -0
- package/dist/api/modules/plugin.js +168 -0
- package/dist/api/modules/post.d.ts +44 -0
- package/dist/api/modules/post.js +92 -0
- package/dist/api/modules/progressbar.d.ts +108 -0
- package/dist/api/modules/progressbar.js +233 -0
- package/dist/api/modules/responder.d.ts +506 -0
- package/dist/api/modules/responder.js +1468 -0
- package/dist/api/modules/session.d.ts +58 -0
- package/dist/api/modules/session.js +171 -0
- package/dist/api/modules/startscreen.d.ts +165 -0
- package/dist/api/modules/startscreen.js +293 -0
- package/dist/api/modules/stat.d.ts +142 -0
- package/dist/api/modules/stat.js +293 -0
- package/dist/api/modules/verifybar.d.ts +54 -0
- package/dist/api/modules/verifybar.js +60 -0
- package/dist/api/modules/worker.d.ts +41 -0
- package/dist/api/modules/worker.js +93 -0
- package/dist/api/utils.d.ts +61 -0
- package/dist/api/utils.js +254 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +40 -0
- package/dist/startup/dump.d.ts +14 -0
- package/dist/startup/dump.js +79 -0
- package/dist/startup/errorHandling.d.ts +2 -0
- package/dist/startup/errorHandling.js +43 -0
- package/dist/startup/pluginLauncher.d.ts +2 -0
- package/dist/startup/pluginLauncher.js +202 -0
- package/package.json +9 -3
- package/src/api/api.ts +29 -0
- package/src/api/main.ts +189 -0
- package/src/api/modules/action.ts +58 -0
- package/src/api/modules/base.ts +811 -0
- package/src/api/modules/builder.ts +1554 -0
- package/src/api/modules/checker.ts +1549 -0
- package/src/api/modules/client.ts +2247 -0
- package/src/api/modules/code.ts +58 -0
- package/src/api/modules/config.ts +159 -0
- package/src/api/modules/console.ts +665 -0
- package/src/api/modules/cooldown.ts +348 -0
- package/src/api/modules/database.ts +278 -0
- package/src/api/modules/event.ts +99 -0
- package/src/api/modules/flag.ts +73 -0
- package/src/api/modules/fuse.ts +348 -0
- package/src/api/modules/helpmenu.ts +216 -0
- package/src/api/modules/language.ts +201 -0
- package/src/api/modules/permission.ts +340 -0
- package/src/api/modules/plugin.ts +242 -0
- package/src/api/modules/post.ts +90 -0
- package/src/api/modules/progressbar.ts +232 -0
- package/src/api/modules/responder.ts +1420 -0
- package/src/api/modules/session.ts +155 -0
- package/src/api/modules/startscreen.ts +320 -0
- package/src/api/modules/stat.ts +313 -0
- package/src/api/modules/verifybar.ts +61 -0
- package/src/api/modules/worker.ts +93 -0
- package/src/api/utils.ts +206 -0
- package/src/cli/cli.ts +151 -0
- package/src/cli/editConfig.ts +943 -0
- package/src/index.ts +6 -1
- package/src/startup/compilation.ts +186 -0
- package/src/startup/dump.ts +45 -0
- package/src/startup/errorHandling.ts +38 -0
- package/src/startup/pluginLauncher.ts +261 -0
- package/LICENSE +0 -21
|
@@ -0,0 +1,1859 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ODAutocompleteManager = exports.ODContextMenu = exports.ODContextMenuManager = exports.ODContextMenuComparator = exports.ODTextCommandManager = exports.ODTextCommand = exports.ODSlashCommand = exports.ODSlashCommandManager = exports.ODSlashCommandComparator = exports.ODClientActivityManager = exports.ODClientManager = void 0;
|
|
37
|
+
///////////////////////////////////////
|
|
38
|
+
//DISCORD CLIENT MODULE
|
|
39
|
+
///////////////////////////////////////
|
|
40
|
+
const base_1 = require("./base");
|
|
41
|
+
const discord = __importStar(require("discord.js"));
|
|
42
|
+
const rest_1 = require("@discordjs/rest");
|
|
43
|
+
const console_1 = require("./console");
|
|
44
|
+
/**## ODClientManager `class`
|
|
45
|
+
* This is an Open Discord client manager.
|
|
46
|
+
*
|
|
47
|
+
* It is responsible for managing the discord.js client. Here, you can set the status, register slash commands and much more!
|
|
48
|
+
*
|
|
49
|
+
* If you want, you can also listen for custom events on the `ODClientManager.client` variable (`discord.Client`)
|
|
50
|
+
*/
|
|
51
|
+
class ODClientManager {
|
|
52
|
+
/**Alias to Open Discord debugger. */
|
|
53
|
+
#debug;
|
|
54
|
+
/**List of required bot intents. Add intents to this list using the `onClientLoad` event. */
|
|
55
|
+
intents = [];
|
|
56
|
+
/**List of required bot privileged intents. Add intents to this list using the `onClientLoad` event. */
|
|
57
|
+
privileges = [];
|
|
58
|
+
/**List of required bot partials. Add intents to this list using the `onClientLoad` event. **❌ Only use when neccessery!** */
|
|
59
|
+
partials = [];
|
|
60
|
+
/**List of required bot permissions. Add permissions to this list using the `onClientLoad` event. */
|
|
61
|
+
permissions = [];
|
|
62
|
+
/**The discord bot token, empty by default. */
|
|
63
|
+
set token(value) {
|
|
64
|
+
this.#token = value;
|
|
65
|
+
this.rest.setToken(value);
|
|
66
|
+
}
|
|
67
|
+
get token() {
|
|
68
|
+
return this.#token;
|
|
69
|
+
}
|
|
70
|
+
/**The discord bot token. **DON'T USE THIS!!!** (use `ODClientManager.token` instead) */
|
|
71
|
+
#token = "";
|
|
72
|
+
/**The discord.js `discord.Client`. Only use it when initiated! */
|
|
73
|
+
client = new discord.Client({ intents: [] }); //temporary client
|
|
74
|
+
/**The discord.js REST client. Used for stuff that discord.js can't handle :) */
|
|
75
|
+
rest = new rest_1.REST({ version: "10" });
|
|
76
|
+
/**Is the bot initiated? */
|
|
77
|
+
initiated = false;
|
|
78
|
+
/**Is the bot logged in? */
|
|
79
|
+
loggedIn = false;
|
|
80
|
+
/**Is the bot ready? */
|
|
81
|
+
ready = false;
|
|
82
|
+
/**The main server of the bot. Provided by serverId in the config */
|
|
83
|
+
mainServer = null;
|
|
84
|
+
/**(❌ DO NOT OVERWRITE ❌) Internal Open Discord function to continue the startup when the client is ready! */
|
|
85
|
+
readyListener = null;
|
|
86
|
+
/**The status manager is responsible for setting the bot status. */
|
|
87
|
+
activity;
|
|
88
|
+
/**The slash command manager is responsible for all slash commands & their events inside the bot. */
|
|
89
|
+
slashCommands;
|
|
90
|
+
/**The text command manager is responsible for all text commands & their events inside the bot. */
|
|
91
|
+
textCommands;
|
|
92
|
+
/**The context menu manager is responsible for all context menus & their events inside the bot. */
|
|
93
|
+
contextMenus;
|
|
94
|
+
/**The autocomplete manager is responsible for all autocomplete events inside the bot. */
|
|
95
|
+
autocompletes;
|
|
96
|
+
constructor(debug) {
|
|
97
|
+
this.#debug = debug;
|
|
98
|
+
this.activity = new ODClientActivityManager(this.#debug, this);
|
|
99
|
+
this.slashCommands = new ODSlashCommandManager(this.#debug, this);
|
|
100
|
+
this.textCommands = new ODTextCommandManager(this.#debug, this);
|
|
101
|
+
this.contextMenus = new ODContextMenuManager(this.#debug, this);
|
|
102
|
+
this.autocompletes = new ODAutocompleteManager(this.#debug, this);
|
|
103
|
+
}
|
|
104
|
+
/**Initiate the `client` variable & add the intents & partials to the bot. */
|
|
105
|
+
initClient() {
|
|
106
|
+
if (!this.intents.every((value) => typeof discord.GatewayIntentBits[value] != "undefined"))
|
|
107
|
+
throw new base_1.ODSystemError("Client has non-existing intents!");
|
|
108
|
+
if (!this.privileges.every((value) => typeof { GuildMembers: true, MessageContent: true, Presence: true }[value] != "undefined"))
|
|
109
|
+
throw new base_1.ODSystemError("Client has non-existing privileged intents!");
|
|
110
|
+
if (!this.partials.every((value) => typeof discord.Partials[value] != "undefined"))
|
|
111
|
+
throw new base_1.ODSystemError("Client has non-existing partials!");
|
|
112
|
+
if (!this.permissions.every((value) => typeof discord.PermissionFlagsBits[value] != "undefined"))
|
|
113
|
+
throw new base_1.ODSystemError("Client has non-existing partials!");
|
|
114
|
+
const intents = this.intents.map((value) => discord.GatewayIntentBits[value]);
|
|
115
|
+
const partials = this.partials.map((value) => discord.Partials[value]);
|
|
116
|
+
const oldClient = this.client;
|
|
117
|
+
this.client = new discord.Client({ intents, partials });
|
|
118
|
+
//@ts-ignore
|
|
119
|
+
oldClient.eventNames().forEach((event) => {
|
|
120
|
+
//@ts-ignore
|
|
121
|
+
const callbacks = oldClient.rawListeners(event);
|
|
122
|
+
callbacks.forEach((cb) => {
|
|
123
|
+
this.client.on(event, cb);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
this.initiated = true;
|
|
127
|
+
this.#debug.debug("Created client with intents: " + this.intents.join(", "));
|
|
128
|
+
this.#debug.debug("Created client with privileged intents: " + this.privileges.join(", "));
|
|
129
|
+
this.#debug.debug("Created client with partials: " + this.partials.join(", "));
|
|
130
|
+
this.#debug.debug("Created client with permissions: " + this.permissions.join(", "));
|
|
131
|
+
}
|
|
132
|
+
/**Get all servers the bot is part of. */
|
|
133
|
+
async getGuilds() {
|
|
134
|
+
if (!this.initiated)
|
|
135
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
136
|
+
if (!this.ready)
|
|
137
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
138
|
+
return this.client.guilds.cache.map((guild) => guild);
|
|
139
|
+
}
|
|
140
|
+
/**Check if the bot is in a specific guild */
|
|
141
|
+
checkBotInGuild(guild) {
|
|
142
|
+
return (guild.members.me) ? true : false;
|
|
143
|
+
}
|
|
144
|
+
/**Check if a specific guild has all required permissions (or `Administrator`) */
|
|
145
|
+
checkGuildPerms(guild) {
|
|
146
|
+
if (!guild.members.me)
|
|
147
|
+
throw new base_1.ODSystemError("Client isn't a member in this server!");
|
|
148
|
+
const perms = guild.members.me.permissions;
|
|
149
|
+
if (perms.has("Administrator"))
|
|
150
|
+
return true;
|
|
151
|
+
else {
|
|
152
|
+
return this.permissions.every((perm) => {
|
|
153
|
+
return perms.has(perm);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**Log-in with a discord auth token. Rejects returns `false` using 'softErrors' on failure. */
|
|
158
|
+
login(softErrors) {
|
|
159
|
+
return new Promise(async (resolve, reject) => {
|
|
160
|
+
if (!this.initiated)
|
|
161
|
+
reject("Client isn't initiated yet!");
|
|
162
|
+
if (!this.token)
|
|
163
|
+
reject("Client doesn't have a token!");
|
|
164
|
+
try {
|
|
165
|
+
this.client.once("clientReady", async () => {
|
|
166
|
+
this.ready = true;
|
|
167
|
+
//set slashCommandManager & contextMenuManager to client applicationCommandManager
|
|
168
|
+
if (!this.client.application)
|
|
169
|
+
throw new base_1.ODSystemError("Couldn't get client application for slashCommand & contextMenu managers!");
|
|
170
|
+
this.slashCommands.commandManager = this.client.application.commands;
|
|
171
|
+
this.contextMenus.commandManager = this.client.application.commands;
|
|
172
|
+
this.autocompletes.commandManager = this.client.application.commands;
|
|
173
|
+
if (this.readyListener)
|
|
174
|
+
await this.readyListener();
|
|
175
|
+
resolve(true);
|
|
176
|
+
});
|
|
177
|
+
this.#debug.debug("Actual discord.js client.login()");
|
|
178
|
+
await this.client.login(this.token);
|
|
179
|
+
this.#debug.debug("Finished discord.js client.login()");
|
|
180
|
+
this.loggedIn = true;
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
if (softErrors)
|
|
184
|
+
return resolve(false);
|
|
185
|
+
else if (err.message.toLowerCase().includes("used disallowed intents")) {
|
|
186
|
+
process.emit("uncaughtException", new base_1.ODSystemError("Used disallowed intents"));
|
|
187
|
+
}
|
|
188
|
+
else if (err.message.toLowerCase().includes("tokeninvalid") || err.message.toLowerCase().includes("an invalid token was provided")) {
|
|
189
|
+
process.emit("uncaughtException", new base_1.ODSystemError("Invalid discord bot token provided"));
|
|
190
|
+
}
|
|
191
|
+
else
|
|
192
|
+
reject("OD Login Error: " + err);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**A simplified shortcut to get a `discord.User` :) */
|
|
197
|
+
async fetchUser(id) {
|
|
198
|
+
if (!this.initiated)
|
|
199
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
200
|
+
if (!this.ready)
|
|
201
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
202
|
+
try {
|
|
203
|
+
return await this.client.users.fetch(id);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**A simplified shortcut to get a `discord.Guild` :) */
|
|
210
|
+
async fetchGuild(id) {
|
|
211
|
+
if (!this.initiated)
|
|
212
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
213
|
+
if (!this.ready)
|
|
214
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
215
|
+
try {
|
|
216
|
+
return await this.client.guilds.fetch(id);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**A simplified shortcut to get a `discord.Channel` :) */
|
|
223
|
+
async fetchChannel(id) {
|
|
224
|
+
if (!this.initiated)
|
|
225
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
226
|
+
if (!this.ready)
|
|
227
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
228
|
+
try {
|
|
229
|
+
return await this.client.channels.fetch(id);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**A simplified shortcut to get a `discord.GuildBasedChannel` :) */
|
|
236
|
+
async fetchGuildChannel(guildId, id) {
|
|
237
|
+
if (!this.initiated)
|
|
238
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
239
|
+
if (!this.ready)
|
|
240
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
241
|
+
try {
|
|
242
|
+
const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId);
|
|
243
|
+
if (!guild)
|
|
244
|
+
return null;
|
|
245
|
+
const channel = await guild.channels.fetch(id);
|
|
246
|
+
return channel;
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**A simplified shortcut to get a `discord.TextChannel` :) */
|
|
253
|
+
async fetchGuildTextChannel(guildId, id) {
|
|
254
|
+
if (!this.initiated)
|
|
255
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
256
|
+
if (!this.ready)
|
|
257
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
258
|
+
try {
|
|
259
|
+
const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId);
|
|
260
|
+
if (!guild)
|
|
261
|
+
return null;
|
|
262
|
+
const channel = await guild.channels.fetch(id);
|
|
263
|
+
if (!channel || channel.type != discord.ChannelType.GuildText)
|
|
264
|
+
return null;
|
|
265
|
+
return channel;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**A simplified shortcut to get a `discord.CategoryChannel` :) */
|
|
272
|
+
async fetchGuildCategoryChannel(guildId, id) {
|
|
273
|
+
if (!this.initiated)
|
|
274
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
275
|
+
if (!this.ready)
|
|
276
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
277
|
+
try {
|
|
278
|
+
const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId);
|
|
279
|
+
if (!guild)
|
|
280
|
+
return null;
|
|
281
|
+
const channel = await guild.channels.fetch(id);
|
|
282
|
+
if (!channel || channel.type != discord.ChannelType.GuildCategory)
|
|
283
|
+
return null;
|
|
284
|
+
return channel;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**A simplified shortcut to get a `discord.GuildMember` :) */
|
|
291
|
+
async fetchGuildMember(guildId, id) {
|
|
292
|
+
if (!this.initiated)
|
|
293
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
294
|
+
if (!this.ready)
|
|
295
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
296
|
+
if (typeof id != "string")
|
|
297
|
+
throw new base_1.ODSystemError("TEMP ERROR => ODClientManager.fetchGuildMember() => id param isn't string");
|
|
298
|
+
try {
|
|
299
|
+
const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId);
|
|
300
|
+
if (!guild)
|
|
301
|
+
return null;
|
|
302
|
+
return await guild.members.fetch(id);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**A simplified shortcut to get a `discord.Role` :) */
|
|
309
|
+
async fetchGuildRole(guildId, id) {
|
|
310
|
+
if (!this.initiated)
|
|
311
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
312
|
+
if (!this.ready)
|
|
313
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
314
|
+
if (typeof id != "string")
|
|
315
|
+
throw new base_1.ODSystemError("TEMP ERROR => ODClientManager.fetchGuildRole() => id param isn't string");
|
|
316
|
+
try {
|
|
317
|
+
const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId);
|
|
318
|
+
if (!guild)
|
|
319
|
+
return null;
|
|
320
|
+
return await guild.roles.fetch(id);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async fetchGuildChannelMessage(guildId, channelId, id) {
|
|
327
|
+
if (!this.initiated)
|
|
328
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
329
|
+
if (!this.ready)
|
|
330
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
331
|
+
try {
|
|
332
|
+
if (guildId instanceof discord.TextChannel && typeof channelId == "string") {
|
|
333
|
+
const channel = guildId;
|
|
334
|
+
return await channel.messages.fetch(channelId);
|
|
335
|
+
}
|
|
336
|
+
else if (!(guildId instanceof discord.TextChannel) && id) {
|
|
337
|
+
const channel = (channelId instanceof discord.TextChannel) ? channelId : await this.fetchGuildTextChannel(guildId, channelId);
|
|
338
|
+
if (!channel)
|
|
339
|
+
return null;
|
|
340
|
+
return await channel.messages.fetch(id);
|
|
341
|
+
}
|
|
342
|
+
else
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**A simplified shortcut to send a DM to a user :) */
|
|
350
|
+
async sendUserDm(user, message) {
|
|
351
|
+
if (!this.initiated)
|
|
352
|
+
throw new base_1.ODSystemError("Client isn't initiated yet!");
|
|
353
|
+
if (!this.ready)
|
|
354
|
+
throw new base_1.ODSystemError("Client isn't ready yet!");
|
|
355
|
+
try {
|
|
356
|
+
if (user instanceof discord.User) {
|
|
357
|
+
if (user.bot)
|
|
358
|
+
return { success: false, message: null };
|
|
359
|
+
const channel = await user.createDM();
|
|
360
|
+
const msg = await channel.send(message.message);
|
|
361
|
+
return { success: true, message: msg };
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
const newUser = await this.fetchUser(user);
|
|
365
|
+
if (!newUser)
|
|
366
|
+
throw new Error();
|
|
367
|
+
if (newUser.bot)
|
|
368
|
+
return { success: false, message: null };
|
|
369
|
+
const channel = await newUser.createDM();
|
|
370
|
+
const msg = await channel.send(message.message);
|
|
371
|
+
return { success: true, message: msg };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
try {
|
|
376
|
+
this.#debug.console.log("Failed to send DM to user! ", "warning", [
|
|
377
|
+
{ key: "id", value: (user instanceof discord.User ? user.id : user) },
|
|
378
|
+
{ key: "message", value: message.id.value }
|
|
379
|
+
]);
|
|
380
|
+
}
|
|
381
|
+
catch { }
|
|
382
|
+
return { success: false, message: null };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
exports.ODClientManager = ODClientManager;
|
|
387
|
+
/**## ODClientActivityManager `class`
|
|
388
|
+
* This is an Open Discord client activity manager.
|
|
389
|
+
*
|
|
390
|
+
* It's responsible for managing the client status. Here, you can set the activity & status of the bot.
|
|
391
|
+
*
|
|
392
|
+
* It also has a built-in refresh function, so the status will refresh every 10 minutes to keep it visible.
|
|
393
|
+
*/
|
|
394
|
+
class ODClientActivityManager {
|
|
395
|
+
/**Alias to Open Discord debugger. */
|
|
396
|
+
#debug;
|
|
397
|
+
/**Copy of discord.js client */
|
|
398
|
+
manager;
|
|
399
|
+
/**The current status type */
|
|
400
|
+
type = false;
|
|
401
|
+
/**The current status text */
|
|
402
|
+
text = "";
|
|
403
|
+
/**The current status mode */
|
|
404
|
+
mode = "online";
|
|
405
|
+
/**Additional state text */
|
|
406
|
+
state = "";
|
|
407
|
+
/**The timer responsible for refreshing the status. Stop it using `clearInterval(interval)` */
|
|
408
|
+
interval;
|
|
409
|
+
/**status refresh interval in seconds (5 minutes by default)*/
|
|
410
|
+
refreshInterval = 600;
|
|
411
|
+
/**Is the status already initiated? */
|
|
412
|
+
initiated = false;
|
|
413
|
+
constructor(debug, manager) {
|
|
414
|
+
this.#debug = debug;
|
|
415
|
+
this.manager = manager;
|
|
416
|
+
}
|
|
417
|
+
/**Update the status. When already initiated, it can take up to 10min to see the updated status in discord. */
|
|
418
|
+
setStatus(type, text, mode, state, forceUpdate) {
|
|
419
|
+
this.type = type;
|
|
420
|
+
this.text = text;
|
|
421
|
+
this.mode = mode;
|
|
422
|
+
this.state = state;
|
|
423
|
+
if (forceUpdate)
|
|
424
|
+
this.#updateClientActivity(this.type, this.text);
|
|
425
|
+
}
|
|
426
|
+
/**When initiating the status, the bot starts updating the status using `discord.js`. Returns `true` when successfull. */
|
|
427
|
+
initStatus() {
|
|
428
|
+
if (this.initiated || !this.manager.ready)
|
|
429
|
+
return false;
|
|
430
|
+
this.#updateClientActivity(this.type, this.text);
|
|
431
|
+
this.interval = setInterval(() => {
|
|
432
|
+
this.#updateClientActivity(this.type, this.text);
|
|
433
|
+
this.#debug.debug("Client status update cycle");
|
|
434
|
+
}, this.refreshInterval * 1000);
|
|
435
|
+
this.initiated = true;
|
|
436
|
+
this.#debug.debug("Client status initiated");
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
/**Update the client status */
|
|
440
|
+
#updateClientActivity(type, text) {
|
|
441
|
+
if (!this.manager.client.user)
|
|
442
|
+
throw new base_1.ODSystemError("Couldn't set client status: client.user == undefined");
|
|
443
|
+
if (type == false) {
|
|
444
|
+
this.manager.client.user.setActivity();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
this.manager.client.user.setPresence({
|
|
448
|
+
activities: [{
|
|
449
|
+
type: this.#getStatusTypeEnum(type),
|
|
450
|
+
state: this.state ? this.state : undefined,
|
|
451
|
+
name: text,
|
|
452
|
+
}],
|
|
453
|
+
status: this.mode
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/**Get the enum that links to the correct type */
|
|
457
|
+
#getStatusTypeEnum(type) {
|
|
458
|
+
if (type == "playing")
|
|
459
|
+
return discord.ActivityType.Playing;
|
|
460
|
+
else if (type == "listening")
|
|
461
|
+
return discord.ActivityType.Listening;
|
|
462
|
+
else if (type == "watching")
|
|
463
|
+
return discord.ActivityType.Watching;
|
|
464
|
+
else if (type == "custom")
|
|
465
|
+
return discord.ActivityType.Custom;
|
|
466
|
+
else
|
|
467
|
+
return discord.ActivityType.Listening;
|
|
468
|
+
}
|
|
469
|
+
/**Get the status type (for displaying the status) */
|
|
470
|
+
getStatusType() {
|
|
471
|
+
if (this.type == "listening" || this.type == "playing" || this.type == "watching")
|
|
472
|
+
return this.type + " ";
|
|
473
|
+
else
|
|
474
|
+
return "";
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
exports.ODClientActivityManager = ODClientActivityManager;
|
|
478
|
+
/**## ODSlashCommandComparator `class`
|
|
479
|
+
* A utility class to compare existing slash commands with newly registered ones.
|
|
480
|
+
*/
|
|
481
|
+
class ODSlashCommandComparator {
|
|
482
|
+
/**Convert a `discord.ApplicationCommandOptionChoiceData<string>` to a universal Open Discord slash command option choice object for comparison. */
|
|
483
|
+
#convertOptionChoice(choice) {
|
|
484
|
+
const nameLoc = choice.nameLocalizations ?? {};
|
|
485
|
+
return {
|
|
486
|
+
name: choice.name,
|
|
487
|
+
nameLocalizations: Object.keys(nameLoc).map((key) => { return { language: key, value: nameLoc[key] }; }),
|
|
488
|
+
value: choice.value
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/**Convert a `discord.ApplicationCommandOptionData` to a universal Open Discord slash command option object for comparison. */
|
|
492
|
+
#convertBuilderOption(option) {
|
|
493
|
+
const nameLoc = option.nameLocalizations ?? {};
|
|
494
|
+
const descLoc = option.descriptionLocalizations ?? {};
|
|
495
|
+
return {
|
|
496
|
+
type: option.type,
|
|
497
|
+
name: option.name,
|
|
498
|
+
nameLocalizations: Object.keys(nameLoc).map((key) => { return { language: key, value: nameLoc[key] }; }),
|
|
499
|
+
description: option.description,
|
|
500
|
+
descriptionLocalizations: Object.keys(descLoc).map((key) => { return { language: key, value: descLoc[key] }; }),
|
|
501
|
+
required: (option.type != discord.ApplicationCommandOptionType.SubcommandGroup && option.type != discord.ApplicationCommandOptionType.Subcommand && option.required) ? true : false,
|
|
502
|
+
autocomplete: option.autocomplete ?? false,
|
|
503
|
+
choices: (option.type == discord.ApplicationCommandOptionType.String && !option.autocomplete && option.choices) ? option.choices.map((choice) => this.#convertOptionChoice(choice)) : [],
|
|
504
|
+
options: ((option.type == discord.ApplicationCommandOptionType.SubcommandGroup || option.type == discord.ApplicationCommandOptionType.Subcommand) && option.options) ? option.options.map((opt) => this.#convertBuilderOption(opt)) : [],
|
|
505
|
+
channelTypes: (option.type == discord.ApplicationCommandOptionType.Channel && option.channelTypes) ? option.channelTypes : [],
|
|
506
|
+
minValue: (option.type == discord.ApplicationCommandOptionType.Number && option.minValue) ? option.minValue : null,
|
|
507
|
+
maxValue: (option.type == discord.ApplicationCommandOptionType.Number && option.maxValue) ? option.maxValue : null,
|
|
508
|
+
minLength: (option.type == discord.ApplicationCommandOptionType.String && option.minLength) ? option.minLength : null,
|
|
509
|
+
maxLength: (option.type == discord.ApplicationCommandOptionType.String && option.maxLength) ? option.maxLength : null
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
/**Convert a `discord.ApplicationCommandOption` to a universal Open Discord slash command option object for comparison. */
|
|
513
|
+
#convertCommandOption(option) {
|
|
514
|
+
const nameLoc = option.nameLocalizations ?? {};
|
|
515
|
+
const descLoc = option.descriptionLocalizations ?? {};
|
|
516
|
+
return {
|
|
517
|
+
type: option.type,
|
|
518
|
+
name: option.name,
|
|
519
|
+
nameLocalizations: Object.keys(nameLoc).map((key) => { return { language: key, value: nameLoc[key] }; }),
|
|
520
|
+
description: option.description,
|
|
521
|
+
descriptionLocalizations: Object.keys(descLoc).map((key) => { return { language: key, value: descLoc[key] }; }),
|
|
522
|
+
required: (option.type != discord.ApplicationCommandOptionType.SubcommandGroup && option.type != discord.ApplicationCommandOptionType.Subcommand && option.required) ? true : false,
|
|
523
|
+
autocomplete: option.autocomplete ?? false,
|
|
524
|
+
choices: (option.type == discord.ApplicationCommandOptionType.String && !option.autocomplete && option.choices) ? option.choices.map((choice) => this.#convertOptionChoice(choice)) : [],
|
|
525
|
+
options: ((option.type == discord.ApplicationCommandOptionType.SubcommandGroup || option.type == discord.ApplicationCommandOptionType.Subcommand) && option.options) ? option.options.map((opt) => this.#convertBuilderOption(opt)) : [],
|
|
526
|
+
channelTypes: (option.type == discord.ApplicationCommandOptionType.Channel && option.channelTypes) ? option.channelTypes : [],
|
|
527
|
+
minValue: (option.type == discord.ApplicationCommandOptionType.Number && option.minValue) ? option.minValue : null,
|
|
528
|
+
maxValue: (option.type == discord.ApplicationCommandOptionType.Number && option.maxValue) ? option.maxValue : null,
|
|
529
|
+
minLength: (option.type == discord.ApplicationCommandOptionType.String && option.minLength) ? option.minLength : null,
|
|
530
|
+
maxLength: (option.type == discord.ApplicationCommandOptionType.String && option.maxLength) ? option.maxLength : null
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
/**Convert a `ODSlashCommandBuilder` to a universal Open Discord slash command object for comparison. */
|
|
534
|
+
convertBuilder(builder, guildId) {
|
|
535
|
+
if (builder.type != discord.ApplicationCommandType.ChatInput)
|
|
536
|
+
return null; //throw new ODSystemError("ODSlashCommandComparator:convertBuilder() is not supported for other types than 'ChatInput'!")
|
|
537
|
+
const nameLoc = builder.nameLocalizations ?? {};
|
|
538
|
+
const descLoc = builder.descriptionLocalizations ?? {};
|
|
539
|
+
return {
|
|
540
|
+
type: 1,
|
|
541
|
+
name: builder.name,
|
|
542
|
+
nameLocalizations: Object.keys(nameLoc).map((key) => { return { language: key, value: nameLoc[key] }; }),
|
|
543
|
+
description: builder.description,
|
|
544
|
+
descriptionLocalizations: Object.keys(descLoc).map((key) => { return { language: key, value: descLoc[key] }; }),
|
|
545
|
+
guildId: guildId,
|
|
546
|
+
nsfw: builder.nsfw ?? false,
|
|
547
|
+
options: builder.options ? builder.options.map((opt) => this.#convertBuilderOption(opt)) : [],
|
|
548
|
+
defaultMemberPermissions: discord.PermissionsBitField.resolve(builder.defaultMemberPermissions ?? ["ViewChannel"]),
|
|
549
|
+
dmPermission: (builder.contexts && builder.contexts.includes(discord.InteractionContextType.BotDM)) ?? false,
|
|
550
|
+
integrationTypes: builder.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall],
|
|
551
|
+
contexts: builder.contexts ?? []
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
/**Convert a `discord.ApplicationCommand` to a universal Open Discord slash command object for comparison. */
|
|
555
|
+
convertCommand(cmd) {
|
|
556
|
+
if (cmd.type != discord.ApplicationCommandType.ChatInput)
|
|
557
|
+
return null; //throw new ODSystemError("ODSlashCommandComparator:convertCommand() is not supported for other types than 'ChatInput'!")
|
|
558
|
+
const nameLoc = cmd.nameLocalizations ?? {};
|
|
559
|
+
const descLoc = cmd.descriptionLocalizations ?? {};
|
|
560
|
+
return {
|
|
561
|
+
type: 1,
|
|
562
|
+
name: cmd.name,
|
|
563
|
+
nameLocalizations: Object.keys(nameLoc).map((key) => { return { language: key, value: nameLoc[key] }; }),
|
|
564
|
+
description: cmd.description,
|
|
565
|
+
descriptionLocalizations: Object.keys(descLoc).map((key) => { return { language: key, value: descLoc[key] }; }),
|
|
566
|
+
guildId: cmd.guildId,
|
|
567
|
+
nsfw: cmd.nsfw,
|
|
568
|
+
options: cmd.options ? cmd.options.map((opt) => this.#convertCommandOption(opt)) : [],
|
|
569
|
+
defaultMemberPermissions: discord.PermissionsBitField.resolve(cmd.defaultMemberPermissions ?? ["ViewChannel"]),
|
|
570
|
+
dmPermission: (cmd.contexts && cmd.contexts.includes(discord.InteractionContextType.BotDM)) ? true : false,
|
|
571
|
+
integrationTypes: cmd.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall],
|
|
572
|
+
contexts: cmd.contexts ?? []
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
/**Returns `true` when the 2 slash command options are the same. */
|
|
576
|
+
compareOption(optA, optB) {
|
|
577
|
+
if (optA.name != optB.name)
|
|
578
|
+
return false;
|
|
579
|
+
if (optA.description != optB.description)
|
|
580
|
+
return false;
|
|
581
|
+
if (optA.type != optB.type)
|
|
582
|
+
return false;
|
|
583
|
+
if (optA.required != optB.required)
|
|
584
|
+
return false;
|
|
585
|
+
if (optA.autocomplete != optB.autocomplete)
|
|
586
|
+
return false;
|
|
587
|
+
if (optA.minValue != optB.minValue)
|
|
588
|
+
return false;
|
|
589
|
+
if (optA.maxValue != optB.maxValue)
|
|
590
|
+
return false;
|
|
591
|
+
if (optA.minLength != optB.minLength)
|
|
592
|
+
return false;
|
|
593
|
+
if (optA.maxLength != optB.maxLength)
|
|
594
|
+
return false;
|
|
595
|
+
//nameLocalizations
|
|
596
|
+
if (optA.nameLocalizations.length != optB.nameLocalizations.length)
|
|
597
|
+
return false;
|
|
598
|
+
if (!optA.nameLocalizations.every((nameA) => {
|
|
599
|
+
const nameB = optB.nameLocalizations.find((nameB) => nameB.language == nameA.language);
|
|
600
|
+
if (!nameB || nameA.value != nameB.value)
|
|
601
|
+
return false;
|
|
602
|
+
else
|
|
603
|
+
return true;
|
|
604
|
+
}))
|
|
605
|
+
return false;
|
|
606
|
+
//descriptionLocalizations
|
|
607
|
+
if (optA.descriptionLocalizations.length != optB.descriptionLocalizations.length)
|
|
608
|
+
return false;
|
|
609
|
+
if (!optA.descriptionLocalizations.every((descA) => {
|
|
610
|
+
const descB = optB.descriptionLocalizations.find((descB) => descB.language == descA.language);
|
|
611
|
+
if (!descB || descA.value != descB.value)
|
|
612
|
+
return false;
|
|
613
|
+
else
|
|
614
|
+
return true;
|
|
615
|
+
}))
|
|
616
|
+
return false;
|
|
617
|
+
//choices
|
|
618
|
+
if (optA.choices.length != optB.choices.length)
|
|
619
|
+
return false;
|
|
620
|
+
if (!optA.choices.every((choiceA, index) => {
|
|
621
|
+
const choiceB = optB.choices[index];
|
|
622
|
+
if (choiceA.name != choiceB.name)
|
|
623
|
+
return false;
|
|
624
|
+
if (choiceA.value != choiceB.value)
|
|
625
|
+
return false;
|
|
626
|
+
//nameLocalizations
|
|
627
|
+
if (choiceA.nameLocalizations.length != choiceB.nameLocalizations.length)
|
|
628
|
+
return false;
|
|
629
|
+
if (!choiceA.nameLocalizations.every((nameA) => {
|
|
630
|
+
const nameB = choiceB.nameLocalizations.find((nameB) => nameB.language == nameA.language);
|
|
631
|
+
if (!nameB || nameA.value != nameB.value)
|
|
632
|
+
return false;
|
|
633
|
+
else
|
|
634
|
+
return true;
|
|
635
|
+
}))
|
|
636
|
+
return false;
|
|
637
|
+
return true;
|
|
638
|
+
}))
|
|
639
|
+
return false;
|
|
640
|
+
//channelTypes
|
|
641
|
+
if (optA.channelTypes.length != optB.channelTypes.length)
|
|
642
|
+
return false;
|
|
643
|
+
if (!optA.channelTypes.every((typeA) => {
|
|
644
|
+
return optB.channelTypes.includes(typeA);
|
|
645
|
+
}))
|
|
646
|
+
return false;
|
|
647
|
+
//options
|
|
648
|
+
if (optA.options.length != optB.options.length)
|
|
649
|
+
return false;
|
|
650
|
+
if (!optA.options.every((subOptA, index) => {
|
|
651
|
+
return this.compareOption(subOptA, optB.options[index]);
|
|
652
|
+
}))
|
|
653
|
+
return false;
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
/**Returns `true` when the 2 slash commands are the same. */
|
|
657
|
+
compare(cmdA, cmdB) {
|
|
658
|
+
if (cmdA.name != cmdB.name)
|
|
659
|
+
return false;
|
|
660
|
+
if (cmdA.description != cmdB.description)
|
|
661
|
+
return false;
|
|
662
|
+
if (cmdA.type != cmdB.type)
|
|
663
|
+
return false;
|
|
664
|
+
if (cmdA.nsfw != cmdB.nsfw)
|
|
665
|
+
return false;
|
|
666
|
+
if (cmdA.guildId != cmdB.guildId)
|
|
667
|
+
return false;
|
|
668
|
+
if (cmdA.dmPermission != cmdB.dmPermission)
|
|
669
|
+
return false;
|
|
670
|
+
if (cmdA.defaultMemberPermissions != cmdB.defaultMemberPermissions)
|
|
671
|
+
return false;
|
|
672
|
+
//nameLocalizations
|
|
673
|
+
if (cmdA.nameLocalizations.length != cmdB.nameLocalizations.length)
|
|
674
|
+
return false;
|
|
675
|
+
if (!cmdA.nameLocalizations.every((nameA) => {
|
|
676
|
+
const nameB = cmdB.nameLocalizations.find((nameB) => nameB.language == nameA.language);
|
|
677
|
+
if (!nameB || nameA.value != nameB.value)
|
|
678
|
+
return false;
|
|
679
|
+
else
|
|
680
|
+
return true;
|
|
681
|
+
}))
|
|
682
|
+
return false;
|
|
683
|
+
//descriptionLocalizations
|
|
684
|
+
if (cmdA.descriptionLocalizations.length != cmdB.descriptionLocalizations.length)
|
|
685
|
+
return false;
|
|
686
|
+
if (!cmdA.descriptionLocalizations.every((descA) => {
|
|
687
|
+
const descB = cmdB.descriptionLocalizations.find((descB) => descB.language == descA.language);
|
|
688
|
+
if (!descB || descA.value != descB.value)
|
|
689
|
+
return false;
|
|
690
|
+
else
|
|
691
|
+
return true;
|
|
692
|
+
}))
|
|
693
|
+
return false;
|
|
694
|
+
//contexts
|
|
695
|
+
if (cmdA.contexts.length != cmdB.contexts.length)
|
|
696
|
+
return false;
|
|
697
|
+
if (!cmdA.contexts.every((contextA) => {
|
|
698
|
+
return cmdB.contexts.includes(contextA);
|
|
699
|
+
}))
|
|
700
|
+
return false;
|
|
701
|
+
//integrationTypes
|
|
702
|
+
if (cmdA.integrationTypes.length != cmdB.integrationTypes.length)
|
|
703
|
+
return false;
|
|
704
|
+
if (!cmdA.integrationTypes.every((integrationA) => {
|
|
705
|
+
return cmdB.integrationTypes.includes(integrationA);
|
|
706
|
+
}))
|
|
707
|
+
return false;
|
|
708
|
+
//options
|
|
709
|
+
if (cmdA.options.length != cmdB.options.length)
|
|
710
|
+
return false;
|
|
711
|
+
if (!cmdA.options.every((optA, index) => {
|
|
712
|
+
return this.compareOption(optA, cmdB.options[index]);
|
|
713
|
+
}))
|
|
714
|
+
return false;
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
exports.ODSlashCommandComparator = ODSlashCommandComparator;
|
|
719
|
+
/**## ODSlashCommandManager `class`
|
|
720
|
+
* This is an Open Discord client slash manager.
|
|
721
|
+
*
|
|
722
|
+
* It's responsible for managing all the slash commands from the client.
|
|
723
|
+
*
|
|
724
|
+
* Here, you can add & remove slash commands & the bot will do the (de)registering.
|
|
725
|
+
*/
|
|
726
|
+
class ODSlashCommandManager extends base_1.ODManager {
|
|
727
|
+
/**Alias to Open Discord debugger. */
|
|
728
|
+
#debug;
|
|
729
|
+
/**Refrerence to discord.js client. */
|
|
730
|
+
manager;
|
|
731
|
+
/**Discord.js application commands manager. */
|
|
732
|
+
commandManager;
|
|
733
|
+
/**Collection of all interaction listeners. */
|
|
734
|
+
#interactionListeners = [];
|
|
735
|
+
/**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */
|
|
736
|
+
listenerLimit = 100;
|
|
737
|
+
/**A utility class used to compare 2 slash commands with each other. */
|
|
738
|
+
comparator = new ODSlashCommandComparator();
|
|
739
|
+
constructor(debug, manager) {
|
|
740
|
+
super(debug, "slash command");
|
|
741
|
+
this.#debug = debug;
|
|
742
|
+
this.manager = manager;
|
|
743
|
+
this.commandManager = (manager.client.application) ? manager.client.application.commands : null;
|
|
744
|
+
}
|
|
745
|
+
/**Get all registered & unregistered slash commands. */
|
|
746
|
+
async getAllRegisteredCommands(guildId) {
|
|
747
|
+
if (!this.commandManager)
|
|
748
|
+
throw new base_1.ODSystemError("Couldn't get client application to register slash commands!");
|
|
749
|
+
const cmds = (await this.commandManager.fetch({ guildId })).toJSON();
|
|
750
|
+
const registered = [];
|
|
751
|
+
const unregistered = [];
|
|
752
|
+
const unused = [];
|
|
753
|
+
await this.loopAll((instance) => {
|
|
754
|
+
if (guildId && instance.guildId != guildId)
|
|
755
|
+
return;
|
|
756
|
+
const index = cmds.findIndex((cmd) => cmd.name == instance.name);
|
|
757
|
+
const cmd = cmds[index];
|
|
758
|
+
cmds.splice(index, 1);
|
|
759
|
+
if (cmd) {
|
|
760
|
+
//command is registered (and may need to be updated)
|
|
761
|
+
const universalBuilder = this.comparator.convertBuilder(instance.builder, instance.guildId);
|
|
762
|
+
const universalCmd = this.comparator.convertCommand(cmd);
|
|
763
|
+
//command is not of the type 'chatinput'
|
|
764
|
+
if (!universalBuilder || !universalCmd)
|
|
765
|
+
return;
|
|
766
|
+
const didChange = !this.comparator.compare(universalBuilder, universalCmd);
|
|
767
|
+
const requiresUpdate = didChange || (instance.requiresUpdate ? instance.requiresUpdate(universalCmd) : false);
|
|
768
|
+
registered.push({ instance, cmd: universalCmd, requiresUpdate });
|
|
769
|
+
//command is not registered
|
|
770
|
+
}
|
|
771
|
+
else
|
|
772
|
+
unregistered.push({ instance, cmd: null, requiresUpdate: true });
|
|
773
|
+
});
|
|
774
|
+
cmds.forEach((cmd) => {
|
|
775
|
+
//command does not exist in the manager (only append to unused when type == 'chatinput')
|
|
776
|
+
const universalCmd = this.comparator.convertCommand(cmd);
|
|
777
|
+
if (!universalCmd)
|
|
778
|
+
return;
|
|
779
|
+
unused.push({ instance: null, cmd: universalCmd, requiresUpdate: false });
|
|
780
|
+
});
|
|
781
|
+
return { registered, unregistered, unused };
|
|
782
|
+
}
|
|
783
|
+
/**Create all commands that are not registered yet.*/
|
|
784
|
+
async createNewCommands(instances, progress) {
|
|
785
|
+
if (!this.manager.ready)
|
|
786
|
+
throw new base_1.ODSystemError("Client isn't ready yet! Unable to register slash commands!");
|
|
787
|
+
if (instances.length > 0 && progress) {
|
|
788
|
+
progress.max = instances.length;
|
|
789
|
+
progress.start();
|
|
790
|
+
}
|
|
791
|
+
for (const instance of instances) {
|
|
792
|
+
await this.createCmd(instance);
|
|
793
|
+
this.#debug.debug("Created new slash command", [
|
|
794
|
+
{ key: "id", value: instance.id.value },
|
|
795
|
+
{ key: "name", value: instance.name }
|
|
796
|
+
]);
|
|
797
|
+
if (progress)
|
|
798
|
+
progress.increase(1);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
/**Update all commands that are already registered. */
|
|
802
|
+
async updateExistingCommands(instances, progress) {
|
|
803
|
+
if (!this.manager.ready)
|
|
804
|
+
throw new base_1.ODSystemError("Client isn't ready yet! Unable to register slash commands!");
|
|
805
|
+
if (instances.length > 0 && progress) {
|
|
806
|
+
progress.max = instances.length;
|
|
807
|
+
progress.start();
|
|
808
|
+
}
|
|
809
|
+
for (const instance of instances) {
|
|
810
|
+
await this.createCmd(instance);
|
|
811
|
+
this.#debug.debug("Updated existing slash command", [{ key: "id", value: instance.id.value }, { key: "name", value: instance.name }]);
|
|
812
|
+
if (progress)
|
|
813
|
+
progress.increase(1);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
/**Remove all commands that are registered but unused by Open Discord. */
|
|
817
|
+
async removeUnusedCommands(instances, guildId, progress) {
|
|
818
|
+
if (!this.manager.ready)
|
|
819
|
+
throw new base_1.ODSystemError("Client isn't ready yet! Unable to register slash commands!");
|
|
820
|
+
if (!this.commandManager)
|
|
821
|
+
throw new base_1.ODSystemError("Couldn't get client application to register slash commands!");
|
|
822
|
+
if (instances.length > 0 && progress) {
|
|
823
|
+
progress.max = instances.length;
|
|
824
|
+
progress.start();
|
|
825
|
+
}
|
|
826
|
+
const cmds = await this.commandManager.fetch({ guildId });
|
|
827
|
+
for (const instance of instances) {
|
|
828
|
+
const cmd = cmds.find((cmd) => cmd.name == instance.name);
|
|
829
|
+
if (cmd) {
|
|
830
|
+
try {
|
|
831
|
+
await cmd.delete();
|
|
832
|
+
this.#debug.debug("Removed existing slash command", [{ key: "name", value: cmd.name }, { key: "guildId", value: guildId ?? "/" }]);
|
|
833
|
+
}
|
|
834
|
+
catch (err) {
|
|
835
|
+
process.emit("uncaughtException", err);
|
|
836
|
+
throw new base_1.ODSystemError("Failed to delete slash command '/" + cmd.name + "'!");
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (progress)
|
|
840
|
+
progress.increase(1);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
/**Create a slash command. **(SYSTEM ONLY)** => Use `ODSlashCommandManager` for registering commands the default way! */
|
|
844
|
+
async createCmd(cmd) {
|
|
845
|
+
if (!this.commandManager)
|
|
846
|
+
throw new base_1.ODSystemError("Couldn't get client application to register slash commands!");
|
|
847
|
+
try {
|
|
848
|
+
await this.commandManager.create(cmd.builder, (cmd.guildId ?? undefined));
|
|
849
|
+
}
|
|
850
|
+
catch (err) {
|
|
851
|
+
process.emit("uncaughtException", err);
|
|
852
|
+
throw new base_1.ODSystemError("Failed to register slash command '/" + cmd.name + "'!");
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**Start listening to the discord.js client `interactionCreate` event. */
|
|
856
|
+
startListeningToInteractions() {
|
|
857
|
+
this.manager.client.on("interactionCreate", (interaction) => {
|
|
858
|
+
//return when not in main server or DM
|
|
859
|
+
if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id))
|
|
860
|
+
return;
|
|
861
|
+
if (!interaction.isChatInputCommand())
|
|
862
|
+
return;
|
|
863
|
+
const cmd = this.getFiltered((cmd) => cmd.name == interaction.commandName)[0];
|
|
864
|
+
if (!cmd)
|
|
865
|
+
return;
|
|
866
|
+
this.#interactionListeners.forEach((listener) => {
|
|
867
|
+
if (typeof listener.name == "string" && (interaction.commandName != listener.name))
|
|
868
|
+
return;
|
|
869
|
+
else if (listener.name instanceof RegExp && !listener.name.test(interaction.commandName))
|
|
870
|
+
return;
|
|
871
|
+
//this is a valid listener
|
|
872
|
+
listener.callback(interaction, cmd);
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
/**Callback on interaction from one or multiple slash commands. */
|
|
877
|
+
onInteraction(commandName, callback) {
|
|
878
|
+
this.#interactionListeners.push({
|
|
879
|
+
name: commandName,
|
|
880
|
+
callback
|
|
881
|
+
});
|
|
882
|
+
if (this.#interactionListeners.length > this.listenerLimit) {
|
|
883
|
+
this.#debug.console.log(new console_1.ODConsoleWarningMessage("Possible slash command interaction memory leak detected!", [
|
|
884
|
+
{ key: "listeners", value: this.#interactionListeners.length.toString() }
|
|
885
|
+
]));
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
exports.ODSlashCommandManager = ODSlashCommandManager;
|
|
890
|
+
/**## ODSlashCommand `class`
|
|
891
|
+
* This is an Open Discord slash command.
|
|
892
|
+
*
|
|
893
|
+
* When registered, you can listen for this command using the `ODCommandResponder`. The advantages of using this class for creating a slash command are:
|
|
894
|
+
* - automatic option parsing (even for channels, users, roles & mentions)!
|
|
895
|
+
* - automatic registration in discord.js
|
|
896
|
+
* - error reporting to the user when the bot fails to respond
|
|
897
|
+
* - plugins can extend this command
|
|
898
|
+
* - the bot won't re-register the command when it already exists (except when requested)!
|
|
899
|
+
*
|
|
900
|
+
* And more!
|
|
901
|
+
*/
|
|
902
|
+
class ODSlashCommand extends base_1.ODManagerData {
|
|
903
|
+
/**The discord.js builder for this slash command. */
|
|
904
|
+
builder;
|
|
905
|
+
/**The id of the guild this command is for. Null when not set. */
|
|
906
|
+
guildId;
|
|
907
|
+
/**Function to check if the slash command requires to be updated (when it already exists). */
|
|
908
|
+
requiresUpdate = null;
|
|
909
|
+
constructor(id, builder, requiresUpdate, guildId) {
|
|
910
|
+
super(id);
|
|
911
|
+
if (builder.type != discord.ApplicationCommandType.ChatInput)
|
|
912
|
+
throw new base_1.ODSystemError("ApplicationCommandData is required to be the 'ChatInput' type!");
|
|
913
|
+
this.builder = builder;
|
|
914
|
+
this.guildId = guildId ?? null;
|
|
915
|
+
this.requiresUpdate = requiresUpdate ?? null;
|
|
916
|
+
}
|
|
917
|
+
/**The name of this slash command. */
|
|
918
|
+
get name() {
|
|
919
|
+
return this.builder.name;
|
|
920
|
+
}
|
|
921
|
+
set name(name) {
|
|
922
|
+
this.builder.name = name;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
exports.ODSlashCommand = ODSlashCommand;
|
|
926
|
+
/**## ODTextCommand `class`
|
|
927
|
+
* This is an Open Discord text command.
|
|
928
|
+
*
|
|
929
|
+
* When registered, you can listen for this command using the `ODCommandResponder`. The advantages of using this class for creating a text command are:
|
|
930
|
+
* - automatic option parsing (even for channels, users, roles & mentions)!
|
|
931
|
+
* - automatic errors on invalid parameters
|
|
932
|
+
* - error reporting to the user when the bot fails to respond
|
|
933
|
+
* - plugins can extend this command
|
|
934
|
+
*
|
|
935
|
+
* And more!
|
|
936
|
+
*/
|
|
937
|
+
class ODTextCommand extends base_1.ODManagerData {
|
|
938
|
+
/**The builder for this slash command. */
|
|
939
|
+
builder;
|
|
940
|
+
/**The name of this slash command. */
|
|
941
|
+
name;
|
|
942
|
+
constructor(id, builder) {
|
|
943
|
+
super(id);
|
|
944
|
+
this.builder = builder;
|
|
945
|
+
this.name = builder.name;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
exports.ODTextCommand = ODTextCommand;
|
|
949
|
+
/**## ODTextCommandManager `class`
|
|
950
|
+
* This is an Open Discord client text manager.
|
|
951
|
+
*
|
|
952
|
+
* It's responsible for managing all the text commands from the client.
|
|
953
|
+
*
|
|
954
|
+
* Here, you can add & remove text commands & the bot will do the (de)registering.
|
|
955
|
+
*/
|
|
956
|
+
class ODTextCommandManager extends base_1.ODManager {
|
|
957
|
+
/**Alias to Open Discord debugger. */
|
|
958
|
+
#debug;
|
|
959
|
+
/**Copy of discord.js client. */
|
|
960
|
+
manager;
|
|
961
|
+
/**Collection of all interaction listeners. */
|
|
962
|
+
#interactionListeners = [];
|
|
963
|
+
/**Collection of all error listeners. */
|
|
964
|
+
#errorListeners = [];
|
|
965
|
+
/**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */
|
|
966
|
+
listenerLimit = 100;
|
|
967
|
+
constructor(debug, manager) {
|
|
968
|
+
super(debug, "text command");
|
|
969
|
+
this.#debug = debug;
|
|
970
|
+
this.manager = manager;
|
|
971
|
+
}
|
|
972
|
+
/*Check if a message is a registered command. */
|
|
973
|
+
async #checkMessage(msg) {
|
|
974
|
+
if (this.manager.client.user && msg.author.id == this.manager.client.user.id)
|
|
975
|
+
return false;
|
|
976
|
+
//filter commands for correct prefix
|
|
977
|
+
const validPrefixCommands = [];
|
|
978
|
+
await this.loopAll((cmd) => {
|
|
979
|
+
if (msg.content.startsWith(cmd.builder.prefix))
|
|
980
|
+
validPrefixCommands.push({
|
|
981
|
+
cmd: cmd,
|
|
982
|
+
newContent: msg.content.substring(cmd.builder.prefix.length)
|
|
983
|
+
});
|
|
984
|
+
});
|
|
985
|
+
//return when no command with prefix
|
|
986
|
+
if (validPrefixCommands.length == 0) {
|
|
987
|
+
this.#errorListeners.forEach((cb) => cb({
|
|
988
|
+
type: "unknown_prefix",
|
|
989
|
+
msg: msg
|
|
990
|
+
}));
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
//filter commands for correct name
|
|
994
|
+
const validNameCommands = [];
|
|
995
|
+
validPrefixCommands.forEach((cmd) => {
|
|
996
|
+
if (cmd.newContent.startsWith(cmd.cmd.builder.name + " ") || cmd.newContent == cmd.cmd.builder.name)
|
|
997
|
+
validNameCommands.push({
|
|
998
|
+
cmd: cmd.cmd,
|
|
999
|
+
newContent: cmd.newContent.substring(cmd.cmd.builder.name.length + 1) //+1 because of space after command name
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
//return when no command with name
|
|
1003
|
+
if (validNameCommands.length == 0) {
|
|
1004
|
+
this.#errorListeners.forEach((cb) => cb({
|
|
1005
|
+
type: "unknown_command",
|
|
1006
|
+
msg: msg
|
|
1007
|
+
}));
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
//the final command
|
|
1011
|
+
const command = validNameCommands[0];
|
|
1012
|
+
const builder = command.cmd.builder;
|
|
1013
|
+
//check additional options
|
|
1014
|
+
if (typeof builder.allowBots != "undefined" && !builder.allowBots && msg.author.bot)
|
|
1015
|
+
return false;
|
|
1016
|
+
else if (typeof builder.dmPermission != "undefined" && !builder.dmPermission && msg.channel.type == discord.ChannelType.DM)
|
|
1017
|
+
return false;
|
|
1018
|
+
else if (typeof builder.guildPermission != "undefined" && !builder.guildPermission && msg.guild)
|
|
1019
|
+
return false;
|
|
1020
|
+
else if (typeof builder.allowedGuildIds != "undefined" && msg.guild && !builder.allowedGuildIds.includes(msg.guild.id))
|
|
1021
|
+
return false;
|
|
1022
|
+
//check all command options & return when incorrect
|
|
1023
|
+
const options = await this.#checkOptions(command.cmd, command.newContent, msg);
|
|
1024
|
+
if (!options.valid)
|
|
1025
|
+
return false;
|
|
1026
|
+
//a command matched this message => emit event
|
|
1027
|
+
this.#interactionListeners.forEach((listener) => {
|
|
1028
|
+
if (typeof listener.prefix == "string" && (command.cmd.builder.prefix != listener.prefix))
|
|
1029
|
+
return;
|
|
1030
|
+
if (typeof listener.name == "string" && (command.cmd.name.split(" ")[0] != listener.name))
|
|
1031
|
+
return;
|
|
1032
|
+
else if (listener.name instanceof RegExp && !listener.name.test(command.cmd.name.split(" ")[0]))
|
|
1033
|
+
return;
|
|
1034
|
+
//this is a valid listener
|
|
1035
|
+
listener.callback(msg, command.cmd, options.data);
|
|
1036
|
+
});
|
|
1037
|
+
return true;
|
|
1038
|
+
}
|
|
1039
|
+
/**Check if all options of a command are correct. */
|
|
1040
|
+
async #checkOptions(cmd, newContent, msg) {
|
|
1041
|
+
const options = cmd.builder.options;
|
|
1042
|
+
if (!options)
|
|
1043
|
+
return { valid: true, data: [] };
|
|
1044
|
+
let tempContent = newContent;
|
|
1045
|
+
let optionInvalid = false;
|
|
1046
|
+
const optionData = [];
|
|
1047
|
+
const optionError = (type, option, location, value, reason) => {
|
|
1048
|
+
//ERROR INVALID
|
|
1049
|
+
if (type == "invalid_option" && value && reason) {
|
|
1050
|
+
this.#errorListeners.forEach((cb) => cb({
|
|
1051
|
+
type: "invalid_option",
|
|
1052
|
+
msg: msg,
|
|
1053
|
+
prefix: cmd.builder.prefix,
|
|
1054
|
+
command: cmd,
|
|
1055
|
+
name: cmd.builder.name,
|
|
1056
|
+
option,
|
|
1057
|
+
location,
|
|
1058
|
+
value,
|
|
1059
|
+
reason
|
|
1060
|
+
}));
|
|
1061
|
+
}
|
|
1062
|
+
else if (type == "missing_option") {
|
|
1063
|
+
this.#errorListeners.forEach((cb) => cb({
|
|
1064
|
+
type: "missing_option",
|
|
1065
|
+
msg: msg,
|
|
1066
|
+
prefix: cmd.builder.prefix,
|
|
1067
|
+
command: cmd,
|
|
1068
|
+
name: cmd.builder.name,
|
|
1069
|
+
option,
|
|
1070
|
+
location
|
|
1071
|
+
}));
|
|
1072
|
+
}
|
|
1073
|
+
optionInvalid = true;
|
|
1074
|
+
};
|
|
1075
|
+
for (let location = 0; location < options.length; location++) {
|
|
1076
|
+
const option = options[location];
|
|
1077
|
+
if (optionInvalid)
|
|
1078
|
+
break;
|
|
1079
|
+
//CHECK BOOLEAN
|
|
1080
|
+
if (option.type == "boolean") {
|
|
1081
|
+
const falseValue = option.falseValue ?? "false";
|
|
1082
|
+
const trueValue = option.trueValue ?? "true";
|
|
1083
|
+
if (tempContent.startsWith(falseValue + " ")) {
|
|
1084
|
+
//FALSE VALUE
|
|
1085
|
+
optionData.push({
|
|
1086
|
+
name: option.name,
|
|
1087
|
+
type: "boolean",
|
|
1088
|
+
value: false
|
|
1089
|
+
});
|
|
1090
|
+
tempContent = tempContent.substring(falseValue.length + 1);
|
|
1091
|
+
}
|
|
1092
|
+
else if (tempContent.startsWith(trueValue + " ")) {
|
|
1093
|
+
//TRUE VALUE
|
|
1094
|
+
optionData.push({
|
|
1095
|
+
name: option.name,
|
|
1096
|
+
type: "boolean",
|
|
1097
|
+
value: true
|
|
1098
|
+
});
|
|
1099
|
+
tempContent = tempContent.substring(trueValue.length + 1);
|
|
1100
|
+
}
|
|
1101
|
+
else if (option.required) {
|
|
1102
|
+
//REQUIRED => ERROR IF NOT EXISTING
|
|
1103
|
+
const invalidregex = /^[^ ]+/;
|
|
1104
|
+
const invalidRes = invalidregex.exec(tempContent);
|
|
1105
|
+
if (invalidRes)
|
|
1106
|
+
optionError("invalid_option", option, location, invalidRes[0], "boolean");
|
|
1107
|
+
else
|
|
1108
|
+
optionError("missing_option", option, location);
|
|
1109
|
+
}
|
|
1110
|
+
//CHECK NUMBER
|
|
1111
|
+
}
|
|
1112
|
+
else if (option.type == "number") {
|
|
1113
|
+
const numRegex = /^[0-9\.\,]+/;
|
|
1114
|
+
const res = numRegex.exec(tempContent);
|
|
1115
|
+
if (res) {
|
|
1116
|
+
const value = res[0].replace(/\,/g, ".");
|
|
1117
|
+
tempContent = tempContent.substring(value.length + 1);
|
|
1118
|
+
const numValue = Number(value);
|
|
1119
|
+
if (isNaN(numValue)) {
|
|
1120
|
+
optionError("invalid_option", option, location, value, "number_invalid");
|
|
1121
|
+
}
|
|
1122
|
+
else if (typeof option.allowDecimal == "boolean" && !option.allowDecimal && (numValue % 1) !== 0) {
|
|
1123
|
+
optionError("invalid_option", option, location, value, "number_decimal");
|
|
1124
|
+
}
|
|
1125
|
+
else if (typeof option.allowNegative == "boolean" && !option.allowNegative && numValue < 0) {
|
|
1126
|
+
optionError("invalid_option", option, location, value, "number_negative");
|
|
1127
|
+
}
|
|
1128
|
+
else if (typeof option.allowPositive == "boolean" && !option.allowPositive && numValue > 0) {
|
|
1129
|
+
optionError("invalid_option", option, location, value, "number_positive");
|
|
1130
|
+
}
|
|
1131
|
+
else if (typeof option.allowZero == "boolean" && !option.allowZero && numValue == 0) {
|
|
1132
|
+
optionError("invalid_option", option, location, value, "number_zero");
|
|
1133
|
+
}
|
|
1134
|
+
else if (typeof option.max == "number" && numValue > option.max) {
|
|
1135
|
+
optionError("invalid_option", option, location, value, "number_max");
|
|
1136
|
+
}
|
|
1137
|
+
else if (typeof option.min == "number" && numValue < option.min) {
|
|
1138
|
+
optionError("invalid_option", option, location, value, "number_min");
|
|
1139
|
+
}
|
|
1140
|
+
else {
|
|
1141
|
+
//VALID NUMBER
|
|
1142
|
+
optionData.push({
|
|
1143
|
+
name: option.name,
|
|
1144
|
+
type: "number",
|
|
1145
|
+
value: numValue
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
else if (option.required) {
|
|
1150
|
+
//REQUIRED => ERROR IF NOT EXISTING
|
|
1151
|
+
const invalidRegex = /^[^ ]+/;
|
|
1152
|
+
const invalidRes = invalidRegex.exec(tempContent);
|
|
1153
|
+
if (invalidRes)
|
|
1154
|
+
optionError("invalid_option", option, location, invalidRes[0], "number_invalid");
|
|
1155
|
+
else
|
|
1156
|
+
optionError("missing_option", option, location);
|
|
1157
|
+
}
|
|
1158
|
+
//CHECK STRING
|
|
1159
|
+
}
|
|
1160
|
+
else if (option.type == "string") {
|
|
1161
|
+
if (option.allowSpaces) {
|
|
1162
|
+
//STRING WITH SPACES
|
|
1163
|
+
const value = tempContent;
|
|
1164
|
+
tempContent = "";
|
|
1165
|
+
if (typeof option.minLength == "number" && value.length < option.minLength) {
|
|
1166
|
+
optionError("invalid_option", option, location, value, "string_min_length");
|
|
1167
|
+
}
|
|
1168
|
+
else if (typeof option.maxLength == "number" && value.length > option.maxLength) {
|
|
1169
|
+
optionError("invalid_option", option, location, value, "string_max_length");
|
|
1170
|
+
}
|
|
1171
|
+
else if (option.regex && !option.regex.test(value)) {
|
|
1172
|
+
optionError("invalid_option", option, location, value, "string_regex");
|
|
1173
|
+
}
|
|
1174
|
+
else if (option.choices && !option.choices.includes(value)) {
|
|
1175
|
+
optionError("invalid_option", option, location, value, "string_choice");
|
|
1176
|
+
}
|
|
1177
|
+
else if (option.required && value === "") {
|
|
1178
|
+
//REQUIRED => ERROR IF NOT EXISTING
|
|
1179
|
+
optionError("missing_option", option, location);
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
//VALID STRING
|
|
1183
|
+
optionData.push({
|
|
1184
|
+
name: option.name,
|
|
1185
|
+
type: "string",
|
|
1186
|
+
value
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
//STRING WITHOUT SPACES
|
|
1192
|
+
const stringRegex = /^[^ ]+/;
|
|
1193
|
+
const res = stringRegex.exec(tempContent);
|
|
1194
|
+
if (res) {
|
|
1195
|
+
const value = res[0];
|
|
1196
|
+
tempContent = tempContent.substring(value.length + 1);
|
|
1197
|
+
if (typeof option.minLength == "number" && value.length < option.minLength) {
|
|
1198
|
+
optionError("invalid_option", option, location, value, "string_min_length");
|
|
1199
|
+
}
|
|
1200
|
+
else if (typeof option.maxLength == "number" && value.length > option.maxLength) {
|
|
1201
|
+
optionError("invalid_option", option, location, value, "string_max_length");
|
|
1202
|
+
}
|
|
1203
|
+
else if (option.regex && !option.regex.test(value)) {
|
|
1204
|
+
optionError("invalid_option", option, location, value, "string_regex");
|
|
1205
|
+
}
|
|
1206
|
+
else if (option.choices && !option.choices.includes(value)) {
|
|
1207
|
+
optionError("invalid_option", option, location, value, "string_choice");
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
//VALID STRING
|
|
1211
|
+
optionData.push({
|
|
1212
|
+
name: option.name,
|
|
1213
|
+
type: "string",
|
|
1214
|
+
value
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
else if (option.required) {
|
|
1219
|
+
//REQUIRED => ERROR IF NOT EXISTING
|
|
1220
|
+
optionError("missing_option", option, location);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
//CHECK CHANNEL
|
|
1224
|
+
}
|
|
1225
|
+
else if (option.type == "channel") {
|
|
1226
|
+
const channelRegex = /^(?:<#)?([0-9]+)>?/;
|
|
1227
|
+
const res = channelRegex.exec(tempContent);
|
|
1228
|
+
if (res) {
|
|
1229
|
+
const value = res[0];
|
|
1230
|
+
tempContent = tempContent.substring(value.length + 1);
|
|
1231
|
+
const channelId = res[1];
|
|
1232
|
+
if (!msg.guild) {
|
|
1233
|
+
optionError("invalid_option", option, location, value, "not_in_guild");
|
|
1234
|
+
}
|
|
1235
|
+
else {
|
|
1236
|
+
try {
|
|
1237
|
+
const channel = await msg.guild.channels.fetch(channelId);
|
|
1238
|
+
if (!channel) {
|
|
1239
|
+
optionError("invalid_option", option, location, value, "channel_not_found");
|
|
1240
|
+
}
|
|
1241
|
+
else if (option.channelTypes && !option.channelTypes.includes(channel.type)) {
|
|
1242
|
+
optionError("invalid_option", option, location, value, "channel_type");
|
|
1243
|
+
}
|
|
1244
|
+
else {
|
|
1245
|
+
//VALID CHANNEL
|
|
1246
|
+
optionData.push({
|
|
1247
|
+
name: option.name,
|
|
1248
|
+
type: "channel",
|
|
1249
|
+
value: channel
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
catch {
|
|
1254
|
+
optionError("invalid_option", option, location, value, "channel_not_found");
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
else if (option.required) {
|
|
1259
|
+
//REQUIRED => ERROR IF NOT EXISTING
|
|
1260
|
+
const invalidRegex = /^[^ ]+/;
|
|
1261
|
+
const invalidRes = invalidRegex.exec(tempContent);
|
|
1262
|
+
if (invalidRes)
|
|
1263
|
+
optionError("invalid_option", option, location, invalidRes[0], "channel_not_found");
|
|
1264
|
+
else
|
|
1265
|
+
optionError("missing_option", option, location);
|
|
1266
|
+
}
|
|
1267
|
+
//CHECK ROLE
|
|
1268
|
+
}
|
|
1269
|
+
else if (option.type == "role") {
|
|
1270
|
+
const roleRegex = /^(?:<@&)?([0-9]+)>?/;
|
|
1271
|
+
const res = roleRegex.exec(tempContent);
|
|
1272
|
+
if (res) {
|
|
1273
|
+
const value = res[0];
|
|
1274
|
+
tempContent = tempContent.substring(value.length + 1);
|
|
1275
|
+
const roleId = res[1];
|
|
1276
|
+
if (!msg.guild) {
|
|
1277
|
+
optionError("invalid_option", option, location, value, "not_in_guild");
|
|
1278
|
+
}
|
|
1279
|
+
else {
|
|
1280
|
+
try {
|
|
1281
|
+
const role = await msg.guild.roles.fetch(roleId);
|
|
1282
|
+
if (!role) {
|
|
1283
|
+
optionError("invalid_option", option, location, value, "role_not_found");
|
|
1284
|
+
}
|
|
1285
|
+
else {
|
|
1286
|
+
//VALID ROLE
|
|
1287
|
+
optionData.push({
|
|
1288
|
+
name: option.name,
|
|
1289
|
+
type: "role",
|
|
1290
|
+
value: role
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
catch {
|
|
1295
|
+
optionError("invalid_option", option, location, value, "role_not_found");
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
else if (option.required) {
|
|
1300
|
+
//REQUIRED => ERROR IF NOT EXISTING
|
|
1301
|
+
const invalidRegex = /^[^ ]+/;
|
|
1302
|
+
const invalidRes = invalidRegex.exec(tempContent);
|
|
1303
|
+
if (invalidRes)
|
|
1304
|
+
optionError("invalid_option", option, location, invalidRes[0], "role_not_found");
|
|
1305
|
+
else
|
|
1306
|
+
optionError("missing_option", option, location);
|
|
1307
|
+
}
|
|
1308
|
+
//CHECK GUILD MEMBER
|
|
1309
|
+
}
|
|
1310
|
+
else if (option.type == "guildmember") {
|
|
1311
|
+
const memberRegex = /^(?:<@)?([0-9]+)>?/;
|
|
1312
|
+
const res = memberRegex.exec(tempContent);
|
|
1313
|
+
if (res) {
|
|
1314
|
+
const value = res[0];
|
|
1315
|
+
tempContent = tempContent.substring(value.length + 1);
|
|
1316
|
+
const memberId = res[1];
|
|
1317
|
+
if (!msg.guild) {
|
|
1318
|
+
optionError("invalid_option", option, location, value, "not_in_guild");
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
try {
|
|
1322
|
+
const member = await msg.guild.members.fetch(memberId);
|
|
1323
|
+
if (!member) {
|
|
1324
|
+
optionError("invalid_option", option, location, value, "member_not_found");
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
//VALID GUILD MEMBER
|
|
1328
|
+
optionData.push({
|
|
1329
|
+
name: option.name,
|
|
1330
|
+
type: "guildmember",
|
|
1331
|
+
value: member
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
catch {
|
|
1336
|
+
optionError("invalid_option", option, location, value, "member_not_found");
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
else if (option.required) {
|
|
1341
|
+
//REQUIRED => ERROR IF NOT EXISTING
|
|
1342
|
+
const invalidRegex = /^[^ ]+/;
|
|
1343
|
+
const invalidRes = invalidRegex.exec(tempContent);
|
|
1344
|
+
if (invalidRes)
|
|
1345
|
+
optionError("invalid_option", option, location, invalidRes[0], "member_not_found");
|
|
1346
|
+
else
|
|
1347
|
+
optionError("missing_option", option, location);
|
|
1348
|
+
}
|
|
1349
|
+
//CHECK USER
|
|
1350
|
+
}
|
|
1351
|
+
else if (option.type == "user") {
|
|
1352
|
+
const userRegex = /^(?:<@)?([0-9]+)>?/;
|
|
1353
|
+
const res = userRegex.exec(tempContent);
|
|
1354
|
+
if (res) {
|
|
1355
|
+
const value = res[0];
|
|
1356
|
+
tempContent = tempContent.substring(value.length + 1);
|
|
1357
|
+
const userId = res[1];
|
|
1358
|
+
try {
|
|
1359
|
+
const user = await this.manager.client.users.fetch(userId);
|
|
1360
|
+
if (!user) {
|
|
1361
|
+
optionError("invalid_option", option, location, value, "user_not_found");
|
|
1362
|
+
}
|
|
1363
|
+
else {
|
|
1364
|
+
//VALID USER
|
|
1365
|
+
optionData.push({
|
|
1366
|
+
name: option.name,
|
|
1367
|
+
type: "user",
|
|
1368
|
+
value: user
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
catch {
|
|
1373
|
+
optionError("invalid_option", option, location, value, "user_not_found");
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
else if (option.required) {
|
|
1377
|
+
//REQUIRED => ERROR IF NOT EXISTING
|
|
1378
|
+
const invalidRegex = /^[^ ]+/;
|
|
1379
|
+
const invalidRes = invalidRegex.exec(tempContent);
|
|
1380
|
+
if (invalidRes)
|
|
1381
|
+
optionError("invalid_option", option, location, invalidRes[0], "user_not_found");
|
|
1382
|
+
else
|
|
1383
|
+
optionError("missing_option", option, location);
|
|
1384
|
+
}
|
|
1385
|
+
//CHECK MENTIONABLE
|
|
1386
|
+
}
|
|
1387
|
+
else if (option.type == "mentionable") {
|
|
1388
|
+
const mentionableRegex = /^<(@&?)([0-9]+)>/;
|
|
1389
|
+
const res = mentionableRegex.exec(tempContent);
|
|
1390
|
+
if (res) {
|
|
1391
|
+
const value = res[0];
|
|
1392
|
+
const type = (res[1] == "@&") ? "role" : "user";
|
|
1393
|
+
tempContent = tempContent.substring(value.length + 1);
|
|
1394
|
+
const mentionableId = res[2];
|
|
1395
|
+
if (!msg.guild) {
|
|
1396
|
+
optionError("invalid_option", option, location, value, "not_in_guild");
|
|
1397
|
+
}
|
|
1398
|
+
else if (type == "role") {
|
|
1399
|
+
try {
|
|
1400
|
+
const role = await msg.guild.roles.fetch(mentionableId);
|
|
1401
|
+
if (!role) {
|
|
1402
|
+
optionError("invalid_option", option, location, value, "mentionable_not_found");
|
|
1403
|
+
}
|
|
1404
|
+
else {
|
|
1405
|
+
//VALID ROLE
|
|
1406
|
+
optionData.push({
|
|
1407
|
+
name: option.name,
|
|
1408
|
+
type: "mentionable",
|
|
1409
|
+
value: role
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
catch {
|
|
1414
|
+
optionError("invalid_option", option, location, value, "mentionable_not_found");
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
else if (type == "user") {
|
|
1418
|
+
try {
|
|
1419
|
+
const user = await this.manager.client.users.fetch(mentionableId);
|
|
1420
|
+
if (!user) {
|
|
1421
|
+
optionError("invalid_option", option, location, value, "mentionable_not_found");
|
|
1422
|
+
}
|
|
1423
|
+
else {
|
|
1424
|
+
//VALID USER
|
|
1425
|
+
optionData.push({
|
|
1426
|
+
name: option.name,
|
|
1427
|
+
type: "mentionable",
|
|
1428
|
+
value: user
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
catch {
|
|
1433
|
+
optionError("invalid_option", option, location, value, "mentionable_not_found");
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
else if (option.required) {
|
|
1438
|
+
//REQUIRED => ERROR IF NOT EXISTING
|
|
1439
|
+
const invalidRegex = /^[^ ]+/;
|
|
1440
|
+
const invalidRes = invalidRegex.exec(tempContent);
|
|
1441
|
+
if (invalidRes)
|
|
1442
|
+
optionError("invalid_option", option, location, invalidRes[0], "mentionable_not_found");
|
|
1443
|
+
else
|
|
1444
|
+
optionError("missing_option", option, location);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return { valid: !optionInvalid, data: optionData };
|
|
1449
|
+
}
|
|
1450
|
+
/**Start listening to the discord.js client `messageCreate` event. */
|
|
1451
|
+
startListeningToInteractions() {
|
|
1452
|
+
this.manager.client.on("messageCreate", (msg) => {
|
|
1453
|
+
//return when not in main server or DM
|
|
1454
|
+
if (!this.manager.mainServer || (msg.guild && msg.guild.id != this.manager.mainServer.id))
|
|
1455
|
+
return;
|
|
1456
|
+
this.#checkMessage(msg);
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
/**Check if optional values are only present at the end of the command. */
|
|
1460
|
+
#checkBuilderOptions(builder) {
|
|
1461
|
+
let optionalVisited = false;
|
|
1462
|
+
let valid = true;
|
|
1463
|
+
let reason = null;
|
|
1464
|
+
if (!builder.options)
|
|
1465
|
+
return { valid: true, reason: null };
|
|
1466
|
+
builder.options.forEach((opt, index, list) => {
|
|
1467
|
+
if (!opt.required)
|
|
1468
|
+
optionalVisited = true;
|
|
1469
|
+
if (optionalVisited && opt.required) {
|
|
1470
|
+
valid = false;
|
|
1471
|
+
reason = "required_after_optional";
|
|
1472
|
+
}
|
|
1473
|
+
if (opt.type == "string" && opt.allowSpaces && ((index + 1) != list.length)) {
|
|
1474
|
+
valid = false;
|
|
1475
|
+
reason = "allowspaces_not_last";
|
|
1476
|
+
}
|
|
1477
|
+
});
|
|
1478
|
+
return { valid, reason };
|
|
1479
|
+
}
|
|
1480
|
+
/**Callback on interaction from one of the registered text commands */
|
|
1481
|
+
onInteraction(commandPrefix, commandName, callback) {
|
|
1482
|
+
this.#interactionListeners.push({
|
|
1483
|
+
prefix: commandPrefix,
|
|
1484
|
+
name: commandName,
|
|
1485
|
+
callback
|
|
1486
|
+
});
|
|
1487
|
+
if (this.#interactionListeners.length > this.listenerLimit) {
|
|
1488
|
+
this.#debug.console.log(new console_1.ODConsoleWarningMessage("Possible text command interaction memory leak detected!", [
|
|
1489
|
+
{ key: "listeners", value: this.#interactionListeners.length.toString() }
|
|
1490
|
+
]));
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
/**Callback on error from all the registered text commands */
|
|
1494
|
+
onError(callback) {
|
|
1495
|
+
this.#errorListeners.push(callback);
|
|
1496
|
+
}
|
|
1497
|
+
add(data, overwrite) {
|
|
1498
|
+
const checkResult = this.#checkBuilderOptions(data.builder);
|
|
1499
|
+
if (!checkResult.valid && checkResult.reason == "required_after_optional")
|
|
1500
|
+
throw new base_1.ODSystemError("Invalid text command '" + data.id.value + "' => optional options are only allowed at the end of a command!");
|
|
1501
|
+
else if (!checkResult.valid && checkResult.reason == "allowspaces_not_last")
|
|
1502
|
+
throw new base_1.ODSystemError("Invalid text command '" + data.id.value + "' => string option with 'allowSpaces' is only allowed at the end of a command!");
|
|
1503
|
+
else
|
|
1504
|
+
return super.add(data, overwrite);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
exports.ODTextCommandManager = ODTextCommandManager;
|
|
1508
|
+
/**## ODContextMenuComparator `class`
|
|
1509
|
+
* A utility class to compare existing context menu's with newly registered ones.
|
|
1510
|
+
*/
|
|
1511
|
+
class ODContextMenuComparator {
|
|
1512
|
+
/**Convert a `ODContextMenuBuilder` to a universal Open Discord context menu object for comparison. */
|
|
1513
|
+
convertBuilder(builder, guildId) {
|
|
1514
|
+
if (builder.type != discord.ApplicationCommandType.Message && builder.type != discord.ApplicationCommandType.User)
|
|
1515
|
+
return null;
|
|
1516
|
+
const nameLoc = builder.nameLocalizations ?? {};
|
|
1517
|
+
return {
|
|
1518
|
+
type: builder.type,
|
|
1519
|
+
name: builder.name,
|
|
1520
|
+
nameLocalizations: Object.keys(nameLoc).map((key) => { return { language: key, value: nameLoc[key] }; }),
|
|
1521
|
+
guildId: guildId,
|
|
1522
|
+
nsfw: builder.nsfw ?? false,
|
|
1523
|
+
defaultMemberPermissions: discord.PermissionsBitField.resolve(builder.defaultMemberPermissions ?? ["ViewChannel"]),
|
|
1524
|
+
dmPermission: (builder.contexts && builder.contexts.includes(discord.InteractionContextType.BotDM)) ?? false,
|
|
1525
|
+
integrationTypes: builder.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall],
|
|
1526
|
+
contexts: builder.contexts ?? []
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
/**Convert a `discord.ApplicationCommand` to a universal Open Discord context menu object for comparison. */
|
|
1530
|
+
convertMenu(cmd) {
|
|
1531
|
+
if (cmd.type != discord.ApplicationCommandType.Message && cmd.type != discord.ApplicationCommandType.User)
|
|
1532
|
+
return null;
|
|
1533
|
+
const nameLoc = cmd.nameLocalizations ?? {};
|
|
1534
|
+
return {
|
|
1535
|
+
type: cmd.type,
|
|
1536
|
+
name: cmd.name,
|
|
1537
|
+
nameLocalizations: Object.keys(nameLoc).map((key) => { return { language: key, value: nameLoc[key] }; }),
|
|
1538
|
+
guildId: cmd.guildId,
|
|
1539
|
+
nsfw: cmd.nsfw,
|
|
1540
|
+
defaultMemberPermissions: discord.PermissionsBitField.resolve(cmd.defaultMemberPermissions ?? ["ViewChannel"]),
|
|
1541
|
+
dmPermission: (cmd.contexts && cmd.contexts.includes(discord.InteractionContextType.BotDM)) ? true : false,
|
|
1542
|
+
integrationTypes: cmd.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall],
|
|
1543
|
+
contexts: cmd.contexts ?? []
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
/**Returns `true` when the 2 context menus are the same. */
|
|
1547
|
+
compare(ctxA, ctxB) {
|
|
1548
|
+
if (ctxA.name != ctxB.name)
|
|
1549
|
+
return false;
|
|
1550
|
+
if (ctxA.type != ctxB.type)
|
|
1551
|
+
return false;
|
|
1552
|
+
if (ctxA.nsfw != ctxB.nsfw)
|
|
1553
|
+
return false;
|
|
1554
|
+
if (ctxA.guildId != ctxB.guildId)
|
|
1555
|
+
return false;
|
|
1556
|
+
if (ctxA.dmPermission != ctxB.dmPermission)
|
|
1557
|
+
return false;
|
|
1558
|
+
if (ctxA.defaultMemberPermissions != ctxB.defaultMemberPermissions)
|
|
1559
|
+
return false;
|
|
1560
|
+
//nameLocalizations
|
|
1561
|
+
if (ctxA.nameLocalizations.length != ctxB.nameLocalizations.length)
|
|
1562
|
+
return false;
|
|
1563
|
+
if (!ctxA.nameLocalizations.every((nameA) => {
|
|
1564
|
+
const nameB = ctxB.nameLocalizations.find((nameB) => nameB.language == nameA.language);
|
|
1565
|
+
if (!nameB || nameA.value != nameB.value)
|
|
1566
|
+
return false;
|
|
1567
|
+
else
|
|
1568
|
+
return true;
|
|
1569
|
+
}))
|
|
1570
|
+
return false;
|
|
1571
|
+
//contexts
|
|
1572
|
+
if (ctxA.contexts.length != ctxB.contexts.length)
|
|
1573
|
+
return false;
|
|
1574
|
+
if (!ctxA.contexts.every((contextA) => {
|
|
1575
|
+
return ctxB.contexts.includes(contextA);
|
|
1576
|
+
}))
|
|
1577
|
+
return false;
|
|
1578
|
+
//integrationTypes
|
|
1579
|
+
if (ctxA.integrationTypes.length != ctxB.integrationTypes.length)
|
|
1580
|
+
return false;
|
|
1581
|
+
if (!ctxA.integrationTypes.every((integrationA) => {
|
|
1582
|
+
return ctxB.integrationTypes.includes(integrationA);
|
|
1583
|
+
}))
|
|
1584
|
+
return false;
|
|
1585
|
+
return true;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
exports.ODContextMenuComparator = ODContextMenuComparator;
|
|
1589
|
+
/**## ODContextMenuManager `class`
|
|
1590
|
+
* This is an Open Discord client context menu manager.
|
|
1591
|
+
*
|
|
1592
|
+
* It's responsible for managing all the context interactions from the client.
|
|
1593
|
+
*
|
|
1594
|
+
* Here, you can add & remove context interactions & the bot will do the (de)registering.
|
|
1595
|
+
*/
|
|
1596
|
+
class ODContextMenuManager extends base_1.ODManager {
|
|
1597
|
+
/**Alias to Open Discord debugger. */
|
|
1598
|
+
#debug;
|
|
1599
|
+
/**Refrerence to discord.js client. */
|
|
1600
|
+
manager;
|
|
1601
|
+
/**Discord.js application commands manager. */
|
|
1602
|
+
commandManager;
|
|
1603
|
+
/**Collection of all interaction listeners. */
|
|
1604
|
+
#interactionListeners = [];
|
|
1605
|
+
/**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */
|
|
1606
|
+
listenerLimit = 100;
|
|
1607
|
+
/**A utility class used to compare 2 context menus with each other. */
|
|
1608
|
+
comparator = new ODContextMenuComparator();
|
|
1609
|
+
constructor(debug, manager) {
|
|
1610
|
+
super(debug, "context menu");
|
|
1611
|
+
this.#debug = debug;
|
|
1612
|
+
this.manager = manager;
|
|
1613
|
+
this.commandManager = (manager.client.application) ? manager.client.application.commands : null;
|
|
1614
|
+
}
|
|
1615
|
+
/**Get all registered & unregistered message context menu commands. */
|
|
1616
|
+
async getAllRegisteredMenus(guildId) {
|
|
1617
|
+
if (!this.commandManager)
|
|
1618
|
+
throw new base_1.ODSystemError("Couldn't get client application to register message context menus!");
|
|
1619
|
+
const menus = (await this.commandManager.fetch({ guildId })).toJSON();
|
|
1620
|
+
const registered = [];
|
|
1621
|
+
const unregistered = [];
|
|
1622
|
+
const unused = [];
|
|
1623
|
+
await this.loopAll((instance) => {
|
|
1624
|
+
if (guildId && instance.guildId != guildId)
|
|
1625
|
+
return;
|
|
1626
|
+
const index = menus.findIndex((menu) => menu.name == instance.name);
|
|
1627
|
+
const menu = menus[index];
|
|
1628
|
+
menus.splice(index, 1);
|
|
1629
|
+
if (menu) {
|
|
1630
|
+
//menu is registered (and may need to be updated)
|
|
1631
|
+
const universalBuilder = this.comparator.convertBuilder(instance.builder, instance.guildId);
|
|
1632
|
+
const universalMenu = this.comparator.convertMenu(menu);
|
|
1633
|
+
//menu is not of the type 'message'|'user'
|
|
1634
|
+
if (!universalBuilder || !universalMenu)
|
|
1635
|
+
return;
|
|
1636
|
+
const didChange = !this.comparator.compare(universalBuilder, universalMenu);
|
|
1637
|
+
const requiresUpdate = didChange || (instance.requiresUpdate ? instance.requiresUpdate(universalMenu) : false);
|
|
1638
|
+
registered.push({ instance, menu: universalMenu, requiresUpdate });
|
|
1639
|
+
//menu is not registered
|
|
1640
|
+
}
|
|
1641
|
+
else
|
|
1642
|
+
unregistered.push({ instance, menu: null, requiresUpdate: true });
|
|
1643
|
+
});
|
|
1644
|
+
menus.forEach((menu) => {
|
|
1645
|
+
//menu does not exist in the manager (only append to unused when type == 'message'|'user')
|
|
1646
|
+
const universalCmd = this.comparator.convertMenu(menu);
|
|
1647
|
+
if (!universalCmd)
|
|
1648
|
+
return;
|
|
1649
|
+
unused.push({ instance: null, menu: universalCmd, requiresUpdate: false });
|
|
1650
|
+
});
|
|
1651
|
+
return { registered, unregistered, unused };
|
|
1652
|
+
}
|
|
1653
|
+
/**Create all context menus that are not registered yet.*/
|
|
1654
|
+
async createNewMenus(instances, progress) {
|
|
1655
|
+
if (!this.manager.ready)
|
|
1656
|
+
throw new base_1.ODSystemError("Client isn't ready yet! Unable to register context menus!");
|
|
1657
|
+
if (instances.length > 0 && progress) {
|
|
1658
|
+
progress.max = instances.length;
|
|
1659
|
+
progress.start();
|
|
1660
|
+
}
|
|
1661
|
+
for (const instance of instances) {
|
|
1662
|
+
await this.createMenu(instance);
|
|
1663
|
+
this.#debug.debug("Created new context menu", [
|
|
1664
|
+
{ key: "id", value: instance.id.value },
|
|
1665
|
+
{ key: "name", value: instance.name },
|
|
1666
|
+
{ key: "type", value: (instance.builder.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context" }
|
|
1667
|
+
]);
|
|
1668
|
+
if (progress)
|
|
1669
|
+
progress.increase(1);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
/**Update all context menus that are already registered. */
|
|
1673
|
+
async updateExistingMenus(instances, progress) {
|
|
1674
|
+
if (!this.manager.ready)
|
|
1675
|
+
throw new base_1.ODSystemError("Client isn't ready yet! Unable to register context menus!");
|
|
1676
|
+
if (instances.length > 0 && progress) {
|
|
1677
|
+
progress.max = instances.length;
|
|
1678
|
+
progress.start();
|
|
1679
|
+
}
|
|
1680
|
+
for (const instance of instances) {
|
|
1681
|
+
await this.createMenu(instance);
|
|
1682
|
+
this.#debug.debug("Updated existing context menu", [
|
|
1683
|
+
{ key: "id", value: instance.id.value },
|
|
1684
|
+
{ key: "name", value: instance.name },
|
|
1685
|
+
{ key: "type", value: (instance.builder.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context" }
|
|
1686
|
+
]);
|
|
1687
|
+
if (progress)
|
|
1688
|
+
progress.increase(1);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
/**Remove all context menus that are registered but unused by Open Discord. */
|
|
1692
|
+
async removeUnusedMenus(instances, guildId, progress) {
|
|
1693
|
+
if (!this.manager.ready)
|
|
1694
|
+
throw new base_1.ODSystemError("Client isn't ready yet! Unable to register context menus!");
|
|
1695
|
+
if (!this.commandManager)
|
|
1696
|
+
throw new base_1.ODSystemError("Couldn't get client application to register context menus!");
|
|
1697
|
+
if (instances.length > 0 && progress) {
|
|
1698
|
+
progress.max = instances.length;
|
|
1699
|
+
progress.start();
|
|
1700
|
+
}
|
|
1701
|
+
const menus = await this.commandManager.fetch({ guildId });
|
|
1702
|
+
for (const instance of instances) {
|
|
1703
|
+
const menu = menus.find((menu) => menu.name == instance.name);
|
|
1704
|
+
if (menu) {
|
|
1705
|
+
try {
|
|
1706
|
+
await menu.delete();
|
|
1707
|
+
this.#debug.debug("Removed existing context menu", [
|
|
1708
|
+
{ key: "name", value: menu.name },
|
|
1709
|
+
{ key: "guildId", value: guildId ?? "/" },
|
|
1710
|
+
{ key: "type", value: (instance.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context" }
|
|
1711
|
+
]);
|
|
1712
|
+
}
|
|
1713
|
+
catch (err) {
|
|
1714
|
+
process.emit("uncaughtException", err);
|
|
1715
|
+
throw new base_1.ODSystemError("Failed to delete context menu '" + menu.name + "'!");
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
if (progress)
|
|
1719
|
+
progress.increase(1);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
/**Create a context menu. **(SYSTEM ONLY)** => Use `ODContextMenuManager` for registering context menu's the default way! */
|
|
1723
|
+
async createMenu(menu) {
|
|
1724
|
+
if (!this.commandManager)
|
|
1725
|
+
throw new base_1.ODSystemError("Couldn't get client application to register context menu's!");
|
|
1726
|
+
try {
|
|
1727
|
+
await this.commandManager.create(menu.builder, (menu.guildId ?? undefined));
|
|
1728
|
+
}
|
|
1729
|
+
catch (err) {
|
|
1730
|
+
process.emit("uncaughtException", err);
|
|
1731
|
+
throw new base_1.ODSystemError("Failed to register context menu '" + menu.name + "'!");
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
/**Start listening to the discord.js client `interactionCreate` event. */
|
|
1735
|
+
startListeningToInteractions() {
|
|
1736
|
+
this.manager.client.on("interactionCreate", (interaction) => {
|
|
1737
|
+
//return when not in main server or DM
|
|
1738
|
+
if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id))
|
|
1739
|
+
return;
|
|
1740
|
+
if (!interaction.isContextMenuCommand())
|
|
1741
|
+
return;
|
|
1742
|
+
const menu = this.getFiltered((menu) => menu.name == interaction.commandName)[0];
|
|
1743
|
+
if (!menu)
|
|
1744
|
+
return;
|
|
1745
|
+
this.#interactionListeners.forEach((listener) => {
|
|
1746
|
+
if (typeof listener.name == "string" && (interaction.commandName != listener.name))
|
|
1747
|
+
return;
|
|
1748
|
+
else if (listener.name instanceof RegExp && !listener.name.test(interaction.commandName))
|
|
1749
|
+
return;
|
|
1750
|
+
//this is a valid listener
|
|
1751
|
+
listener.callback(interaction, menu);
|
|
1752
|
+
});
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
/**Callback on interaction from one or multiple context menu's. */
|
|
1756
|
+
onInteraction(menuName, callback) {
|
|
1757
|
+
this.#interactionListeners.push({
|
|
1758
|
+
name: menuName,
|
|
1759
|
+
callback
|
|
1760
|
+
});
|
|
1761
|
+
if (this.#interactionListeners.length > this.listenerLimit) {
|
|
1762
|
+
this.#debug.console.log("Possible context menu interaction memory leak detected!", "warning", [
|
|
1763
|
+
{ key: "listeners", value: this.#interactionListeners.length.toString() }
|
|
1764
|
+
]);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
exports.ODContextMenuManager = ODContextMenuManager;
|
|
1769
|
+
/**## ODContextMenu `class`
|
|
1770
|
+
* This is an Open Discord context menu.
|
|
1771
|
+
*
|
|
1772
|
+
* When registered, you can listen for this context menu using the `ODContextResponder`. The advantages of using this class for creating a context menu are:
|
|
1773
|
+
* - automatic registration in discord.js
|
|
1774
|
+
* - error reporting to the user when the bot fails to respond
|
|
1775
|
+
* - plugins can extend this context menu
|
|
1776
|
+
* - the bot won't re-register the context menu when it already exists (except when requested)!
|
|
1777
|
+
*
|
|
1778
|
+
* And more!
|
|
1779
|
+
*/
|
|
1780
|
+
class ODContextMenu extends base_1.ODManagerData {
|
|
1781
|
+
/**The discord.js builder for this context menu. */
|
|
1782
|
+
builder;
|
|
1783
|
+
/**The id of the guild this context menu is for. `null` when not set. */
|
|
1784
|
+
guildId;
|
|
1785
|
+
/**Function to check if the context menu requires to be updated (when it already exists). */
|
|
1786
|
+
requiresUpdate = null;
|
|
1787
|
+
constructor(id, builder, requiresUpdate, guildId) {
|
|
1788
|
+
super(id);
|
|
1789
|
+
if (builder.type != discord.ApplicationCommandType.Message && builder.type != discord.ApplicationCommandType.User)
|
|
1790
|
+
throw new base_1.ODSystemError("ApplicationCommandData is required to be the 'Message'|'User' type!");
|
|
1791
|
+
this.builder = builder;
|
|
1792
|
+
this.guildId = guildId ?? null;
|
|
1793
|
+
this.requiresUpdate = requiresUpdate ?? null;
|
|
1794
|
+
}
|
|
1795
|
+
/**The name of this context menu. */
|
|
1796
|
+
get name() {
|
|
1797
|
+
return this.builder.name;
|
|
1798
|
+
}
|
|
1799
|
+
set name(name) {
|
|
1800
|
+
this.builder.name = name;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
exports.ODContextMenu = ODContextMenu;
|
|
1804
|
+
/**## ODAutocompleteManager `class`
|
|
1805
|
+
* This is an Open Discord client autocomplete interaction manager.
|
|
1806
|
+
*
|
|
1807
|
+
* It's responsible for managing all the autocomplete interactions from the client.
|
|
1808
|
+
*/
|
|
1809
|
+
class ODAutocompleteManager {
|
|
1810
|
+
/**Alias to Open Discord debugger. */
|
|
1811
|
+
#debug;
|
|
1812
|
+
/**Refrerence to discord.js client. */
|
|
1813
|
+
manager;
|
|
1814
|
+
/**Discord.js application commands manager. */
|
|
1815
|
+
commandManager;
|
|
1816
|
+
/**Collection of all interaction listeners. */
|
|
1817
|
+
#interactionListeners = [];
|
|
1818
|
+
/**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */
|
|
1819
|
+
listenerLimit = 100;
|
|
1820
|
+
constructor(debug, manager) {
|
|
1821
|
+
this.#debug = debug;
|
|
1822
|
+
this.manager = manager;
|
|
1823
|
+
this.commandManager = (manager.client.application) ? manager.client.application.commands : null;
|
|
1824
|
+
}
|
|
1825
|
+
/**Start listening to the discord.js client `interactionCreate` event. */
|
|
1826
|
+
startListeningToInteractions() {
|
|
1827
|
+
this.manager.client.on("interactionCreate", (interaction) => {
|
|
1828
|
+
//return when not in main server or DM
|
|
1829
|
+
if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id))
|
|
1830
|
+
return;
|
|
1831
|
+
if (!interaction.isAutocomplete())
|
|
1832
|
+
return;
|
|
1833
|
+
this.#interactionListeners.forEach((listener) => {
|
|
1834
|
+
if (typeof listener.cmdName == "string" && (interaction.commandName != listener.cmdName))
|
|
1835
|
+
return;
|
|
1836
|
+
else if (listener.cmdName instanceof RegExp && !listener.cmdName.test(interaction.commandName))
|
|
1837
|
+
return;
|
|
1838
|
+
if (typeof listener.optName == "string" && (interaction.options.getFocused(true).name != listener.optName))
|
|
1839
|
+
return;
|
|
1840
|
+
else if (listener.optName instanceof RegExp && !listener.optName.test(interaction.options.getFocused(true).name))
|
|
1841
|
+
return;
|
|
1842
|
+
//this is a valid listener
|
|
1843
|
+
listener.callback(interaction);
|
|
1844
|
+
});
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
/**Callback on interaction from one or multiple autocompletes. */
|
|
1848
|
+
onInteraction(cmdName, optName, callback) {
|
|
1849
|
+
this.#interactionListeners.push({
|
|
1850
|
+
cmdName, optName, callback
|
|
1851
|
+
});
|
|
1852
|
+
if (this.#interactionListeners.length > this.listenerLimit) {
|
|
1853
|
+
this.#debug.console.log("Possible autocomplete interaction memory leak detected!", "warning", [
|
|
1854
|
+
{ key: "listeners", value: this.#interactionListeners.length.toString() }
|
|
1855
|
+
]);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
exports.ODAutocompleteManager = ODAutocompleteManager;
|