gralonium 0.2.0

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.
Files changed (50) hide show
  1. package/README.md +62 -0
  2. package/lib/classes/Client.d.ts +44 -0
  3. package/lib/classes/Client.js +162 -0
  4. package/lib/classes/Context.d.ts +25 -0
  5. package/lib/classes/Context.js +139 -0
  6. package/lib/classes/Cooldowns.d.ts +25 -0
  7. package/lib/classes/Cooldowns.js +72 -0
  8. package/lib/classes/Errors.d.ts +135 -0
  9. package/lib/classes/Errors.js +1 -0
  10. package/lib/classes/HelpCommand.d.ts +15 -0
  11. package/lib/classes/HelpCommand.js +1 -0
  12. package/lib/classes/Loader.d.ts +85 -0
  13. package/lib/classes/Loader.js +359 -0
  14. package/lib/classes/Plugins.d.ts +52 -0
  15. package/lib/classes/Plugins.js +107 -0
  16. package/lib/classes/Utils.d.ts +25 -0
  17. package/lib/classes/Utils.js +705 -0
  18. package/lib/classes/builders/CommandBuilder.d.ts +50 -0
  19. package/lib/classes/builders/CommandBuilder.js +107 -0
  20. package/lib/classes/builders/ComponentsV2Builder.d.ts +125 -0
  21. package/lib/classes/builders/ComponentsV2Builder.js +287 -0
  22. package/lib/classes/builders/EventBuilder.d.ts +19 -0
  23. package/lib/classes/builders/EventBuilder.js +1 -0
  24. package/lib/classes/builders/GroupBuilder.d.ts +46 -0
  25. package/lib/classes/builders/GroupBuilder.js +90 -0
  26. package/lib/classes/builders/InteractionBuilder.d.ts +32 -0
  27. package/lib/classes/builders/InteractionBuilder.js +1 -0
  28. package/lib/classes/builders/ParamsBuilder.d.ts +87 -0
  29. package/lib/classes/builders/ParamsBuilder.js +1 -0
  30. package/lib/classes/builders/components/StyleMapper.d.ts +10 -0
  31. package/lib/classes/builders/components/StyleMapper.js +32 -0
  32. package/lib/classes/builders/components/helpers.d.ts +22 -0
  33. package/lib/classes/builders/components/helpers.js +51 -0
  34. package/lib/classes/builders/components/templates.d.ts +13 -0
  35. package/lib/classes/builders/components/templates.js +41 -0
  36. package/lib/classes/experiments/Confirmator.d.ts +58 -0
  37. package/lib/classes/experiments/Confirmator.js +114 -0
  38. package/lib/classes/experiments/Paginator.d.ts +65 -0
  39. package/lib/classes/experiments/Paginator.js +124 -0
  40. package/lib/events/interactionCreate.d.ts +4 -0
  41. package/lib/events/interactionCreate.js +17 -0
  42. package/lib/events/messageCreate.d.ts +4 -0
  43. package/lib/events/messageCreate.js +17 -0
  44. package/lib/events/messageUpdate.d.ts +4 -0
  45. package/lib/events/messageUpdate.js +17 -0
  46. package/lib/experiments.d.ts +2 -0
  47. package/lib/experiments.js +1 -0
  48. package/lib/main.d.ts +18 -0
  49. package/lib/main.js +25 -0
  50. package/package.json +69 -0
@@ -0,0 +1,705 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Utils = void 0;
4
+
5
+ const tslib_1 = require("tslib");
6
+ const types_1 = require("util/types");
7
+ const DJS = tslib_1.__importStar(require("discord.js"));
8
+ const Errors = tslib_1.__importStar(require("./Errors.js"));
9
+ const Cooldowns_js_1 = require("./Cooldowns.js");
10
+
11
+ class Utils {
12
+ static isType(obj, func) {
13
+ return func(obj);
14
+ }
15
+
16
+ static noop(n = null) {
17
+ return n;
18
+ }
19
+
20
+ static #toLower(value) {
21
+ return String(value || "").toLowerCase().trim();
22
+ }
23
+
24
+ static #isBlank(value) {
25
+ return value === undefined || value === null || value === "";
26
+ }
27
+
28
+ static #consumeHeadTokens(text, count) {
29
+ let output = String(text || "").trim();
30
+ for (let index = 0; index < count; index += 1) {
31
+ output = output.replace(/^\S+\s*/u, "");
32
+ if (!output.length) return "";
33
+ }
34
+ return output.trimStart();
35
+ }
36
+
37
+ static async getMember(query, options) {
38
+ if (!query || !options?.guild) return null;
39
+
40
+ const cleanQuery = Utils.#toLower(query);
41
+ const id = query.replace(/[^\d]/g, "");
42
+ let member = id ? options.guild.members.cache.get(id) : null;
43
+
44
+ member =
45
+ member ||
46
+ options.guild.members.cache.find((target) => {
47
+ const username = Utils.#toLower(target.user.username);
48
+ const displayName = Utils.#toLower(target.displayName);
49
+ const globalName = Utils.#toLower(target.user.globalName);
50
+ return username === cleanQuery || displayName === cleanQuery || globalName === cleanQuery || username.includes(cleanQuery);
51
+ }) ||
52
+ null;
53
+
54
+ if (!member && id && options.force) {
55
+ member = (await options.guild.members.fetch(id).catch(Utils.noop)) || null;
56
+ }
57
+
58
+ return member;
59
+ }
60
+
61
+ static async getChannel(query, options) {
62
+ if (!query || !options?.guild) return null;
63
+
64
+ const cleanQuery = Utils.#toLower(query);
65
+ const id = query.replace(/[^\d]/g, "");
66
+ let channel = id ? options.guild.channels.cache.get(id) : null;
67
+
68
+ channel =
69
+ channel ||
70
+ options.guild.channels.cache.find((target) => {
71
+ const name = Utils.#toLower(target.name);
72
+ return name === cleanQuery || name.includes(cleanQuery);
73
+ }) ||
74
+ null;
75
+
76
+ if (!channel && id && options.force) {
77
+ channel = (await options.guild.channels.fetch(id).catch(Utils.noop)) || null;
78
+ }
79
+
80
+ return channel;
81
+ }
82
+
83
+ static async getRole(query, options) {
84
+ if (!query || !options?.guild) return null;
85
+
86
+ const cleanQuery = Utils.#toLower(query);
87
+ const id = query.replace(/[^\d]/g, "");
88
+ let role = id ? options.guild.roles.cache.get(id) : null;
89
+
90
+ role =
91
+ role ||
92
+ options.guild.roles.cache.find((target) => {
93
+ const name = Utils.#toLower(target.name);
94
+ return name === cleanQuery || name.includes(cleanQuery);
95
+ }) ||
96
+ null;
97
+
98
+ if (!role && id && options.force) {
99
+ role = (await options.guild.roles.fetch(id).catch(Utils.noop)) || null;
100
+ }
101
+
102
+ return role;
103
+ }
104
+
105
+ static async getUser(query, bot) {
106
+ if (!query) return null;
107
+
108
+ const cleanQuery = Utils.#toLower(query);
109
+ const id = query.replace(/[^\d]/g, "");
110
+
111
+ const cached =
112
+ (id ? bot.users.cache.get(id) : null) ||
113
+ bot.users.cache.find((user) => {
114
+ const username = Utils.#toLower(user.username);
115
+ const globalName = Utils.#toLower(user.globalName);
116
+ return globalName === cleanQuery || username === cleanQuery || user.id === query;
117
+ }) ||
118
+ null;
119
+
120
+ if (cached) return cached;
121
+
122
+ try {
123
+ return await bot.users.fetch(id || query);
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ static #assertCommandShape(command) {
130
+ if (!command?.data?.name || typeof command.code !== "function") {
131
+ throw new Error("Invalid command module shape: expected { data: CommandBuilder, code(ctx) }.");
132
+ }
133
+ }
134
+
135
+ static #cloneParams(paramsBuilder) {
136
+ if (!paramsBuilder?.params?.length) return [];
137
+ return paramsBuilder.params.map((param) => ({ ...param, value: undefined }));
138
+ }
139
+
140
+ static #normalizeChoiceInput(input) {
141
+ return String(input ?? "").toLowerCase().trim();
142
+ }
143
+
144
+ static async transform(input, param, ctx, seeable = true) {
145
+ if (!input && seeable) {
146
+ return { break: false, value: null };
147
+ }
148
+
149
+ if (param.choices?.length) {
150
+ const normalizedInput = Utils.#normalizeChoiceInput(input);
151
+ const selected = param.choices.find(
152
+ (choice) => Utils.#normalizeChoiceInput(choice.name) === normalizedInput || String(choice.value) === String(input)
153
+ );
154
+ if (selected) return { break: false, value: selected.value };
155
+ throw new Errors.InvalidParamChoice(ctx, param, param.choices);
156
+ }
157
+
158
+ if (param.type === DJS.ApplicationCommandOptionType.String) {
159
+ const value = String(input ?? "");
160
+
161
+ if (typeof param.max_length === "number" && value.length > param.max_length) {
162
+ throw new Errors.InvalidParam(ctx, `${param.name} exceeds max length.`, { param });
163
+ }
164
+
165
+ if (typeof param.min_length === "number" && value.length < param.min_length) {
166
+ throw new Errors.InvalidParam(ctx, `${param.name} is under min length.`, { param });
167
+ }
168
+
169
+ return { break: false, value };
170
+ }
171
+
172
+ if (param.type === DJS.ApplicationCommandOptionType.Number) {
173
+ const value = Number(input);
174
+ if (Number.isNaN(value)) throw new Errors.InvalidParamNumber(ctx, param);
175
+
176
+ if (typeof param.max_value === "number" && value > param.max_value) {
177
+ throw new Errors.InvalidParam(ctx, `${param.name} exceeds max value.`, { param });
178
+ }
179
+
180
+ if (typeof param.min_value === "number" && value < param.min_value) {
181
+ throw new Errors.InvalidParam(ctx, `${param.name} is under min value.`, { param });
182
+ }
183
+
184
+ return { break: false, value };
185
+ }
186
+
187
+ if (param.type === DJS.ApplicationCommandOptionType.Boolean) {
188
+ const lowered = String(input).toLowerCase().trim();
189
+ if (lowered !== "true" && lowered !== "false") throw new Errors.InvalidParamBoolean(ctx, param);
190
+ return { break: false, value: lowered === "true" };
191
+ }
192
+
193
+ if (param.type === DJS.ApplicationCommandOptionType.User) {
194
+ const member = ctx.guild ? await Utils.getMember(String(input), { guild: ctx.guild, force: true }) : await Utils.getUser(String(input), ctx.bot);
195
+ if (member) return { break: false, value: member };
196
+ throw new Errors.InvalidParamMember(ctx, param);
197
+ }
198
+
199
+ if (param.type === DJS.ApplicationCommandOptionType.Channel) {
200
+ const channel = await Utils.getChannel(String(input), { guild: ctx.guild, force: true });
201
+ if (!channel) throw new Errors.InvalidParamChannel(ctx, param);
202
+
203
+ if (param.channel_types?.length && !param.channel_types.includes(channel.type)) {
204
+ throw new Errors.InvalidChannelType(ctx, param, channel.type, param.channel_types);
205
+ }
206
+
207
+ return { break: false, value: channel };
208
+ }
209
+
210
+ if (param.type === DJS.ApplicationCommandOptionType.Role) {
211
+ const role = await Utils.getRole(String(input), { guild: ctx.guild, force: true });
212
+ if (role) return { break: false, value: role };
213
+ throw new Errors.InvalidParamRole(ctx, param);
214
+ }
215
+
216
+ if (param.type === DJS.ApplicationCommandOptionType.Attachment) {
217
+ const attachment = ctx.message?.attachments?.first();
218
+ if (!attachment && param.required) throw new Errors.InvalidParamAttachment(ctx, param);
219
+ return { break: false, value: attachment };
220
+ }
221
+
222
+ return { break: false, value: input ?? null };
223
+ }
224
+
225
+ static async #resolvePrefixParams(ctx, command, args) {
226
+ const params = Utils.#cloneParams(command.params);
227
+ if (!params.length) {
228
+ ctx.params = [];
229
+ ctx.args = args;
230
+ return;
231
+ }
232
+
233
+ let argIndex = 0;
234
+
235
+ for (let paramIndex = 0; paramIndex < params.length; paramIndex += 1) {
236
+ const param = params[paramIndex];
237
+
238
+ if (param.type === DJS.ApplicationCommandOptionType.Attachment) {
239
+ const transformed = await Utils.transform("", param, ctx, false);
240
+ param.value = transformed.value;
241
+ continue;
242
+ }
243
+
244
+ if (param.ellipsis) {
245
+ const remaining = args.slice(argIndex).join(" ").trim();
246
+
247
+ if (!remaining && param.required) {
248
+ throw new Errors.MissingRequiredParam(ctx, param);
249
+ }
250
+
251
+ if (remaining) {
252
+ const transformed = await Utils.transform(remaining, param, ctx);
253
+ param.value = transformed.value;
254
+ argIndex = args.length;
255
+ } else {
256
+ param.value = null;
257
+ }
258
+
259
+ continue;
260
+ }
261
+
262
+ const raw = args[argIndex];
263
+ if (Utils.#isBlank(raw) && param.required) {
264
+ throw new Errors.MissingRequiredParam(ctx, param);
265
+ }
266
+
267
+ if (Utils.#isBlank(raw)) {
268
+ param.value = null;
269
+ } else {
270
+ const transformed = await Utils.transform(raw, param, ctx);
271
+ param.value = transformed.value;
272
+ }
273
+
274
+ argIndex += 1;
275
+ }
276
+
277
+ ctx.params = params;
278
+ ctx.args = args;
279
+ }
280
+
281
+ static #resolveInteractionParamValue(interaction, param) {
282
+ const name = param.name;
283
+ const required = !!param.required;
284
+
285
+ switch (param.type) {
286
+ case DJS.ApplicationCommandOptionType.String:
287
+ return interaction.options.getString(name, required);
288
+ case DJS.ApplicationCommandOptionType.Number:
289
+ return interaction.options.getNumber(name, required);
290
+ case DJS.ApplicationCommandOptionType.Boolean:
291
+ return interaction.options.getBoolean(name, required);
292
+ case DJS.ApplicationCommandOptionType.User:
293
+ return interaction.options.getMember(name) ?? interaction.options.getUser(name, required);
294
+ case DJS.ApplicationCommandOptionType.Channel:
295
+ return interaction.options.getChannel(name, required);
296
+ case DJS.ApplicationCommandOptionType.Role:
297
+ return interaction.options.getRole(name, required);
298
+ case DJS.ApplicationCommandOptionType.Attachment:
299
+ return interaction.options.getAttachment(name, required);
300
+ default:
301
+ return interaction.options.get(name)?.value ?? null;
302
+ }
303
+ }
304
+
305
+ static async #resolveInteractionParams(ctx, command, interaction) {
306
+ const params = Utils.#cloneParams(command.params);
307
+
308
+ for (const param of params) {
309
+ const value = Utils.#resolveInteractionParamValue(interaction, param);
310
+
311
+ if ((value === null || value === undefined) && param.required) {
312
+ throw new Errors.MissingRequiredParam(ctx, param);
313
+ }
314
+
315
+ param.value = value ?? null;
316
+ }
317
+
318
+ ctx.params = params;
319
+ ctx.args = null;
320
+ }
321
+
322
+ static async #runPlugins(ctx, command) {
323
+ const plugins = [...(command.plugins ?? []), ...ctx.bot.loader.globalPlugins];
324
+ for (const plugin of plugins) {
325
+ if ((0, types_1.isPromise)(plugin)) {
326
+ const resolved = await plugin;
327
+ if (!(await resolved(ctx))) return false;
328
+ continue;
329
+ }
330
+
331
+ if (!(await plugin(ctx))) return false;
332
+ }
333
+
334
+ return true;
335
+ }
336
+
337
+ static async #runPredicates(ctx, predicates) {
338
+ for (const predicate of predicates) {
339
+ if (!predicate) continue;
340
+ if (!(await predicate(ctx))) return false;
341
+ }
342
+ return true;
343
+ }
344
+
345
+ static #getMemberPermissions(ctx) {
346
+ if (!ctx.member) return null;
347
+
348
+ if (ctx.member.permissions?.has) {
349
+ return ctx.member.permissions;
350
+ }
351
+
352
+ if (ctx.member.permissions !== undefined) {
353
+ try {
354
+ return new DJS.PermissionsBitField(ctx.member.permissions);
355
+ } catch {
356
+ return null;
357
+ }
358
+ }
359
+
360
+ return null;
361
+ }
362
+
363
+ static #assertRequiredPermissions(ctx, requiredUserPerms = [], requiredBotPerms = []) {
364
+ if (!ctx.guild) return;
365
+
366
+ if (requiredUserPerms.length) {
367
+ const memberPermissions = Utils.#getMemberPermissions(ctx);
368
+ if (!memberPermissions?.has(requiredUserPerms)) {
369
+ throw new Errors.MissingPermission(ctx, requiredUserPerms);
370
+ }
371
+ }
372
+
373
+ if (requiredBotPerms.length) {
374
+ const botPermissions = ctx.guild.members.me?.permissions;
375
+ if (!botPermissions?.has(requiredBotPerms)) {
376
+ throw new Errors.MissingBotPermission(ctx, requiredBotPerms);
377
+ }
378
+ }
379
+ }
380
+
381
+ static #resolveCooldownSource(ctx, bucket) {
382
+ switch (bucket) {
383
+ case Cooldowns_js_1.Bucket.Guild:
384
+ return ctx.guild?.id || "-1";
385
+ case Cooldowns_js_1.Bucket.Member:
386
+ return ctx.guild ? `${ctx.guild.id}_${ctx.author.id}` : ctx.author.id;
387
+ case Cooldowns_js_1.Bucket.Channel:
388
+ return ctx.channel?.id || "-1";
389
+ case Cooldowns_js_1.Bucket.User:
390
+ default:
391
+ return ctx.author.id;
392
+ }
393
+ }
394
+
395
+ static async #applyCooldown(ctx, command) {
396
+ const cooldown = command?.data?.cooldown;
397
+ if (!cooldown?.seconds || cooldown.seconds <= 0) return;
398
+
399
+ const bucket = cooldown.bucket || Cooldowns_js_1.Bucket.User;
400
+ const source = Utils.#resolveCooldownSource(ctx, bucket);
401
+ const cooldownMs = cooldown.seconds * 1000;
402
+
403
+ const active = await ctx.bot.cooldowns.check(command.data.name, source, cooldownMs, bucket);
404
+ if (active) throw new Errors.CommandInCooldown(ctx, active.left);
405
+
406
+ await ctx.bot.cooldowns.setCooldownSource(command.data.name, source, bucket, cooldownMs);
407
+ }
408
+
409
+ static #validateContextRestrictions(ctx) {
410
+ if (ctx.guild && ctx.bot.ops.restrictions?.guildIDs?.has(String(ctx.guild.id))) {
411
+ throw new Errors.RestrictedGuild(ctx, ctx.guild);
412
+ }
413
+
414
+ if (ctx.bot.ops.restrictions?.userIDs?.has(ctx.author.id)) {
415
+ throw new Errors.RestrictedUser(ctx, ctx.author);
416
+ }
417
+ }
418
+
419
+ static #prepareExecutionContext(ctx, command, group, source, prefix = "") {
420
+ ctx.command = command;
421
+ ctx.parent = group;
422
+ ctx.prefix = prefix;
423
+ ctx.args = source === "prefix" ? [] : null;
424
+ ctx.params = [];
425
+ }
426
+
427
+ static #assertGuildRestrictions(ctx, command, group) {
428
+ if (ctx.bot.ops.guildOnly && !ctx.guild) {
429
+ throw new Errors.GuildOnly(ctx);
430
+ }
431
+
432
+ if (command.data.guildOnly === true && !ctx.guild) {
433
+ throw new Errors.GuildOnly(ctx, "This command is only available in guilds.");
434
+ }
435
+
436
+ if (group?.data?.guildOnly === true && !ctx.guild) {
437
+ throw new Errors.GuildOnly(ctx, "This command group is only available in guilds.");
438
+ }
439
+ }
440
+
441
+ static async #resolveCommandInput(ctx, command, options) {
442
+ if (options.source === "prefix") {
443
+ await Utils.#resolvePrefixParams(ctx, command, options.args ?? []);
444
+ return;
445
+ }
446
+
447
+ if (!options.interaction) {
448
+ throw new Error("Missing interaction for slash execution.");
449
+ }
450
+
451
+ await Utils.#resolveInteractionParams(ctx, command, options.interaction);
452
+ }
453
+
454
+ static async #runPreExecution(ctx, command, group) {
455
+ Utils.#assertRequiredPermissions(
456
+ ctx,
457
+ [...(group?.data?.userPermissions ?? []), ...(command.data?.userPermissions ?? [])],
458
+ [...(group?.data?.botPermissions ?? []), ...(command.data?.botPermissions ?? [])]
459
+ );
460
+
461
+ const canRunPlugins = await Utils.#runPlugins(ctx, command);
462
+ if (!canRunPlugins) return false;
463
+
464
+ const canRunGuards = await Utils.#runPredicates(ctx, [...(group?.data?.guards ?? []), ...(command.data?.guards ?? [])]);
465
+ if (!canRunGuards) return false;
466
+
467
+ await Utils.#applyCooldown(ctx, command);
468
+ return true;
469
+ }
470
+
471
+ static async executeCommand(ctx, command, options) {
472
+ Utils.#assertCommandShape(command);
473
+
474
+ const { group = null, source, prefix = "" } = options;
475
+ Utils.#prepareExecutionContext(ctx, command, group, source, prefix);
476
+ Utils.#assertGuildRestrictions(ctx, command, group);
477
+
478
+ const canProceed = await Utils.#runPreExecution(ctx, command, group);
479
+ if (!canProceed) return;
480
+
481
+ await Utils.#resolveCommandInput(ctx, command, options);
482
+ await command.code(ctx);
483
+ }
484
+
485
+ static async #resolvePrefix(bot, ctx, message) {
486
+ if (typeof bot.ops.prefix === "string") {
487
+ if (!message.content.toLowerCase().startsWith(bot.ops.prefix.toLowerCase())) return null;
488
+ return bot.ops.prefix;
489
+ }
490
+
491
+ if (Array.isArray(bot.ops.prefix)) {
492
+ return bot.ops.prefix.find((prefix) => message.content.toLowerCase().startsWith(prefix.toLowerCase())) || null;
493
+ }
494
+
495
+ if ((0, types_1.isAsyncFunction)(bot.ops.prefix) || typeof bot.ops.prefix === "function") {
496
+ const resolved = await bot.ops.prefix(ctx);
497
+ if (!resolved) return null;
498
+
499
+ if (Array.isArray(resolved)) {
500
+ return resolved.find((prefix) => message.content.toLowerCase().startsWith(String(prefix).toLowerCase())) || null;
501
+ }
502
+
503
+ if (!message.content.toLowerCase().startsWith(String(resolved).toLowerCase())) return null;
504
+ return String(resolved);
505
+ }
506
+
507
+ return null;
508
+ }
509
+
510
+ static #matchAlias(target, value) {
511
+ const lowered = value.toLowerCase();
512
+ return target.data.name.toLowerCase() === lowered || (target.data.aliases || []).map((alias) => alias.toLowerCase()).includes(lowered);
513
+ }
514
+
515
+ static #resolvePrefixRoute(bot, body) {
516
+ const routingTokens = body.trim().split(/\s+/g).filter(Boolean);
517
+ const trigger = (routingTokens.shift() || "").toLowerCase();
518
+ if (!trigger) return null;
519
+
520
+ const normal =
521
+ bot.loader.commands.normal?.find((command) => Utils.#matchAlias(command, trigger) && command.data.as_prefix) || null;
522
+
523
+ const group = bot.loader.commands.group?.find((entry) => Utils.#matchAlias(entry, trigger) && entry.data.as_prefix) || null;
524
+
525
+ if (group) {
526
+ const maybeSub = (routingTokens.shift() || "").toLowerCase();
527
+ const sub =
528
+ group.data.commands.find((command) => Utils.#matchAlias(command, maybeSub) && command.data.as_prefix) ||
529
+ group.data.commands.find((command) => command.data.fallback && command.data.as_prefix) ||
530
+ null;
531
+
532
+ if (sub) {
533
+ const consumedTokens = maybeSub ? 2 : 1;
534
+ return { command: sub, group, consumedTokens };
535
+ }
536
+ }
537
+
538
+ if (!normal) return null;
539
+ return { command: normal, group: null, consumedTokens: 1 };
540
+ }
541
+
542
+ static async handleMessage(bot, message, isEdit = false) {
543
+ if (!message || message.author?.bot) return;
544
+
545
+ if (message.partial) {
546
+ try {
547
+ await message.fetch();
548
+ } catch {
549
+ return;
550
+ }
551
+ }
552
+
553
+ if (!message.content) return;
554
+
555
+ const ctx = bot.getContext(message);
556
+ ctx.isEditedMessage = Boolean(isEdit);
557
+ Utils.#validateContextRestrictions(ctx);
558
+
559
+ const prefix = await Utils.#resolvePrefix(bot, ctx, message);
560
+ if (!prefix) return;
561
+
562
+ const body = message.content.slice(prefix.length).trim();
563
+ if (!body.length) return;
564
+
565
+ const resolved = Utils.#resolvePrefixRoute(bot, body);
566
+ if (!resolved) return;
567
+
568
+ const argsBody = Utils.#consumeHeadTokens(body, resolved.consumedTokens);
569
+ const useQuoted = resolved.command.params?.quoted !== false;
570
+ const args = argsBody.length ? Utils.splitArgs(argsBody, useQuoted, false) : [];
571
+
572
+ await Utils.executeCommand(ctx, resolved.command, {
573
+ group: resolved.group,
574
+ source: "prefix",
575
+ args,
576
+ prefix,
577
+ });
578
+ }
579
+
580
+ static #findSlashCommand(bot, interaction) {
581
+ const subCommand = interaction.options.getSubcommand(false);
582
+
583
+ if (subCommand) {
584
+ const group = bot.loader.commands.group?.get(interaction.commandName) || null;
585
+ if (!group?.data?.as_slash) return null;
586
+
587
+ const command =
588
+ group.data.commands.find((entry) => entry.data.name === subCommand && entry.data.as_slash) ||
589
+ group.data.commands.find((entry) => entry.data.fallback && entry.data.as_slash) ||
590
+ null;
591
+
592
+ if (!command) return null;
593
+ return { command, group };
594
+ }
595
+
596
+ const command = bot.loader.commands.normal?.find((entry) => entry.data.name === interaction.commandName && entry.data.as_slash) || null;
597
+ if (!command) return null;
598
+
599
+ return { command, group: null };
600
+ }
601
+
602
+ static #resolveInteractionModule(bot, interaction) {
603
+ if (interaction.isAutocomplete()) return bot.loader.interactions.autocomplete.get(interaction.commandName) || null;
604
+ if (interaction.isButton()) return bot.loader.interactions.button.get(interaction.customId) || null;
605
+ if (interaction.isModalSubmit()) return bot.loader.interactions.modalSubmit.get(interaction.customId) || null;
606
+ if (interaction.isMessageContextMenuCommand()) return bot.loader.interactions.messageContextMenu.get(interaction.commandName) || null;
607
+ if (interaction.isUserContextMenuCommand()) return bot.loader.interactions.userContextMenu.get(interaction.commandName) || null;
608
+ if (interaction.isChannelSelectMenu()) return bot.loader.interactions.channelSelectMenu.get(interaction.customId) || null;
609
+ if (interaction.isMentionableSelectMenu()) return bot.loader.interactions.mentionableSelectMenu.get(interaction.customId) || null;
610
+ if (interaction.isRoleSelectMenu()) return bot.loader.interactions.roleSelectMenu.get(interaction.customId) || null;
611
+ if (interaction.isStringSelectMenu()) return bot.loader.interactions.stringSelectMenu.get(interaction.customId) || null;
612
+ if (interaction.isUserSelectMenu()) return bot.loader.interactions.userSelectMenu.get(interaction.customId) || null;
613
+ return null;
614
+ }
615
+
616
+ static async #executeInteractionModule(bot, interaction, moduleData) {
617
+ if (!moduleData?.code) return;
618
+
619
+ if (moduleData.code.length >= 2) {
620
+ await moduleData.code(bot, interaction);
621
+ return;
622
+ }
623
+
624
+ await moduleData.code(interaction);
625
+ }
626
+
627
+ static async #runAnyInteractionHooks(bot, interaction) {
628
+ for (const callback of bot.loader.interactions.anyInteraction.values()) {
629
+ if (!callback?.code) continue;
630
+
631
+ if (callback.code.length >= 2) {
632
+ await callback.code(bot, interaction);
633
+ continue;
634
+ }
635
+
636
+ await callback.code(interaction);
637
+ }
638
+ }
639
+
640
+ static async handleInteraction(bot, interaction) {
641
+ if (!interaction) return;
642
+
643
+ const ctx = bot.getContext(interaction);
644
+ Utils.#validateContextRestrictions(ctx);
645
+
646
+ if (interaction.isChatInputCommand()) {
647
+ const resolved = Utils.#findSlashCommand(bot, interaction);
648
+ if (!resolved) return;
649
+
650
+ await Utils.executeCommand(ctx, resolved.command, {
651
+ group: resolved.group,
652
+ source: "slash",
653
+ interaction,
654
+ prefix: "/",
655
+ });
656
+ return;
657
+ }
658
+
659
+ const matched = Utils.#resolveInteractionModule(bot, interaction);
660
+ if (matched) {
661
+ await Utils.#executeInteractionModule(bot, interaction, matched);
662
+ }
663
+
664
+ await Utils.#runAnyInteractionHooks(bot, interaction);
665
+ }
666
+
667
+ static async runPrefixCommand(ctx, command, args, group) {
668
+ await Utils.executeCommand(ctx, command, {
669
+ group: group || null,
670
+ source: "prefix",
671
+ args,
672
+ prefix: ctx.prefix,
673
+ });
674
+ }
675
+
676
+ static async runInteractionCommand(ctx, interaction) {
677
+ const resolved = Utils.#findSlashCommand(ctx.bot, interaction);
678
+ if (!resolved) return;
679
+
680
+ await Utils.executeCommand(ctx, resolved.command, {
681
+ group: resolved.group,
682
+ source: "slash",
683
+ interaction,
684
+ prefix: "/",
685
+ });
686
+ }
687
+
688
+ static splitArgs(text, special = true, removeNewLines = false) {
689
+ if (!special) {
690
+ return text.trim().split(/ +/g).filter(Boolean);
691
+ }
692
+
693
+ const regexp = /("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[\S]+)/gim;
694
+ const clean = text.replaceAll(/\r|\n/gm, removeNewLines ? "" : "#ER_BREAK_LINE#");
695
+ const args = [];
696
+
697
+ let match;
698
+ while ((match = regexp.exec(clean))) {
699
+ args.push(match[0].replace(/^(['"])(.*)\1$/, "$2").replace(/\\(.)/gim, "$1"));
700
+ }
701
+
702
+ return args.map((arg) => arg.replaceAll("#ER_BREAK_LINE#", "\n"));
703
+ }
704
+ }
705
+ exports.Utils = Utils;