@seedcord/gateway 0.1.4 → 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.
- package/README.md +37 -8
- package/dist/index.d.mts +85 -74
- package/dist/index.mjs +67 -71
- package/dist/index.mjs.map +1 -1
- package/package.json +27 -40
- package/dist/index.cjs +0 -2486
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1
package/dist/index.cjs
DELETED
|
@@ -1,2486 +0,0 @@
|
|
|
1
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
//#region \0rolldown/runtime.js
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
-
key = keys[i];
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
-
__defProp(to, key, {
|
|
15
|
-
get: ((k) => from[k]).bind(null, key),
|
|
16
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return to;
|
|
22
|
-
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
24
|
-
value: mod,
|
|
25
|
-
enumerable: true
|
|
26
|
-
}) : target, mod));
|
|
27
|
-
|
|
28
|
-
//#endregion
|
|
29
|
-
require("reflect-metadata");
|
|
30
|
-
let _seedcord_logger_node = require("@seedcord/logger/node");
|
|
31
|
-
let _seedcord_core_internal = require("@seedcord/core/internal");
|
|
32
|
-
let _seedcord_errors = require("@seedcord/errors");
|
|
33
|
-
let _seedcord_errors_internal = require("@seedcord/errors/internal");
|
|
34
|
-
let _seedcord_core = require("@seedcord/core");
|
|
35
|
-
let _seedcord_logger = require("@seedcord/logger");
|
|
36
|
-
let discord_js = require("discord.js");
|
|
37
|
-
let _discordjs_builders = require("@discordjs/builders");
|
|
38
|
-
let _seedcord_utils_internal = require("@seedcord/utils/internal");
|
|
39
|
-
let _seedcord_core_plugin = require("@seedcord/core/plugin");
|
|
40
|
-
let _seedcord_core_node_internal = require("@seedcord/core/node/internal");
|
|
41
|
-
let _seedcord_rate_limiter = require("@seedcord/rate-limiter");
|
|
42
|
-
let _seedcord_types_internal = require("@seedcord/types/internal");
|
|
43
|
-
let envapt = require("envapt");
|
|
44
|
-
let chalk = require("chalk");
|
|
45
|
-
chalk = __toESM(chalk, 1);
|
|
46
|
-
let envapt_legacy = require("envapt/legacy");
|
|
47
|
-
let _seedcord_core_hmr = require("@seedcord/core/hmr");
|
|
48
|
-
let _seedcord_utils = require("@seedcord/utils");
|
|
49
|
-
let _seedcord_utils_node = require("@seedcord/utils/node");
|
|
50
|
-
let node_crypto = require("node:crypto");
|
|
51
|
-
node_crypto = __toESM(node_crypto, 1);
|
|
52
|
-
|
|
53
|
-
//#region src/logger-bootstrap.ts
|
|
54
|
-
(0, _seedcord_logger_node.installNodeDefaults)();
|
|
55
|
-
|
|
56
|
-
//#endregion
|
|
57
|
-
//#region src/bot/decorators/Events.ts
|
|
58
|
-
/**
|
|
59
|
-
* Registers an event handler class with one or more Discord.js events.
|
|
60
|
-
*
|
|
61
|
-
* Associates the decorated class with the provided Discord client events for automatic registration and execution when those events are emitted.
|
|
62
|
-
*
|
|
63
|
-
* Supply any number of event tuples. Each tuple can include per event options.
|
|
64
|
-
*
|
|
65
|
-
* @typeParam EventKey - Union of Discord.js ClientEvents keys to register for
|
|
66
|
-
* @param defs - One or more tuples of [event, options]
|
|
67
|
-
* @decorator
|
|
68
|
-
* @example
|
|
69
|
-
* ```ts
|
|
70
|
-
* \@RegisterEvent([Events.MessageCreate])
|
|
71
|
-
* class MessageHandler extends EventHandler<Events.MessageCreate> {
|
|
72
|
-
* async execute() {
|
|
73
|
-
* // Handle message creation
|
|
74
|
-
* }
|
|
75
|
-
* }
|
|
76
|
-
* ```
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```ts
|
|
80
|
-
* \@RegisterEvent([Events.MessageCreate], [Events.MessageUpdate])
|
|
81
|
-
* class MessageHandler extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
|
|
82
|
-
* async execute() {
|
|
83
|
-
* // Handle message creation or update
|
|
84
|
-
* }
|
|
85
|
-
* }
|
|
86
|
-
* ```
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```ts
|
|
90
|
-
* \@RegisterEvent([Events.MessageCreate, { frequency: 'once' }], [Events.MessageUpdate])
|
|
91
|
-
* class OneTimeMessageHandler extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
|
|
92
|
-
* async execute() {
|
|
93
|
-
* // Handle only the first message creation, and message update normally
|
|
94
|
-
* }
|
|
95
|
-
* }
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
function RegisterEvent(...defs) {
|
|
99
|
-
return function(constructor) {
|
|
100
|
-
const existing = Reflect.getMetadata(_seedcord_core_internal.EventMetadataKey, constructor) ?? [];
|
|
101
|
-
const entries = defs.map(([event, options]) => ({
|
|
102
|
-
event,
|
|
103
|
-
frequency: options?.frequency ?? "on"
|
|
104
|
-
}));
|
|
105
|
-
Reflect.defineMetadata(_seedcord_core_internal.EventMetadataKey, [...existing, ...entries], constructor);
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
//#endregion
|
|
110
|
-
//#region src/bot/decorators/Gated.ts
|
|
111
|
-
/**
|
|
112
|
-
* Attaches gates to a handler. The gates run before `execute`, and a gate refusing stops the handler with
|
|
113
|
-
* the reply or drop the gate threw. Multiple gates are ANDed, so each must pass. A gate that requires a
|
|
114
|
-
* context the handler does not provide (a button gate on a slash handler, an interaction gate on an event
|
|
115
|
-
* handler) is a compile error at this line, and the error names the gate and the handler kind. Place it
|
|
116
|
-
* above the route decorator.
|
|
117
|
-
*
|
|
118
|
-
* Combine gates with {@link and} or {@link or} to build a single gate from multiple arms.
|
|
119
|
-
*
|
|
120
|
-
* @example
|
|
121
|
-
* ```ts
|
|
122
|
-
* import { PermissionFlagsBits } from 'discord.js';
|
|
123
|
-
*
|
|
124
|
-
* \@Gated(GuildOnly(), RequirePermissions([PermissionFlagsBits.Administrator]))
|
|
125
|
-
* \@SlashRoute('maintenance')
|
|
126
|
-
* class Maintenance extends SlashHandler<'maintenance'> {
|
|
127
|
-
* async execute() {
|
|
128
|
-
* // both gates passed, so this runs in a guild with an admin caller
|
|
129
|
-
* }
|
|
130
|
-
* }
|
|
131
|
-
* ```
|
|
132
|
-
*
|
|
133
|
-
* @typeParam Gates - The non-empty tuple of gates to run, in order, against the handler's context.
|
|
134
|
-
* @typeParam TCtor - The decorated handler's constructor, checked against every gate's required context.
|
|
135
|
-
* @param gates - One or more gates to attach, each ANDed so the handler runs only when all pass.
|
|
136
|
-
* @param ctor - The handler class being decorated, rejected at compile time when a gate needs a context it lacks.
|
|
137
|
-
*
|
|
138
|
-
* @see {@link defineGate}
|
|
139
|
-
* @see {@link defineEffectGate}
|
|
140
|
-
* @decorator
|
|
141
|
-
*/
|
|
142
|
-
function Gated(...gates) {
|
|
143
|
-
return function(ctor) {
|
|
144
|
-
const existing = Reflect.getMetadata(_seedcord_core_internal.GatedMetadataKey, ctor) ?? [];
|
|
145
|
-
Reflect.defineMetadata(_seedcord_core_internal.GatedMetadataKey, [...existing, ...gates], ctor);
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
//#endregion
|
|
150
|
-
//#region src/bot/decorators/Middlewares.ts
|
|
151
|
-
/**
|
|
152
|
-
* Middleware types supported by Seedcord
|
|
153
|
-
*/
|
|
154
|
-
let MiddlewareType = /* @__PURE__ */ function(MiddlewareType) {
|
|
155
|
-
MiddlewareType["Interaction"] = "interaction";
|
|
156
|
-
MiddlewareType["Event"] = "event";
|
|
157
|
-
return MiddlewareType;
|
|
158
|
-
}({});
|
|
159
|
-
/**
|
|
160
|
-
* Decorator used to register middleware with priority ordering. The lower the priority number, the earlier it runs.
|
|
161
|
-
*
|
|
162
|
-
* Interaction middleware cannot specify event filters.
|
|
163
|
-
*
|
|
164
|
-
* @param type - Middleware kind from {@link MiddlewareType}
|
|
165
|
-
* @param priority - Ordering value where lower runs earlier. {@default `0` }
|
|
166
|
-
* @param options - Additional registration options
|
|
167
|
-
*
|
|
168
|
-
* @decorator
|
|
169
|
-
*
|
|
170
|
-
* @example
|
|
171
|
-
* ```ts
|
|
172
|
-
* \@Middleware(MiddlewareType.Event, 10, { events: [Events.MessageCreate, Events.MessageUpdate] })
|
|
173
|
-
* class MyEventMiddleware extends EventMiddleware {}
|
|
174
|
-
* ```
|
|
175
|
-
* @throws A **SeedcordTypeError** If priority is not a finite number
|
|
176
|
-
* @throws A **SeedcordError** If interaction middleware specifies event filters
|
|
177
|
-
*/
|
|
178
|
-
function Middleware(type, priority = 0, options = {}) {
|
|
179
|
-
return (ctor) => {
|
|
180
|
-
const normalizedPriority = Number(priority);
|
|
181
|
-
if (!Number.isFinite(normalizedPriority)) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.DecoratorInvalidMiddlewarePriority);
|
|
182
|
-
if (type === "interaction" && Array.isArray(options.events) && options.events.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorInteractionEventFilter);
|
|
183
|
-
const metadata = {
|
|
184
|
-
priority: normalizedPriority,
|
|
185
|
-
type,
|
|
186
|
-
...options.events && options.events.length > 0 && { events: options.events }
|
|
187
|
-
};
|
|
188
|
-
Reflect.defineMetadata(_seedcord_core_internal.MiddlewareMetadataKey, metadata, ctor);
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
//#endregion
|
|
193
|
-
//#region src/bot/gates/catalog/IgnoreBots.ts
|
|
194
|
-
/**
|
|
195
|
-
* Drops a client event whose actor is a bot, with a {@link Silence} so nothing is replied. Event-only, because a
|
|
196
|
-
* Silence on an interaction would leave Discord's failed-interaction state. It rejects an interaction handler at
|
|
197
|
-
* the decorator line. Takes no options, so attach it directly without calling it.
|
|
198
|
-
*
|
|
199
|
-
* @see {@link Gated}
|
|
200
|
-
*
|
|
201
|
-
* @example
|
|
202
|
-
* ```ts
|
|
203
|
-
* import { Events } from 'discord.js';
|
|
204
|
-
*
|
|
205
|
-
* \@Gated(IgnoreBots)
|
|
206
|
-
* class OnMessage extends EventHandler<Events.MessageCreate> {
|
|
207
|
-
* async execute() {
|
|
208
|
-
* // ...
|
|
209
|
-
* }
|
|
210
|
-
* }
|
|
211
|
-
* ```
|
|
212
|
-
*/
|
|
213
|
-
const IgnoreBots = (0, _seedcord_core.defineGate)("IgnoreBots", (ctx) => {
|
|
214
|
-
if (ctx.user?.bot) throw new _seedcord_core.Silence("actor is a bot");
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
//#endregion
|
|
218
|
-
//#region src/bot/notices/index.ts
|
|
219
|
-
var NotNsfw = class extends _seedcord_core_internal.GateNotice {
|
|
220
|
-
constructor(message = "This can only be used in an age-restricted channel.") {
|
|
221
|
-
super(message);
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
var RoleHigherThanMe = class extends _seedcord_core.Notice {
|
|
225
|
-
role;
|
|
226
|
-
botRole;
|
|
227
|
-
constructor(message, role, botRole) {
|
|
228
|
-
super(message);
|
|
229
|
-
this.role = role;
|
|
230
|
-
this.botRole = botRole;
|
|
231
|
-
}
|
|
232
|
-
render() {
|
|
233
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`I cannot assign a role that is higher than me.\n\nThe role <@&${this.role.id}> is higher than my role <@&${this.botRole.id}> in the hierarchy.`).component] };
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
var CannotAssignBotRole = class extends _seedcord_core.Notice {
|
|
237
|
-
constructor(message = "I cannot assign a managed role.") {
|
|
238
|
-
super(message);
|
|
239
|
-
}
|
|
240
|
-
render() {
|
|
241
|
-
return { components: [new _seedcord_core_internal.NoticeCard("I cannot assign a managed role.").component] };
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
//#endregion
|
|
246
|
-
//#region src/bot/gates/catalog/Nsfw.ts
|
|
247
|
-
function channelIsNsfw(channel) {
|
|
248
|
-
if (!channel) return false;
|
|
249
|
-
if (channel.isThread()) return channel.parent?.nsfw ?? false;
|
|
250
|
-
return "nsfw" in channel ? channel.nsfw : false;
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Requires an age-restricted channel, else refuses. Pass {@link GateNoticeOptions} to reword or replace the refusal.
|
|
254
|
-
*
|
|
255
|
-
* A thread inherits its parent channel's nsfw flag. ModalSubmit has no reliable channel, so it is excluded.
|
|
256
|
-
*
|
|
257
|
-
* @param options - Reword the default refusal with `message`, or replace it with `notice`.
|
|
258
|
-
*
|
|
259
|
-
* @see {@link Gated}
|
|
260
|
-
*
|
|
261
|
-
* @example
|
|
262
|
-
* ```ts
|
|
263
|
-
* \@Gated(Nsfw())
|
|
264
|
-
* \@SlashRoute('nsfwcmd')
|
|
265
|
-
* class NsfwHandler extends SlashHandler<'nsfwcmd'> {
|
|
266
|
-
* async execute() {
|
|
267
|
-
* // ...
|
|
268
|
-
* }
|
|
269
|
-
* }
|
|
270
|
-
* ```
|
|
271
|
-
*/
|
|
272
|
-
function Nsfw(options) {
|
|
273
|
-
return (0, _seedcord_core.defineGate)("Nsfw", (ctx) => {
|
|
274
|
-
if (channelIsNsfw(ctx.interaction.channel)) return;
|
|
275
|
-
throw (0, _seedcord_core_internal.pickNotice)(options, (message) => new NotNsfw(message));
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
//#endregion
|
|
280
|
-
//#region src/bot/injectors/EmojiInjector.ts
|
|
281
|
-
const emojiStorage = (0, _seedcord_core_internal.accessorStore)();
|
|
282
|
-
/**
|
|
283
|
-
* The bot's resolved emojis, keyed by {@link EmojiMap}. Filled by {@link EmojiInjector} during startup.
|
|
284
|
-
* A read before that throws.
|
|
285
|
-
*/
|
|
286
|
-
const Emojis = (0, _seedcord_core_internal.guardedAccessor)("Emojis", emojiStorage);
|
|
287
|
-
var EmojiInjector = class {
|
|
288
|
-
core;
|
|
289
|
-
logger = new _seedcord_logger.Logger("Emojis", { channel: "bot" });
|
|
290
|
-
constructor(core) {
|
|
291
|
-
this.core = core;
|
|
292
|
-
}
|
|
293
|
-
async init() {
|
|
294
|
-
this.clearEmojis();
|
|
295
|
-
if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
|
|
296
|
-
this.logger.debug("No emojis configured, skipping emoji injection.");
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
const configEmojis = this.core.config.bot.emojis;
|
|
300
|
-
await this.core.bot.client.application?.emojis.fetch();
|
|
301
|
-
const failures = [];
|
|
302
|
-
const fetchedGuilds = /* @__PURE__ */ new Set();
|
|
303
|
-
for (const [key, value] of Object.entries(configEmojis)) if ((0, _seedcord_core_internal.isEmojiTuple)(value)) await this.resolveTuple(key, value, failures, fetchedGuilds);
|
|
304
|
-
else if (typeof value === "string") this.resolveString(key, value, failures);
|
|
305
|
-
else failures.push(` - "${key}" has an invalid value (expected a name or [name, guildId])`);
|
|
306
|
-
if (failures.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigEmojiUnresolved, [failures.length, failures.join("\n")]);
|
|
307
|
-
this.logger.utils.summary("Loaded emojis", { emojis: Object.keys(emojiStorage).length });
|
|
308
|
-
}
|
|
309
|
-
async resolveTuple(key, value, failures, fetchedGuilds) {
|
|
310
|
-
const [emojiName, guildId] = value;
|
|
311
|
-
const guild = this.core.bot.client.guilds.cache.get(guildId);
|
|
312
|
-
if (!guild) {
|
|
313
|
-
failures.push(` - "${emojiName}" for "${key}" targets guild ${guildId}, which is not available (check the Guilds intent)`);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
if (!fetchedGuilds.has(guildId)) {
|
|
317
|
-
await guild.emojis.fetch();
|
|
318
|
-
fetchedGuilds.add(guildId);
|
|
319
|
-
}
|
|
320
|
-
const guildEmoji = guild.emojis.cache.find((e) => e.name === emojiName);
|
|
321
|
-
if (!guildEmoji) {
|
|
322
|
-
failures.push(` - "${emojiName}" for "${key}" was not found in guild ${guildId}`);
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
emojiStorage[key] = guildEmoji;
|
|
326
|
-
}
|
|
327
|
-
resolveString(key, emojiName, failures) {
|
|
328
|
-
const appEmoji = this.core.bot.client.application?.emojis.cache.find((e) => e.name === emojiName);
|
|
329
|
-
if (!appEmoji) {
|
|
330
|
-
failures.push(` - "${emojiName}" for "${key}" was not found among the application emojis`);
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
emojiStorage[key] = appEmoji;
|
|
334
|
-
}
|
|
335
|
-
clearEmojis() {
|
|
336
|
-
(0, _seedcord_core_internal.clearStore)(emojiStorage);
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
//#endregion
|
|
341
|
-
//#region src/bot/notices/utils.ts
|
|
342
|
-
function mentionFor(subject) {
|
|
343
|
-
if (subject instanceof discord_js.Role) return `<@&${subject.id}>`;
|
|
344
|
-
return `<@${subject.id}>`;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
//#endregion
|
|
348
|
-
//#region src/bot/utilities/permissions/checkPermissions.ts
|
|
349
|
-
function checkPermissions(a, b, c, d, e) {
|
|
350
|
-
const { for: pFor, in: pIn, scope, inverse = false, missingNotice, dangerousNotice } = a instanceof discord_js.Role || a instanceof discord_js.GuildMember ? {
|
|
351
|
-
for: a,
|
|
352
|
-
in: b,
|
|
353
|
-
scope: c,
|
|
354
|
-
inverse: d ?? false,
|
|
355
|
-
...e
|
|
356
|
-
} : a;
|
|
357
|
-
const perms = pIn instanceof discord_js.Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
|
|
358
|
-
(0, _seedcord_core.assertPermissions)({
|
|
359
|
-
subject: mentionFor(pFor),
|
|
360
|
-
permissions: perms.bitfield,
|
|
361
|
-
scope,
|
|
362
|
-
inverse,
|
|
363
|
-
...missingNotice && { missingNotice },
|
|
364
|
-
...dangerousNotice && { dangerousNotice }
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
//#endregion
|
|
369
|
-
//#region src/bot/utilities/permissions/checkBotPermissions.ts
|
|
370
|
-
/**
|
|
371
|
-
* Checks the bot's own permissions in a {@link Guild} or {@link TextChannel}. Refuses when a permission in
|
|
372
|
-
* `scope` is missing, or with `inverse` when one is present. When the bot member is uncached, the refusal
|
|
373
|
-
* reports every permission in `scope` as missing.
|
|
374
|
-
*
|
|
375
|
-
* @example
|
|
376
|
-
* ```ts
|
|
377
|
-
* import { PermissionFlagsBits } from 'discord.js';
|
|
378
|
-
* import { checkBotPermissions } from '@seedcord/gateway';
|
|
379
|
-
*
|
|
380
|
-
* checkBotPermissions(textChannel, [PermissionFlagsBits.SendMessages, PermissionFlagsBits.ViewChannel]);
|
|
381
|
-
* ```
|
|
382
|
-
*/
|
|
383
|
-
function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
384
|
-
if (target instanceof discord_js.Guild) {
|
|
385
|
-
const me = target.members.me;
|
|
386
|
-
if (!me) {
|
|
387
|
-
const names = scope.map((bit) => _seedcord_core_internal.PermissionNames.get(bit) ?? String(bit));
|
|
388
|
-
throw new ((errors?.missingNotice) ?? _seedcord_core_internal.MissingPermissions)(void 0, "The bot", names);
|
|
389
|
-
}
|
|
390
|
-
checkPermissions(me, target, scope, inverse, errors);
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
const me = target.guild.members.me;
|
|
394
|
-
if (!me) {
|
|
395
|
-
new _seedcord_logger.Logger("checkBotPermissions", { channel: "errors" }).warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
|
|
396
|
-
const names = scope.map((bit) => _seedcord_core_internal.PermissionNames.get(bit) ?? String(bit));
|
|
397
|
-
throw new ((errors?.missingNotice) ?? _seedcord_core_internal.MissingPermissions)(void 0, "The bot", names);
|
|
398
|
-
}
|
|
399
|
-
checkPermissions(me, target, scope, inverse, errors);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
//#endregion
|
|
403
|
-
//#region src/bot/utilities/permissions/hasPermsToAssign.ts
|
|
404
|
-
function botRoleOf(guild) {
|
|
405
|
-
const botRole = guild.roles.botRoleFor(guild.client.user);
|
|
406
|
-
if (!botRole) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreBotRoleMissing, [guild.id]);
|
|
407
|
-
return botRole;
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Validates that the bot can assign a target role. Refuses when the target role is above the bot's, is managed, or the bot lacks Manage Roles.
|
|
411
|
-
*
|
|
412
|
-
* @param roleOrOptions - Target role or complete options for the check
|
|
413
|
-
* @throws A `Notice` on refusal, or a `SeedcordError` when the bot has no role in the guild's role cache.
|
|
414
|
-
*
|
|
415
|
-
* @example
|
|
416
|
-
* ```ts
|
|
417
|
-
* import { hasPermsToAssign } from '@seedcord/gateway';
|
|
418
|
-
*
|
|
419
|
-
* // Check if the bot can assign a specific role
|
|
420
|
-
* hasPermsToAssign(role);
|
|
421
|
-
*
|
|
422
|
-
* // Check with custom error handling
|
|
423
|
-
* hasPermsToAssign({
|
|
424
|
-
* targetRole: role,
|
|
425
|
-
* noticeOverrides: {
|
|
426
|
-
* higherNotice: CustomHigherNotice,
|
|
427
|
-
* managedNotice: CustomManagedNotice,
|
|
428
|
-
* missingNotice: CustomMissingPermissionsNotice
|
|
429
|
-
* }
|
|
430
|
-
* });
|
|
431
|
-
* ```
|
|
432
|
-
*/
|
|
433
|
-
function hasPermsToAssign(roleOrOptions) {
|
|
434
|
-
const { targetRole, noticeOverrides: errors } = roleOrOptions instanceof discord_js.Role ? {
|
|
435
|
-
targetRole: roleOrOptions,
|
|
436
|
-
noticeOverrides: void 0
|
|
437
|
-
} : roleOrOptions;
|
|
438
|
-
const HigherNotice = errors?.higherNotice ?? RoleHigherThanMe;
|
|
439
|
-
const ManagedNotice = errors?.managedNotice ?? CannotAssignBotRole;
|
|
440
|
-
const MissingNotice = errors?.missingNotice ?? _seedcord_core_internal.MissingPermissions;
|
|
441
|
-
const botRole = botRoleOf(targetRole.guild);
|
|
442
|
-
if (targetRole.comparePositionTo(botRole) >= 0) throw new HigherNotice("Role is higher than me", targetRole, botRole);
|
|
443
|
-
if (targetRole.managed) throw new ManagedNotice(`Cannot assign managed role ${targetRole.name}`);
|
|
444
|
-
checkBotPermissions(targetRole.guild, [discord_js.PermissionFlagsBits.ManageRoles], false, { missingNotice: MissingNotice });
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
//#endregion
|
|
448
|
-
//#region src/bot/confirm/reserved.ts
|
|
449
|
-
const RESERVED_CONFIRM_PREFIX = "__seedcord_confirm";
|
|
450
|
-
const CONFIRM_DEF = new _seedcord_core.CustomId(RESERVED_CONFIRM_PREFIX).oneOf("choice", ["confirm", "cancel"]);
|
|
451
|
-
|
|
452
|
-
//#endregion
|
|
453
|
-
//#region src/bot/confirm/getConfirmation.ts
|
|
454
|
-
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
455
|
-
const CONFIRM_IDS = {
|
|
456
|
-
confirm: CONFIRM_DEF.encode({ choice: "confirm" }),
|
|
457
|
-
cancel: CONFIRM_DEF.encode({ choice: "cancel" })
|
|
458
|
-
};
|
|
459
|
-
var ConfirmButtons = class extends _seedcord_core.RowComponent {
|
|
460
|
-
constructor(options) {
|
|
461
|
-
super("button");
|
|
462
|
-
this.instance.addComponents(new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.confirm).setLabel(options?.confirmLabel ?? "Confirm").setStyle(options?.confirmStyle ?? discord_js.ButtonStyle.Danger), new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.cancel).setLabel(options?.cancelLabel ?? "Cancel").setStyle(discord_js.ButtonStyle.Secondary));
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
var ConfirmPromptCard = class extends _seedcord_core.BuilderComponent {
|
|
466
|
-
constructor(text, options) {
|
|
467
|
-
super("container");
|
|
468
|
-
this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(text)).addActionRowComponents(new ConfirmButtons(options).component);
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
function defaultPrompt(text, options) {
|
|
472
|
-
return { components: [new ConfirmPromptCard(text, options).component] };
|
|
473
|
-
}
|
|
474
|
-
async function collectChoice(message, userId, timeoutMs) {
|
|
475
|
-
try {
|
|
476
|
-
return await message.awaitMessageComponent({
|
|
477
|
-
componentType: discord_js.ComponentType.Button,
|
|
478
|
-
time: timeoutMs,
|
|
479
|
-
filter: (click) => click.user.id === userId && CONFIRM_DEF.owns(click.customId)
|
|
480
|
-
});
|
|
481
|
-
} catch {
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
async function settle(sender, message, outcome) {
|
|
486
|
-
if (outcome === void 0) {
|
|
487
|
-
await sender.delete(message).catch(() => void 0);
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
|
|
491
|
-
}
|
|
492
|
-
async function getConfirmation(handler, prompt, options) {
|
|
493
|
-
const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
|
|
494
|
-
const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
|
|
495
|
-
const interaction = handler.getEvent();
|
|
496
|
-
const sender = handler.getSender();
|
|
497
|
-
const message = await sender.send(response, { ephemeral });
|
|
498
|
-
const winner = await collectChoice(message, interaction.user.id, timeoutMs);
|
|
499
|
-
if (!winner) {
|
|
500
|
-
await settle(sender, message, options?.onTimeout);
|
|
501
|
-
return false;
|
|
502
|
-
}
|
|
503
|
-
await winner.deferUpdate().catch(() => void 0);
|
|
504
|
-
const confirmed = winner.customId === CONFIRM_IDS.confirm;
|
|
505
|
-
await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
|
|
506
|
-
return confirmed;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
//#endregion
|
|
510
|
-
//#region src/handlers/BaseHandler.ts
|
|
511
|
-
/**
|
|
512
|
-
* The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
|
|
513
|
-
* gate hook.
|
|
514
|
-
*/
|
|
515
|
-
var BaseHandler = class extends _seedcord_core.BaseHandler {
|
|
516
|
-
getEvent() {
|
|
517
|
-
return this.event;
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
//#endregion
|
|
522
|
-
//#region src/handlers/interaction/InteractionMiddleware.ts
|
|
523
|
-
/**
|
|
524
|
-
* Base class for interaction middleware
|
|
525
|
-
*
|
|
526
|
-
* Middleware runs before interaction handlers and can modify behavior or block execution.
|
|
527
|
-
* Unlike handlers, middleware should not send responses directly.
|
|
528
|
-
*
|
|
529
|
-
* @typeParam Repliable - The interaction type this middleware processes
|
|
530
|
-
*/
|
|
531
|
-
var InteractionMiddleware = class extends BaseHandler {
|
|
532
|
-
constructor(event, core) {
|
|
533
|
-
super(event, core, void 0, "interactions");
|
|
534
|
-
}
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
//#endregion
|
|
538
|
-
//#region src/bot/ReplySender.ts
|
|
539
|
-
function isReplyFile(file) {
|
|
540
|
-
return typeof file === "object" && "data" in file && ArrayBuffer.isView(file.data);
|
|
541
|
-
}
|
|
542
|
-
function toDjsFile(file) {
|
|
543
|
-
if (!isReplyFile(file)) return file;
|
|
544
|
-
return {
|
|
545
|
-
attachment: Buffer.from(file.data),
|
|
546
|
-
name: file.name,
|
|
547
|
-
...file.description !== void 0 && { description: file.description },
|
|
548
|
-
...file.title !== void 0 && { title: file.title }
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
function seedState(interaction) {
|
|
552
|
-
if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
|
|
553
|
-
if (interaction.replied) return "replied";
|
|
554
|
-
if (interaction.deferred) return "deferred-reply";
|
|
555
|
-
return "unacked";
|
|
556
|
-
}
|
|
557
|
-
/** Writes interaction responses through discord.js. The repliable handler bases and the dispatcher construct it. */
|
|
558
|
-
var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
559
|
-
interaction;
|
|
560
|
-
constructor(interaction, routeId, bus) {
|
|
561
|
-
super(routeId, {
|
|
562
|
-
bus,
|
|
563
|
-
interactionId: interaction.id
|
|
564
|
-
}, seedState(interaction));
|
|
565
|
-
this.interaction = interaction;
|
|
566
|
-
}
|
|
567
|
-
async writeReply(response, opts) {
|
|
568
|
-
const result = await this.interaction.reply({
|
|
569
|
-
...this.replyOptions(response, (0, _seedcord_core_internal.sendFlags)(opts)),
|
|
570
|
-
withResponse: true
|
|
571
|
-
});
|
|
572
|
-
return this.createdMessage(result);
|
|
573
|
-
}
|
|
574
|
-
async writeDefer(opts) {
|
|
575
|
-
await this.interaction.deferReply({ flags: (0, _seedcord_core_internal.deferFlags)(opts) });
|
|
576
|
-
}
|
|
577
|
-
async writeDeferUpdate() {
|
|
578
|
-
await this.sourceInteraction("deferUpdate").deferUpdate();
|
|
579
|
-
}
|
|
580
|
-
async writeUpdate(response) {
|
|
581
|
-
const result = await this.sourceInteraction("update").update({
|
|
582
|
-
...this.updateOptions(response),
|
|
583
|
-
withResponse: true
|
|
584
|
-
});
|
|
585
|
-
return this.createdMessage(result);
|
|
586
|
-
}
|
|
587
|
-
async writeFollowUp(response, opts) {
|
|
588
|
-
return await this.interaction.followUp(this.replyOptions(response, (0, _seedcord_core_internal.sendFlags)(opts)));
|
|
589
|
-
}
|
|
590
|
-
async writeEditOriginal(response) {
|
|
591
|
-
return await this.interaction.editReply(this.editOptions(response));
|
|
592
|
-
}
|
|
593
|
-
async writeEditTarget(targetId, response) {
|
|
594
|
-
return await this.interaction.webhook.editMessage(targetId, this.editOptions(response));
|
|
595
|
-
}
|
|
596
|
-
async writeDeleteOriginal() {
|
|
597
|
-
await this.interaction.deleteReply();
|
|
598
|
-
}
|
|
599
|
-
async writeDeleteTarget(targetId) {
|
|
600
|
-
await this.interaction.deleteReply(targetId);
|
|
601
|
-
}
|
|
602
|
-
async writeModal(data) {
|
|
603
|
-
const { interaction } = this;
|
|
604
|
-
if (interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
605
|
-
await interaction.showModal(data);
|
|
606
|
-
}
|
|
607
|
-
guardModalSource() {
|
|
608
|
-
if (this.interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
609
|
-
}
|
|
610
|
-
rejectModalSubmit() {
|
|
611
|
-
throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyIllegalAckState, [
|
|
612
|
-
"showModal",
|
|
613
|
-
"this interaction is a modal submit",
|
|
614
|
-
"A modal cannot open another modal.",
|
|
615
|
-
this.routeId
|
|
616
|
-
]);
|
|
617
|
-
}
|
|
618
|
-
sourceInteraction(method) {
|
|
619
|
-
const { interaction } = this;
|
|
620
|
-
if (interaction.isMessageComponent()) return interaction;
|
|
621
|
-
if (interaction.isModalSubmit() && interaction.isFromMessage()) return interaction;
|
|
622
|
-
throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
623
|
-
}
|
|
624
|
-
replyOptions(response, flags) {
|
|
625
|
-
const reply = this.serialize(response);
|
|
626
|
-
return {
|
|
627
|
-
components: reply.components,
|
|
628
|
-
flags,
|
|
629
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
630
|
-
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
editOptions(response) {
|
|
634
|
-
const reply = this.serialize(response);
|
|
635
|
-
return {
|
|
636
|
-
components: reply.components,
|
|
637
|
-
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
638
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
639
|
-
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
updateOptions(response) {
|
|
643
|
-
const reply = this.serialize(response);
|
|
644
|
-
return {
|
|
645
|
-
components: reply.components,
|
|
646
|
-
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
647
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
648
|
-
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
createdMessage(result) {
|
|
652
|
-
return result.resource?.message ?? void 0;
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
//#endregion
|
|
657
|
-
//#region src/handlers/RepliableHandler.ts
|
|
658
|
-
/**
|
|
659
|
-
* Shared base the repliable interaction handlers extend.
|
|
660
|
-
*
|
|
661
|
-
* Not a public entry point. Extend {@link SlashHandler}, {@link ButtonHandler}, {@link ModalHandler},
|
|
662
|
-
* {@link SelectMenuHandler}, or {@link ContextMenuHandler} instead. This class binds the core reply base
|
|
663
|
-
* to the gateway sender.
|
|
664
|
-
*
|
|
665
|
-
* @typeParam Event - The repliable interaction type this handler processes
|
|
666
|
-
*/
|
|
667
|
-
var RepliableHandler = class extends _seedcord_core.RepliableHandler {
|
|
668
|
-
/** @internal */
|
|
669
|
-
getEvent() {
|
|
670
|
-
return this.event;
|
|
671
|
-
}
|
|
672
|
-
buildSender(event, core, routeId) {
|
|
673
|
-
return new ReplySender(event, routeId, core.bus);
|
|
674
|
-
}
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
//#endregion
|
|
678
|
-
//#region src/handlers/interaction/InteractionHandler.ts
|
|
679
|
-
/**
|
|
680
|
-
* Shared base the typed interaction handlers extend.
|
|
681
|
-
*
|
|
682
|
-
* Not a public entry point. Extend {@link SlashHandler}, {@link ButtonHandler}, {@link ModalHandler},
|
|
683
|
-
* or {@link SelectMenuHandler} instead. This class adds `showModal` on top of the reply members those bases
|
|
684
|
-
* share.
|
|
685
|
-
*
|
|
686
|
-
* @typeParam Repliable - The interaction type this handler processes
|
|
687
|
-
*/
|
|
688
|
-
var InteractionHandler = class extends RepliableHandler {
|
|
689
|
-
constructor(event, core, dispatch) {
|
|
690
|
-
super(event, core, dispatch);
|
|
691
|
-
}
|
|
692
|
-
/**
|
|
693
|
-
* Open a modal. Must be the initial response to this interaction. The modal kind rejects this call at
|
|
694
|
-
* compile time (Discord forbids a modal in response to a modal).
|
|
695
|
-
*/
|
|
696
|
-
showModal(modal) {
|
|
697
|
-
return this.sender.showModal(modal);
|
|
698
|
-
}
|
|
699
|
-
};
|
|
700
|
-
|
|
701
|
-
//#endregion
|
|
702
|
-
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
703
|
-
function slashRouteOf(interaction) {
|
|
704
|
-
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
705
|
-
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
706
|
-
return (0, _seedcord_utils_internal.buildSlashRoute)(interaction.commandName, subcommand, group);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
//#endregion
|
|
710
|
-
//#region src/handlers/interaction/SlashHandler.ts
|
|
711
|
-
/**
|
|
712
|
-
* Base class for a chat-input (slash) command handler.
|
|
713
|
-
*
|
|
714
|
-
* Pass the route from the generated registry as the generic, the same string as `@SlashRoute`, then read
|
|
715
|
-
* `this.options` for a single command or `this.match` for several. Command authoring stays plain
|
|
716
|
-
* discord.js, `seedcord codegen` reads its `toJSON()` to type these options.
|
|
717
|
-
*
|
|
718
|
-
* @typeParam Route - One or more route keys from {@link SlashOptionRegistry}, e.g. `'ban'` or `'ban' | 'kick'`.
|
|
719
|
-
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
720
|
-
*
|
|
721
|
-
* @example
|
|
722
|
-
* ```ts
|
|
723
|
-
* \@SlashRoute('ban')
|
|
724
|
-
* class BanHandler extends SlashHandler<'ban'> {
|
|
725
|
-
* async execute() {
|
|
726
|
-
* const target = this.options.getUser('target'); // User
|
|
727
|
-
* const reason = this.options.getString('reason'); // string | null
|
|
728
|
-
* }
|
|
729
|
-
* }
|
|
730
|
-
* ```
|
|
731
|
-
*/
|
|
732
|
-
var SlashHandler = class extends InteractionHandler {
|
|
733
|
-
/**
|
|
734
|
-
* The typed options for this command's route. Required options drop the null, choices narrow to their
|
|
735
|
-
* literal union, and only the getters for kinds this command actually uses appear. Use `this.event.options`
|
|
736
|
-
* directly for anything outside this view, such as narrowing a channel option by type.
|
|
737
|
-
*/
|
|
738
|
-
get options() {
|
|
739
|
-
return this.event.options;
|
|
740
|
-
}
|
|
741
|
-
/**
|
|
742
|
-
* Run the arm for whichever command fired. Use this only when the handler is registered for several
|
|
743
|
-
* routes.
|
|
744
|
-
*
|
|
745
|
-
* Provide one arm per registered route, keyed by its route string, and each arm receives that route's
|
|
746
|
-
* own narrowed options. The arms must cover every route in the generic, a missing route or an unknown
|
|
747
|
-
* key is a compile error.
|
|
748
|
-
*
|
|
749
|
-
* @param arms - One handler per registered route, keyed by route string.
|
|
750
|
-
* @returns The result of the arm that ran.
|
|
751
|
-
*
|
|
752
|
-
* @example
|
|
753
|
-
* ```ts
|
|
754
|
-
* \@SlashRoute('ban', 'kick')
|
|
755
|
-
* class ModHandler extends SlashHandler<'ban' | 'kick'> {
|
|
756
|
-
* async execute() {
|
|
757
|
-
* await this.match({
|
|
758
|
-
* ban: (options) => this.event.guild?.members.ban(options.getUser('target', true).id),
|
|
759
|
-
* kick: (options) => this.event.guild?.members.kick(options.getUser('member', true).id)
|
|
760
|
-
* });
|
|
761
|
-
* }
|
|
762
|
-
* }
|
|
763
|
-
* ```
|
|
764
|
-
*/
|
|
765
|
-
async match(arms) {
|
|
766
|
-
const route = slashRouteOf(this.event);
|
|
767
|
-
const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
|
|
768
|
-
if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.SlashMatchArmMissing, [route]);
|
|
769
|
-
return await arm(this.options);
|
|
770
|
-
}
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
//#endregion
|
|
774
|
-
//#region src/handlers/interaction/ContextMenuHandler.ts
|
|
775
|
-
/**
|
|
776
|
-
* Base class for a context-menu command handler (right-click a user or a message).
|
|
777
|
-
*
|
|
778
|
-
* Pass the kind from `discord.js`'s {@link ApplicationCommandType} as the generic, the same value as
|
|
779
|
-
* `@ContextMenuRoute`. Read the right-clicked entity from `this.target`, a `User` for a user menu and a
|
|
780
|
-
* `Message` for a message menu. Context menus carry no options, so a handler registered for several names
|
|
781
|
-
* reads `this.target` uniformly with no per-name branch.
|
|
782
|
-
*
|
|
783
|
-
* @typeParam Kind - `ApplicationCommandType.User` or `ApplicationCommandType.Message`.
|
|
784
|
-
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
785
|
-
*
|
|
786
|
-
* @example
|
|
787
|
-
* ```ts
|
|
788
|
-
* \@ContextMenuRoute(ApplicationCommandType.Message, 'Report Message')
|
|
789
|
-
* class ReportMessage extends ContextMenuHandler<ApplicationCommandType.Message> {
|
|
790
|
-
* async execute() {
|
|
791
|
-
* const message = this.target;
|
|
792
|
-
* }
|
|
793
|
-
* }
|
|
794
|
-
* ```
|
|
795
|
-
*/
|
|
796
|
-
var ContextMenuHandler = class extends InteractionHandler {
|
|
797
|
-
get target() {
|
|
798
|
-
const event = this.event;
|
|
799
|
-
return "targetUser" in event ? event.targetUser : event.targetMessage;
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* The invoking guild member, resolved only on user menus. Reading it on a message menu is a compile
|
|
803
|
-
* error since the type is `never` there.
|
|
804
|
-
*/
|
|
805
|
-
get targetMember() {
|
|
806
|
-
return this.event.targetMember;
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
|
|
810
|
-
//#endregion
|
|
811
|
-
//#region src/handlers/interaction/AutocompleteHandler.ts
|
|
812
|
-
/**
|
|
813
|
-
* Base class for a Discord autocomplete handler.
|
|
814
|
-
*
|
|
815
|
-
* Pass the command route(s) from the generated registry as the generic, the same string(s) as
|
|
816
|
-
* `@AutocompleteRoute`. Branch on the focused field with `this.match`, read already-entered sibling options
|
|
817
|
-
* with `this.options`, and find which command fired with `this.route`.
|
|
818
|
-
*
|
|
819
|
-
* @typeParam Route - One or more route keys from {@link SlashOptionRegistry}, e.g. `'search'` or `'search' | 'find'`.
|
|
820
|
-
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
821
|
-
*
|
|
822
|
-
* @example
|
|
823
|
-
* ```ts
|
|
824
|
-
* \@AutocompleteRoute('search')
|
|
825
|
-
* class SearchAutocomplete extends AutocompleteHandler<'search'> {
|
|
826
|
-
* async execute() {
|
|
827
|
-
* await this.match({
|
|
828
|
-
* query: (value, respond) => respond([{ name: value, value }])
|
|
829
|
-
* });
|
|
830
|
-
* }
|
|
831
|
-
* }
|
|
832
|
-
* ```
|
|
833
|
-
*/
|
|
834
|
-
var AutocompleteHandler = class extends BaseHandler {
|
|
835
|
-
constructor(event, core, dispatch) {
|
|
836
|
-
super(event, core, dispatch, "interactions");
|
|
837
|
-
}
|
|
838
|
-
decodedFocused;
|
|
839
|
-
/**
|
|
840
|
-
* The focused option for this interaction. `value` is always the raw partial string the user is typing,
|
|
841
|
-
* even for an integer or number option, so coerce it yourself when you need a number.
|
|
842
|
-
*/
|
|
843
|
-
get focused() {
|
|
844
|
-
if (this.decodedFocused) return this.decodedFocused;
|
|
845
|
-
const raw = this.event.options.getFocused(true);
|
|
846
|
-
const focused = {
|
|
847
|
-
name: raw.name,
|
|
848
|
-
value: raw.value
|
|
849
|
-
};
|
|
850
|
-
this.decodedFocused = focused;
|
|
851
|
-
return focused;
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* Run the arm for the focused field. Because an autocomplete always dispatches by which field is
|
|
855
|
-
* focused, match is how you read it. There is no single-field shortcut the way slash/component handlers have one.
|
|
856
|
-
*
|
|
857
|
-
* Provide one arm per autocompletable field across the registered commands, keyed by field name. Each
|
|
858
|
-
* arm receives the focused partial value and a `respond` typed to that field's choice value. The
|
|
859
|
-
* arms must cover every autocompletable field, a missing field or an unknown key is a compile error. A
|
|
860
|
-
* focused field with no arm, only reachable from a stale-deployed command, throws at runtime.
|
|
861
|
-
*
|
|
862
|
-
* @param arms - One handler per autocompletable field, keyed by field name.
|
|
863
|
-
* @returns The result of the arm that ran.
|
|
864
|
-
*
|
|
865
|
-
* @example
|
|
866
|
-
* ```ts
|
|
867
|
-
* \@AutocompleteRoute('search')
|
|
868
|
-
* class SearchAutocomplete extends AutocompleteHandler<'search'> {
|
|
869
|
-
* async execute() {
|
|
870
|
-
* await this.match({
|
|
871
|
-
* query: (value, respond) => respond(titles(value).map((s) => ({ name: s, value: s }))),
|
|
872
|
-
* tag: (value, respond) => respond(tags(value).map((s) => ({ name: s, value: s })))
|
|
873
|
-
* });
|
|
874
|
-
* }
|
|
875
|
-
* }
|
|
876
|
-
* ```
|
|
877
|
-
*/
|
|
878
|
-
async match(arms) {
|
|
879
|
-
const { name, value } = this.focused;
|
|
880
|
-
const respond = (choices) => this.respond(choices);
|
|
881
|
-
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
882
|
-
if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.AutocompleteMatchArmMissing, [name]);
|
|
883
|
-
return await arm(value, respond);
|
|
884
|
-
}
|
|
885
|
-
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
886
|
-
async respond(choices) {
|
|
887
|
-
const routeId = this.dispatch?.routeId ?? "autocomplete";
|
|
888
|
-
await (0, _seedcord_core_internal.reportedWrite)({
|
|
889
|
-
bus: this.core.bus,
|
|
890
|
-
interactionId: this.event.id
|
|
891
|
-
}, routeId, "respond", () => this.event.respond(choices));
|
|
892
|
-
}
|
|
893
|
-
/** The firing command route, for a field whose completion differs per registered command. */
|
|
894
|
-
get route() {
|
|
895
|
-
return slashRouteOf(this.event);
|
|
896
|
-
}
|
|
897
|
-
/**
|
|
898
|
-
* The already-entered options on this command, restricted to the kinds Discord resolves during autocomplete
|
|
899
|
-
* (string, integer, number, boolean) with every read nullable, since a sibling is partial while the user
|
|
900
|
-
* types the focused field. The focused field is on `this.focused`.
|
|
901
|
-
*/
|
|
902
|
-
get options() {
|
|
903
|
-
return this.event.options;
|
|
904
|
-
}
|
|
905
|
-
};
|
|
906
|
-
|
|
907
|
-
//#endregion
|
|
908
|
-
//#region src/handlers/interaction/components/ComponentHandler.ts
|
|
909
|
-
/**
|
|
910
|
-
* Shared base the customId-routed component handlers extend.
|
|
911
|
-
*
|
|
912
|
-
* Not a public entry point. Extend {@link ButtonHandler}, {@link SelectMenuHandler}, or {@link ModalHandler}
|
|
913
|
-
* instead. This class defines the customId decode and the route matching those bases share.
|
|
914
|
-
*
|
|
915
|
-
* @typeParam Event - The component interaction type this handler processes
|
|
916
|
-
* @typeParam Defs - The customId route definitions registered on the concrete handler
|
|
917
|
-
*/
|
|
918
|
-
var ComponentHandler = class extends InteractionHandler {
|
|
919
|
-
/** Rewrite the source message this component interaction came from. */
|
|
920
|
-
update(response) {
|
|
921
|
-
return this.sender.update(response);
|
|
922
|
-
}
|
|
923
|
-
/** Acknowledge the component without changing the source message. */
|
|
924
|
-
deferUpdate() {
|
|
925
|
-
return this.sender.deferUpdate();
|
|
926
|
-
}
|
|
927
|
-
decoded;
|
|
928
|
-
get route() {
|
|
929
|
-
this.decoded ??= (0, _seedcord_core_internal.decodeComponentRoute)(this.constructor, this.event.customId);
|
|
930
|
-
return this.decoded;
|
|
931
|
-
}
|
|
932
|
-
/**
|
|
933
|
-
* The decoded params of the single route this handler is registered for.
|
|
934
|
-
*
|
|
935
|
-
* Reading this decodes `this.event.customId` once (cached after the first read) and throws
|
|
936
|
-
* `StaleCustomId` or `InvalidCustomId` when the wire no longer matches the current shape, which the
|
|
937
|
-
* controller boundary turns into a reply. On a handler registered for several routes this is
|
|
938
|
-
* `never`. Use {@link match} instead.
|
|
939
|
-
*/
|
|
940
|
-
get params() {
|
|
941
|
-
return this.route.params;
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* Run the arm for whichever route the component was minted from. Use this only when the handler is
|
|
945
|
-
* registered for several routes. A single-route handler reads `this.params` directly. On a multi-route
|
|
946
|
-
* handler `this.params` is `never`, leaving match as the only way to read the decoded params.
|
|
947
|
-
*
|
|
948
|
-
* Provide one arm per registered route, keyed by its prefix, and each arm receives that route's own
|
|
949
|
-
* decoded params. The arms cover every registered route prefix, checked at compile time, and a prefix
|
|
950
|
-
* unmatched at runtime throws `CustomIdMatchArmMissing`. Decoding runs before any arm, so a stale or
|
|
951
|
-
* corrupt wire throws before an arm body executes.
|
|
952
|
-
*
|
|
953
|
-
* @param arms - One callback per registered route, keyed by prefix.
|
|
954
|
-
* @returns The result of the arm that ran.
|
|
955
|
-
*
|
|
956
|
-
* @example
|
|
957
|
-
* ```ts
|
|
958
|
-
* \@ButtonRoute(Approve, Reject)
|
|
959
|
-
* class ReviewButtons extends ButtonHandler<[typeof Approve, typeof Reject]> {
|
|
960
|
-
* async execute() {
|
|
961
|
-
* await this.match({
|
|
962
|
-
* approve: ({ userId }) => this.reply(`approved <@${userId}>`),
|
|
963
|
-
* reject: ({ userId }) => this.reply(`rejected <@${userId}>`)
|
|
964
|
-
* });
|
|
965
|
-
* }
|
|
966
|
-
* }
|
|
967
|
-
* ```
|
|
968
|
-
*/
|
|
969
|
-
async match(arms) {
|
|
970
|
-
const { prefix, params } = this.route;
|
|
971
|
-
const arm = Object.hasOwn(arms, prefix) ? arms[prefix] : void 0;
|
|
972
|
-
if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CustomIdMatchArmMissing, [prefix]);
|
|
973
|
-
return await arm(params);
|
|
974
|
-
}
|
|
975
|
-
};
|
|
976
|
-
|
|
977
|
-
//#endregion
|
|
978
|
-
//#region src/handlers/interaction/components/ButtonHandler.ts
|
|
979
|
-
/**
|
|
980
|
-
* Base class for a button interaction handler.
|
|
981
|
-
*
|
|
982
|
-
* Register the customId definitions this handler decodes with `@ButtonRoute`, list the same ones in the
|
|
983
|
-
* generic, then read `this.params` for a single route or `this.match` for several. Reply through the handler
|
|
984
|
-
* members, or rewrite the source message with `this.update`. Passing different definitions to the decorator
|
|
985
|
-
* and the generic is a compile error.
|
|
986
|
-
*
|
|
987
|
-
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ApproveId]`.
|
|
988
|
-
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
989
|
-
*
|
|
990
|
-
* @example
|
|
991
|
-
* ```ts
|
|
992
|
-
* \@ButtonRoute(ApproveId)
|
|
993
|
-
* class ApproveButton extends ButtonHandler<[typeof ApproveId]> {
|
|
994
|
-
* async execute() {
|
|
995
|
-
* const { userId } = this.params;
|
|
996
|
-
* await this.reply(`approved <@${userId}>`);
|
|
997
|
-
* }
|
|
998
|
-
* }
|
|
999
|
-
* ```
|
|
1000
|
-
*/
|
|
1001
|
-
var ButtonHandler = class extends ComponentHandler {};
|
|
1002
|
-
|
|
1003
|
-
//#endregion
|
|
1004
|
-
//#region src/handlers/interaction/components/ModalHandler.ts
|
|
1005
|
-
/**
|
|
1006
|
-
* Base class for a modal submit handler.
|
|
1007
|
-
*
|
|
1008
|
-
* Register the customId definitions this handler decodes with `@ModalRoute`, list the same ones in the
|
|
1009
|
-
* generic, then read `this.params` for a single route or `this.match` for several. Read the submitted
|
|
1010
|
-
* inputs from `this.event.fields`, and reply through the handler members. Discord forbids opening a modal in
|
|
1011
|
-
* response to a modal, so `showModal` fails compilation on this kind.
|
|
1012
|
-
*
|
|
1013
|
-
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ConfigId]`.
|
|
1014
|
-
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
1015
|
-
*
|
|
1016
|
-
* @example
|
|
1017
|
-
* ```ts
|
|
1018
|
-
* \@ModalRoute(ConfigId)
|
|
1019
|
-
* class ConfigModal extends ModalHandler<[typeof ConfigId]> {
|
|
1020
|
-
* async execute() {
|
|
1021
|
-
* const { guildId } = this.params;
|
|
1022
|
-
* const name = this.event.fields.getTextInputValue('name');
|
|
1023
|
-
* await this.reply(`saved ${name} for ${guildId}`);
|
|
1024
|
-
* }
|
|
1025
|
-
* }
|
|
1026
|
-
* ```
|
|
1027
|
-
*/
|
|
1028
|
-
var ModalHandler = class extends ComponentHandler {
|
|
1029
|
-
/** Rewrite the message this modal was opened from. Throws when a command opened the modal. */
|
|
1030
|
-
update(response) {
|
|
1031
|
-
this.ensureSourceMessage("update");
|
|
1032
|
-
return super.update(response);
|
|
1033
|
-
}
|
|
1034
|
-
/** Acknowledge the submit without changing the source message. Throws when a command opened the modal. */
|
|
1035
|
-
deferUpdate() {
|
|
1036
|
-
this.ensureSourceMessage("deferUpdate");
|
|
1037
|
-
return super.deferUpdate();
|
|
1038
|
-
}
|
|
1039
|
-
ensureSourceMessage(method) {
|
|
1040
|
-
if (this.event.isFromMessage()) return;
|
|
1041
|
-
throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
1042
|
-
}
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
|
-
//#endregion
|
|
1046
|
-
//#region src/handlers/interaction/components/SelectMenuHandler.ts
|
|
1047
|
-
/**
|
|
1048
|
-
* Base class for a select menu handler.
|
|
1049
|
-
*
|
|
1050
|
-
* Pass the select kind first and the customId definitions second, the same order as `@SelectMenuRoute`,
|
|
1051
|
-
* so `this.event` and `this.event.values` are narrowed to that kind. Read `this.params` for a single
|
|
1052
|
-
* route or `this.match` for several, and reply through the handler members or rewrite the source message
|
|
1053
|
-
* with `this.update`.
|
|
1054
|
-
*
|
|
1055
|
-
* @typeParam Kind - The select kind from {@link SelectMenuKind}, e.g. `SelectMenuKind.User`.
|
|
1056
|
-
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof AssignId]`.
|
|
1057
|
-
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
1058
|
-
*
|
|
1059
|
-
* @example
|
|
1060
|
-
* ```ts
|
|
1061
|
-
* \@SelectMenuRoute(SelectMenuKind.User, AssignId)
|
|
1062
|
-
* class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {
|
|
1063
|
-
* async execute() {
|
|
1064
|
-
* const { roleId } = this.params;
|
|
1065
|
-
* await this.reply(`assigning ${this.event.values.length} member(s) to <@&${roleId}>`);
|
|
1066
|
-
* }
|
|
1067
|
-
* }
|
|
1068
|
-
* ```
|
|
1069
|
-
*/
|
|
1070
|
-
var SelectMenuHandler = class extends ComponentHandler {};
|
|
1071
|
-
|
|
1072
|
-
//#endregion
|
|
1073
|
-
//#region src/handlers/event/EventHandler.ts
|
|
1074
|
-
/**
|
|
1075
|
-
* Base class for a Discord client event handler.
|
|
1076
|
-
*
|
|
1077
|
-
* Pass the event name(s) as the generic, the same one(s) as `@RegisterEvent`. A single-event handler reads
|
|
1078
|
-
* `this.event` (the payload tuple) directly. A handler registered for several events branches with `this.match`,
|
|
1079
|
-
* keyed by event name, since the union of payload tuples is not directly readable.
|
|
1080
|
-
*
|
|
1081
|
-
* @typeParam Names - One or more `ClientEvents` keys, e.g. `Events.MessageCreate` or `Events.MessageCreate | Events.MessageUpdate`.
|
|
1082
|
-
*
|
|
1083
|
-
* @example
|
|
1084
|
-
* ```ts
|
|
1085
|
-
* \@RegisterEvent([Events.MessageCreate], [Events.MessageUpdate])
|
|
1086
|
-
* class PingPong extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
|
|
1087
|
-
* async execute() {
|
|
1088
|
-
* await this.match({
|
|
1089
|
-
* [Events.MessageCreate]: (message) => message.reply('pong'),
|
|
1090
|
-
* [Events.MessageUpdate]: (_oldMessage, edited) => edited.reply('pong')
|
|
1091
|
-
* });
|
|
1092
|
-
* }
|
|
1093
|
-
* }
|
|
1094
|
-
* ```
|
|
1095
|
-
*/
|
|
1096
|
-
var EventHandler = class extends BaseHandler {
|
|
1097
|
-
firedEvent;
|
|
1098
|
-
constructor(event, core, eventName) {
|
|
1099
|
-
super(event, core, void 0, "events");
|
|
1100
|
-
this.firedEvent = eventName;
|
|
1101
|
-
}
|
|
1102
|
-
/**
|
|
1103
|
-
* Run the arm for whichever event fired. Use this only when the handler is registered for several
|
|
1104
|
-
* events. A single-event handler reads `this.event` directly. On a multi-event handler `this.event` is
|
|
1105
|
-
* `never`, so match is the only way to read the payload.
|
|
1106
|
-
*
|
|
1107
|
-
* Provide one arm per registered event, keyed by its name, and each arm receives that event's own
|
|
1108
|
-
* payload tuple. The arms must cover every event in the generic, a missing event or an unknown key is a
|
|
1109
|
-
* compile error.
|
|
1110
|
-
*
|
|
1111
|
-
* @param arms - One handler per registered event, keyed by event name.
|
|
1112
|
-
* @returns The result of the arm that ran.
|
|
1113
|
-
*
|
|
1114
|
-
* @example
|
|
1115
|
-
* ```ts
|
|
1116
|
-
* \@RegisterEvent([Events.MessageCreate], [Events.MessageUpdate])
|
|
1117
|
-
* class MessageWatcher extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
|
|
1118
|
-
* async execute() {
|
|
1119
|
-
* await this.match({
|
|
1120
|
-
* [Events.MessageCreate]: (message) => this.scan(message),
|
|
1121
|
-
* [Events.MessageUpdate]: (_old, updated) => this.scan(updated)
|
|
1122
|
-
* });
|
|
1123
|
-
* }
|
|
1124
|
-
* }
|
|
1125
|
-
* ```
|
|
1126
|
-
*/
|
|
1127
|
-
async match(arms) {
|
|
1128
|
-
const name = this.firedEvent;
|
|
1129
|
-
if (name === void 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.EventMatchArmMissing, ["<no event>"]);
|
|
1130
|
-
const arm = arms[name];
|
|
1131
|
-
if (typeof arm !== "function") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.EventMatchArmMissing, [name]);
|
|
1132
|
-
return await arm(...this.getEvent());
|
|
1133
|
-
}
|
|
1134
|
-
};
|
|
1135
|
-
|
|
1136
|
-
//#endregion
|
|
1137
|
-
//#region src/handlers/event/EventMiddleware.ts
|
|
1138
|
-
/**
|
|
1139
|
-
* Base class for Discord event middleware.
|
|
1140
|
-
*
|
|
1141
|
-
* Middleware runs before event handlers and can stop the event by throwing a `Silence`. Unlike `EventHandler`, it
|
|
1142
|
-
* runs the SAME for every event it is registered for, so it has no `match`. Specify a single event in the
|
|
1143
|
-
* generic and `{ events }` to read `this.event` fully typed. Span several events (or omit `{ events }` for a
|
|
1144
|
-
* catchall) and `this.event` narrows to `never`, read `this.eventName` and do work that does not depend on the
|
|
1145
|
-
* payload shape. A middleware that needs each event's payload is written one event per class.
|
|
1146
|
-
*
|
|
1147
|
-
* @typeParam EventName - One or more `ClientEvents` keys. Defaults to every event (catchall).
|
|
1148
|
-
*/
|
|
1149
|
-
var EventMiddleware = class extends BaseHandler {
|
|
1150
|
-
firedEvent;
|
|
1151
|
-
constructor(event, core, eventName) {
|
|
1152
|
-
super(event, core, void 0, "events");
|
|
1153
|
-
this.firedEvent = eventName;
|
|
1154
|
-
}
|
|
1155
|
-
/**
|
|
1156
|
-
* The event that fired this middleware. Read it on a catchall or multi-event middleware, where `this.event`
|
|
1157
|
-
* is `never`, to do work that does not depend on the payload shape. A single-event middleware reads
|
|
1158
|
-
* `this.event` directly and does not need this.
|
|
1159
|
-
*/
|
|
1160
|
-
get eventName() {
|
|
1161
|
-
if (this.firedEvent === void 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.EventMiddlewareNameUnavailable);
|
|
1162
|
-
return this.firedEvent;
|
|
1163
|
-
}
|
|
1164
|
-
};
|
|
1165
|
-
|
|
1166
|
-
//#endregion
|
|
1167
|
-
//#region src/pagination/Paginator.ts
|
|
1168
|
-
function contextOf(interaction, core) {
|
|
1169
|
-
return {
|
|
1170
|
-
interaction,
|
|
1171
|
-
user: interaction.user,
|
|
1172
|
-
guild: interaction.guild,
|
|
1173
|
-
core
|
|
1174
|
-
};
|
|
1175
|
-
}
|
|
1176
|
-
/**
|
|
1177
|
-
* A restart-proof paginator. Each nav button's customId encodes its full target page, so clicks are
|
|
1178
|
-
* idempotent and survive a restart. A persistent `@ButtonRoute` on `Handler` dispatches the clicks.
|
|
1179
|
-
*
|
|
1180
|
-
* @typeParam Item - The item type, inferred from the source.
|
|
1181
|
-
* @typeParam Prefix - The route prefix, inferred from `config.prefix`.
|
|
1182
|
-
*
|
|
1183
|
-
* @example
|
|
1184
|
-
* ```ts
|
|
1185
|
-
* export const Bans = new Paginator({
|
|
1186
|
-
* prefix: 'bans',
|
|
1187
|
-
* source: new ArraySource((ctx) => ctx.guild.bans.fetch().then((b) => [...b.values()]), { perPage: 10 }),
|
|
1188
|
-
* renderItem: (ban) => ban.user.tag
|
|
1189
|
-
* });
|
|
1190
|
-
*
|
|
1191
|
-
* \@ButtonRoute(Bans.cursor)
|
|
1192
|
-
* export class BansNav extends Bans.Handler {}
|
|
1193
|
-
* ```
|
|
1194
|
-
*/
|
|
1195
|
-
var Paginator = class extends _seedcord_core.PaginatorBase {
|
|
1196
|
-
/** The nav handler base. Extend it with an empty body and decorate it, `@ButtonRoute(p.cursor)`. */
|
|
1197
|
-
Handler;
|
|
1198
|
-
constructor(config) {
|
|
1199
|
-
super(config);
|
|
1200
|
-
const loadPage = (ctx, n) => this.page(ctx, n);
|
|
1201
|
-
this.Handler = class Nav extends ButtonHandler {
|
|
1202
|
-
async execute() {
|
|
1203
|
-
await this.deferUpdate();
|
|
1204
|
-
const response = await loadPage(contextOf(this.event, this.core), this.params.page);
|
|
1205
|
-
await this.update(response);
|
|
1206
|
-
}
|
|
1207
|
-
};
|
|
1208
|
-
}
|
|
1209
|
-
/**
|
|
1210
|
-
* Render page 0 and send it through the handler's sender, which picks reply, edit, or followUp from its
|
|
1211
|
-
* ack state.
|
|
1212
|
-
*
|
|
1213
|
-
* @param handler - The handler starting the paginator, normally `this`.
|
|
1214
|
-
*/
|
|
1215
|
-
async start(handler) {
|
|
1216
|
-
const interaction = handler.getEvent();
|
|
1217
|
-
const response = await this.page(contextOf(interaction, handler.core), 0);
|
|
1218
|
-
return handler.getSender().send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
1219
|
-
}
|
|
1220
|
-
};
|
|
1221
|
-
|
|
1222
|
-
//#endregion
|
|
1223
|
-
//#region src/pagination/sources.ts
|
|
1224
|
-
/**
|
|
1225
|
-
* A source for a bounded list you can load whole. It loads the full list on every click, then slices the
|
|
1226
|
-
* page, so the real total is known (a real last button, "Page X of Y"). Cache inside your loader if the
|
|
1227
|
-
* load is expensive.
|
|
1228
|
-
*
|
|
1229
|
-
* @typeParam Item - The item type, inferred from the loader's return.
|
|
1230
|
-
*/
|
|
1231
|
-
var ArraySource = class extends _seedcord_core.ArraySource {};
|
|
1232
|
-
/**
|
|
1233
|
-
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
1234
|
-
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
1235
|
-
* source has no cheap total, so `totalPages` is undefined, the last button is omitted, and the indicator
|
|
1236
|
-
* reads "Page X".
|
|
1237
|
-
*
|
|
1238
|
-
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
1239
|
-
*/
|
|
1240
|
-
var CursorSource = class extends _seedcord_core.CursorSource {};
|
|
1241
|
-
|
|
1242
|
-
//#endregion
|
|
1243
|
-
//#region src/subscribers/index.ts
|
|
1244
|
-
/**
|
|
1245
|
-
* Base class for a gateway subscriber, binding `this.core` to the gateway {@link Core}.
|
|
1246
|
-
*
|
|
1247
|
-
* @typeParam KeyOfSubscribers - The subscription key this handler receives
|
|
1248
|
-
*/
|
|
1249
|
-
var Subscriber = class extends _seedcord_core.Subscriber {};
|
|
1250
|
-
/**
|
|
1251
|
-
* Base class for a gateway subscriber that delivers its event to a Discord webhook, binding
|
|
1252
|
-
* `this.core` to the gateway {@link Core}.
|
|
1253
|
-
*
|
|
1254
|
-
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
1255
|
-
*/
|
|
1256
|
-
var WebhookLog = class extends _seedcord_core.WebhookLog {};
|
|
1257
|
-
|
|
1258
|
-
//#endregion
|
|
1259
|
-
//#region src/plugin.ts
|
|
1260
|
-
/**
|
|
1261
|
-
* Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
|
|
1262
|
-
*
|
|
1263
|
-
* A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
|
|
1264
|
-
* `this.core` is typed to the shared members only.
|
|
1265
|
-
*
|
|
1266
|
-
* @typeParam Opts - The transport and runtime the plugin declares.
|
|
1267
|
-
*/
|
|
1268
|
-
var Plugin = class extends _seedcord_core_plugin.Plugin {};
|
|
1269
|
-
|
|
1270
|
-
//#endregion
|
|
1271
|
-
//#region src/miscellaneous/deriveEventActor.ts
|
|
1272
|
-
function deriveEventActor(args) {
|
|
1273
|
-
const tuple = Array.isArray(args) ? args : [args];
|
|
1274
|
-
for (const arg of tuple) {
|
|
1275
|
-
if (arg instanceof discord_js.Message) return {
|
|
1276
|
-
guild: arg.guild,
|
|
1277
|
-
member: arg.member,
|
|
1278
|
-
user: arg.author,
|
|
1279
|
-
channelId: arg.channelId
|
|
1280
|
-
};
|
|
1281
|
-
if (arg instanceof discord_js.GuildMember) return {
|
|
1282
|
-
guild: arg.guild,
|
|
1283
|
-
member: arg,
|
|
1284
|
-
user: arg.user,
|
|
1285
|
-
channelId: null
|
|
1286
|
-
};
|
|
1287
|
-
if (arg instanceof discord_js.User) return {
|
|
1288
|
-
guild: null,
|
|
1289
|
-
member: null,
|
|
1290
|
-
user: arg,
|
|
1291
|
-
channelId: null
|
|
1292
|
-
};
|
|
1293
|
-
}
|
|
1294
|
-
return {
|
|
1295
|
-
guild: null,
|
|
1296
|
-
member: null,
|
|
1297
|
-
user: null,
|
|
1298
|
-
channelId: null
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
//#endregion
|
|
1303
|
-
//#region src/bot/gates/runGates.ts
|
|
1304
|
-
function interactionGateContext(interaction, core) {
|
|
1305
|
-
const rawMember = interaction.member;
|
|
1306
|
-
const memberRoleIds = rawMember instanceof discord_js.GuildMember ? [...rawMember.roles.cache.keys()].filter((id) => id !== interaction.guildId) : rawMember?.roles ?? [];
|
|
1307
|
-
return {
|
|
1308
|
-
kind: "interaction",
|
|
1309
|
-
interaction,
|
|
1310
|
-
core,
|
|
1311
|
-
user: interaction.user,
|
|
1312
|
-
guild: interaction.guild,
|
|
1313
|
-
member: rawMember instanceof discord_js.GuildMember ? rawMember : null,
|
|
1314
|
-
userId: interaction.user.id,
|
|
1315
|
-
guildId: interaction.guildId,
|
|
1316
|
-
channelId: interaction.channelId,
|
|
1317
|
-
memberRoleIds,
|
|
1318
|
-
memberPermissions: interaction.memberPermissions?.bitfield ?? null,
|
|
1319
|
-
appPermissions: interaction.appPermissions.bitfield,
|
|
1320
|
-
memberGuildPermissions: rawMember instanceof discord_js.GuildMember ? rawMember.permissions.bitfield : null,
|
|
1321
|
-
appGuildPermissions: interaction.guild?.members.me?.permissions.bitfield ?? null,
|
|
1322
|
-
routeId: null
|
|
1323
|
-
};
|
|
1324
|
-
}
|
|
1325
|
-
function eventGateContext(eventName, args, core) {
|
|
1326
|
-
const actor = deriveEventActor(args);
|
|
1327
|
-
return {
|
|
1328
|
-
kind: "event",
|
|
1329
|
-
core,
|
|
1330
|
-
eventName,
|
|
1331
|
-
payload: args,
|
|
1332
|
-
user: actor.user,
|
|
1333
|
-
guild: actor.guild,
|
|
1334
|
-
member: actor.member,
|
|
1335
|
-
userId: actor.user?.id ?? null,
|
|
1336
|
-
guildId: actor.guild?.id ?? null,
|
|
1337
|
-
channelId: actor.channelId,
|
|
1338
|
-
memberRoleIds: actor.member ? [...actor.member.roles.cache.keys()].filter((id) => id !== actor.guild?.id) : [],
|
|
1339
|
-
memberPermissions: actor.member?.permissions.bitfield ?? null,
|
|
1340
|
-
appPermissions: null,
|
|
1341
|
-
memberGuildPermissions: actor.member?.permissions.bitfield ?? null,
|
|
1342
|
-
appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null,
|
|
1343
|
-
routeId: null
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
//#endregion
|
|
1348
|
-
//#region src/miscellaneous/extractErrorResponse.ts
|
|
1349
|
-
const logger$2 = new _seedcord_logger.Logger("ErrorsHandling", { channel: "errors" });
|
|
1350
|
-
function extractErrorResponse(error, core, origin) {
|
|
1351
|
-
const uuid = node_crypto.randomUUID();
|
|
1352
|
-
const developerUsername = core.config.notifications?.developerUsername;
|
|
1353
|
-
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
1354
|
-
uuid,
|
|
1355
|
-
developerUsername
|
|
1356
|
-
};
|
|
1357
|
-
if (error instanceof _seedcord_core.Notice) {
|
|
1358
|
-
if (error.report) reportFault(error, core, origin, uuid);
|
|
1359
|
-
return {
|
|
1360
|
-
uuid,
|
|
1361
|
-
response: error.render(ctx)
|
|
1362
|
-
};
|
|
1363
|
-
}
|
|
1364
|
-
reportRawFault(error, core, origin, uuid);
|
|
1365
|
-
const override = core.config.errors?.defaultError;
|
|
1366
|
-
return {
|
|
1367
|
-
uuid,
|
|
1368
|
-
response: override ? new override(uuid).render(ctx) : new _seedcord_core.Fault().render(ctx)
|
|
1369
|
-
};
|
|
1370
|
-
}
|
|
1371
|
-
function withThrottle(core, origin, error, publish) {
|
|
1372
|
-
const throttle = _seedcord_core_internal.FaultThrottle.for(core);
|
|
1373
|
-
const key = faultKey(origin, error);
|
|
1374
|
-
if (!throttle.shouldReport(key)) {
|
|
1375
|
-
logger$2.debug(`throttled duplicate fault ${key}`);
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
|
-
publish();
|
|
1379
|
-
throttle.markReported(key);
|
|
1380
|
-
}
|
|
1381
|
-
function reportFault(denial, core, origin, uuid) {
|
|
1382
|
-
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
1383
|
-
withThrottle(core, origin, denial, () => {
|
|
1384
|
-
if (origin.interaction) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
|
|
1385
|
-
denial,
|
|
1386
|
-
uuid,
|
|
1387
|
-
source: buildInteractionSource(origin.interaction)
|
|
1388
|
-
});
|
|
1389
|
-
else if (origin.event) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
|
|
1390
|
-
denial,
|
|
1391
|
-
uuid,
|
|
1392
|
-
source: buildEventSource(origin.event, origin)
|
|
1393
|
-
});
|
|
1394
|
-
else core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
|
|
1395
|
-
uuid,
|
|
1396
|
-
error: denial,
|
|
1397
|
-
...scalarActors(origin),
|
|
1398
|
-
metadata: metadataFor(origin)
|
|
1399
|
-
});
|
|
1400
|
-
});
|
|
1401
|
-
}
|
|
1402
|
-
function causeLine(error) {
|
|
1403
|
-
const { cause } = error;
|
|
1404
|
-
if (!Error.isError(cause)) return "";
|
|
1405
|
-
const [first] = cause.message.split("\n");
|
|
1406
|
-
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
1407
|
-
}
|
|
1408
|
-
function reportRawFault(error, core, origin, uuid) {
|
|
1409
|
-
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
1410
|
-
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
1411
|
-
withThrottle(core, origin, error, () => {
|
|
1412
|
-
core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
|
|
1413
|
-
uuid,
|
|
1414
|
-
error,
|
|
1415
|
-
...scalarActors(origin),
|
|
1416
|
-
metadata: metadataFor(origin)
|
|
1417
|
-
});
|
|
1418
|
-
});
|
|
1419
|
-
}
|
|
1420
|
-
function scalarActors(origin) {
|
|
1421
|
-
return {
|
|
1422
|
-
guild: origin.guild ? {
|
|
1423
|
-
id: origin.guild.id,
|
|
1424
|
-
name: origin.guild.name
|
|
1425
|
-
} : void 0,
|
|
1426
|
-
user: origin.user ? {
|
|
1427
|
-
id: origin.user.id,
|
|
1428
|
-
username: origin.user.username
|
|
1429
|
-
} : void 0
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
function metadataFor(origin) {
|
|
1433
|
-
if (origin.event) return {
|
|
1434
|
-
eventName: origin.event.name,
|
|
1435
|
-
handler: origin.event.handler,
|
|
1436
|
-
args: origin.event.args
|
|
1437
|
-
};
|
|
1438
|
-
return origin.metadata;
|
|
1439
|
-
}
|
|
1440
|
-
function buildEventSource(event, origin) {
|
|
1441
|
-
return {
|
|
1442
|
-
kind: "event",
|
|
1443
|
-
eventName: event.name,
|
|
1444
|
-
handler: event.handler,
|
|
1445
|
-
userId: origin.user?.id ?? null,
|
|
1446
|
-
guildId: origin.guild?.id ?? null,
|
|
1447
|
-
channelId: event.channelId,
|
|
1448
|
-
raw: event.args
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
function faultKey(origin, error) {
|
|
1452
|
-
const name = error instanceof _seedcord_core.Notice ? error.name : error instanceof discord_js.DiscordAPIError ? String(error.code) : error.constructor.name;
|
|
1453
|
-
if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
|
|
1454
|
-
if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
|
|
1455
|
-
if (origin.route) return `autocomplete:${origin.route}:${name}`;
|
|
1456
|
-
return `autocomplete:${name}`;
|
|
1457
|
-
}
|
|
1458
|
-
function interactionRoute(interaction) {
|
|
1459
|
-
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
1460
|
-
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
1461
|
-
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return (0, _seedcord_core_internal.prefixOf)(interaction.customId) || interaction.customId;
|
|
1462
|
-
return "interaction";
|
|
1463
|
-
}
|
|
1464
|
-
function buildInteractionSource(interaction) {
|
|
1465
|
-
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1466
|
-
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1467
|
-
return {
|
|
1468
|
-
kind: "interaction",
|
|
1469
|
-
interactionKind: interactionKind(interaction),
|
|
1470
|
-
command,
|
|
1471
|
-
customId,
|
|
1472
|
-
userId: interaction.user.id,
|
|
1473
|
-
guildId: interaction.guildId,
|
|
1474
|
-
channelId: interaction.channelId,
|
|
1475
|
-
interactionId: interaction.id,
|
|
1476
|
-
raw: interaction
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
function interactionKind(interaction) {
|
|
1480
|
-
if (interaction.isChatInputCommand()) return "slash";
|
|
1481
|
-
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1482
|
-
if (interaction.isButton()) return "button";
|
|
1483
|
-
if (interaction.isAnySelectMenu()) return "select";
|
|
1484
|
-
return "modal";
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
//#endregion
|
|
1488
|
-
//#region src/bot/handleEventFault.ts
|
|
1489
|
-
const logger$1 = new _seedcord_logger.Logger("Faults", { channel: "errors" });
|
|
1490
|
-
function handleEventFault(caught, eventName, handlerName, args, core) {
|
|
1491
|
-
if (caught instanceof _seedcord_core.Silence) {
|
|
1492
|
-
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger$1.debug(`Silence: ${caught.reason}`);
|
|
1493
|
-
return;
|
|
1494
|
-
}
|
|
1495
|
-
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
1496
|
-
const ignore = new Set(core.config.errors?.ignoreEventApiCodes ?? []);
|
|
1497
|
-
if (error instanceof discord_js.DiscordAPIError && ignore.has(error.code)) {
|
|
1498
|
-
logger$1.debug(`swallowed api code ${error.code}`);
|
|
1499
|
-
return;
|
|
1500
|
-
}
|
|
1501
|
-
if (error instanceof _seedcord_core.Notice && !error.report) return;
|
|
1502
|
-
const actor = deriveEventActor(args);
|
|
1503
|
-
extractErrorResponse(error, core, {
|
|
1504
|
-
event: {
|
|
1505
|
-
name: eventName,
|
|
1506
|
-
handler: handlerName,
|
|
1507
|
-
args,
|
|
1508
|
-
channelId: actor.channelId
|
|
1509
|
-
},
|
|
1510
|
-
guild: actor.guild,
|
|
1511
|
-
user: actor.user
|
|
1512
|
-
});
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
//#endregion
|
|
1516
|
-
//#region src/bot/controllers/EventDispatcher.ts
|
|
1517
|
-
/**
|
|
1518
|
-
* Interactions route through InteractionDispatcher.
|
|
1519
|
-
*
|
|
1520
|
-
* @internal
|
|
1521
|
-
*/
|
|
1522
|
-
var EventDispatcher = class {
|
|
1523
|
-
core;
|
|
1524
|
-
logger = new _seedcord_logger.Logger("Events", { channel: "events" });
|
|
1525
|
-
isInitialized = false;
|
|
1526
|
-
eventMap = /* @__PURE__ */ new Map();
|
|
1527
|
-
middlewares = [];
|
|
1528
|
-
executedOnceHandlers = /* @__PURE__ */ new Set();
|
|
1529
|
-
attachedEvents = /* @__PURE__ */ new Set();
|
|
1530
|
-
inFlight = /* @__PURE__ */ new Set();
|
|
1531
|
-
draining = false;
|
|
1532
|
-
loading = false;
|
|
1533
|
-
loadedHandlers = [];
|
|
1534
|
-
loadedMiddlewares = [];
|
|
1535
|
-
hmrHandler;
|
|
1536
|
-
constructor(core) {
|
|
1537
|
-
this.core = core;
|
|
1538
|
-
const eventsDir = this.core.config.bot.events.path;
|
|
1539
|
-
if (!eventsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["EventDispatcher", "events"]);
|
|
1540
|
-
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
1541
|
-
this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
|
|
1542
|
-
handlersDir: eventsDir,
|
|
1543
|
-
...(0, _seedcord_utils.hasKeys)(this.core.config.bot.events, ["middlewares"]) && { middlewaresDir: this.core.config.bot.events.middlewares },
|
|
1544
|
-
isHandler: this.isEventHandlerClass.bind(this),
|
|
1545
|
-
isMiddleware: this.isMiddlewareClass.bind(this),
|
|
1546
|
-
registerHandler: this.registerHandler.bind(this),
|
|
1547
|
-
registerMiddleware: this.registerMiddleware.bind(this),
|
|
1548
|
-
unregisterHandler: this.unregisterHandler.bind(this),
|
|
1549
|
-
unregisterMiddleware: this.unregisterMiddleware.bind(this),
|
|
1550
|
-
getArtifacts: this.getArtifacts.bind(this),
|
|
1551
|
-
logger: this.logger
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
getArtifacts(ctor) {
|
|
1555
|
-
const events = [];
|
|
1556
|
-
for (const [event, handlers] of this.eventMap.entries()) if (handlers.some((h) => h.ctor === ctor)) events.push(event);
|
|
1557
|
-
return events;
|
|
1558
|
-
}
|
|
1559
|
-
async init() {
|
|
1560
|
-
if (this.isInitialized) return;
|
|
1561
|
-
this.isInitialized = true;
|
|
1562
|
-
const handlersDir = this.core.config.bot.events.path;
|
|
1563
|
-
if (!handlersDir) return;
|
|
1564
|
-
const middlewareDir = (0, _seedcord_utils.hasKeys)(this.core.config.bot.events, ["middlewares"]) ? this.core.config.bot.events.middlewares : void 0;
|
|
1565
|
-
this.loading = true;
|
|
1566
|
-
this.loadedHandlers.length = 0;
|
|
1567
|
-
this.loadedMiddlewares.length = 0;
|
|
1568
|
-
try {
|
|
1569
|
-
if (middlewareDir) await this.loadMiddlewares(middlewareDir);
|
|
1570
|
-
await this.loadHandlers(handlersDir);
|
|
1571
|
-
} finally {
|
|
1572
|
-
this.loading = false;
|
|
1573
|
-
}
|
|
1574
|
-
this.attachToClient();
|
|
1575
|
-
this.reportLoad();
|
|
1576
|
-
}
|
|
1577
|
-
reportLoad() {
|
|
1578
|
-
const { utils } = this.logger;
|
|
1579
|
-
utils.summary("Loaded", {
|
|
1580
|
-
"event handlers": this.loadedHandlers.length,
|
|
1581
|
-
middlewares: this.loadedMiddlewares.length
|
|
1582
|
-
}, "debug");
|
|
1583
|
-
if (this.loadedMiddlewares.length > 0) utils.block("Loaded event middlewares", utils.entries(this.loadedMiddlewares), "debug");
|
|
1584
|
-
const perEvent = {};
|
|
1585
|
-
for (const [event, handlers] of this.eventMap) perEvent[event] = handlers.length;
|
|
1586
|
-
utils.block("Loaded event handlers", [...utils.entries(this.loadedHandlers), ...utils.counts(perEvent)], "debug");
|
|
1587
|
-
}
|
|
1588
|
-
async loadHandlers(dir) {
|
|
1589
|
-
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
1590
|
-
for (const val of Object.values(imported)) {
|
|
1591
|
-
if (!this.isEventHandlerClass(val)) continue;
|
|
1592
|
-
this.registerHandler(val, relativePath);
|
|
1593
|
-
this.hmrHandler?.trackHandler(fullPath, val);
|
|
1594
|
-
}
|
|
1595
|
-
});
|
|
1596
|
-
}
|
|
1597
|
-
async loadMiddlewares(dir) {
|
|
1598
|
-
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
1599
|
-
for (const val of Object.values(imported)) {
|
|
1600
|
-
if (!this.isMiddlewareClass(val)) continue;
|
|
1601
|
-
this.registerMiddleware(val, relativePath);
|
|
1602
|
-
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
1603
|
-
}
|
|
1604
|
-
});
|
|
1605
|
-
}
|
|
1606
|
-
/** @internal */
|
|
1607
|
-
async onHmr(event) {
|
|
1608
|
-
await this.hmrHandler?.handle(event);
|
|
1609
|
-
}
|
|
1610
|
-
unregisterHandler(handlerClass, artifacts) {
|
|
1611
|
-
const events = artifacts ?? [...this.eventMap.keys()];
|
|
1612
|
-
for (const event of events) {
|
|
1613
|
-
const handlers = this.eventMap.get(event);
|
|
1614
|
-
if (!handlers) continue;
|
|
1615
|
-
const index = handlers.findIndex((h) => h.ctor === handlerClass);
|
|
1616
|
-
if (index !== -1) {
|
|
1617
|
-
handlers.splice(index, 1);
|
|
1618
|
-
if (handlers.length === 0) this.eventMap.delete(event);
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
unregisterMiddleware(middlewareCtor) {
|
|
1623
|
-
const index = this.middlewares.findIndex((entry) => entry.ctor === middlewareCtor);
|
|
1624
|
-
if (index !== -1) this.middlewares.splice(index, 1);
|
|
1625
|
-
}
|
|
1626
|
-
registerMiddleware(middlewareCtor, relativePath) {
|
|
1627
|
-
const metadata = Reflect.getMetadata(_seedcord_core_internal.MiddlewareMetadataKey, middlewareCtor);
|
|
1628
|
-
if (metadata?.type !== "event") return;
|
|
1629
|
-
if (this.middlewares.some((entry) => entry.ctor === middlewareCtor)) return;
|
|
1630
|
-
this.middlewares.push({
|
|
1631
|
-
ctor: middlewareCtor,
|
|
1632
|
-
priority: metadata.priority,
|
|
1633
|
-
...metadata.events && { events: metadata.events }
|
|
1634
|
-
});
|
|
1635
|
-
this.middlewares.sort((a, b) => a.priority - b.priority);
|
|
1636
|
-
if (this.loading) this.loadedMiddlewares.push({
|
|
1637
|
-
name: `${middlewareCtor.name} (${metadata.priority})`,
|
|
1638
|
-
from: (0, _seedcord_utils.formatFilePath)(relativePath)
|
|
1639
|
-
});
|
|
1640
|
-
}
|
|
1641
|
-
async runMiddlewares(eventName, args) {
|
|
1642
|
-
for (const { ctor, events } of this.middlewares) {
|
|
1643
|
-
if (events && !events.includes(eventName)) continue;
|
|
1644
|
-
try {
|
|
1645
|
-
await new ctor(args, this.core, eventName).execute();
|
|
1646
|
-
} catch (caught) {
|
|
1647
|
-
handleEventFault(caught, String(eventName), ctor.name, args, this.core);
|
|
1648
|
-
return false;
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
return true;
|
|
1652
|
-
}
|
|
1653
|
-
isEventHandlerClass(obj) {
|
|
1654
|
-
if (typeof obj !== "function") return false;
|
|
1655
|
-
return obj.prototype instanceof EventHandler && Reflect.hasMetadata(_seedcord_core_internal.EventMetadataKey, obj);
|
|
1656
|
-
}
|
|
1657
|
-
isMiddlewareClass(obj) {
|
|
1658
|
-
if (typeof obj !== "function") return false;
|
|
1659
|
-
return obj.prototype instanceof EventMiddleware && Reflect.hasMetadata(_seedcord_core_internal.MiddlewareMetadataKey, obj);
|
|
1660
|
-
}
|
|
1661
|
-
registerHandler(handlerClass, relativePath) {
|
|
1662
|
-
const raw = Reflect.getMetadata(_seedcord_core_internal.EventMetadataKey, handlerClass);
|
|
1663
|
-
const register = (key, frequency) => {
|
|
1664
|
-
let handlers = this.eventMap.get(key);
|
|
1665
|
-
if (!handlers) {
|
|
1666
|
-
handlers = [];
|
|
1667
|
-
this.eventMap.set(key, handlers);
|
|
1668
|
-
}
|
|
1669
|
-
handlers.push({
|
|
1670
|
-
ctor: handlerClass,
|
|
1671
|
-
frequency
|
|
1672
|
-
});
|
|
1673
|
-
if (this.isInitialized && !this.attachedEvents.has(key)) this.attachListener(key);
|
|
1674
|
-
};
|
|
1675
|
-
if (Array.isArray(raw)) for (const entry of raw) register(entry.event, entry.frequency);
|
|
1676
|
-
else {
|
|
1677
|
-
const names = (0, _seedcord_core_internal.areRoutes)(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
1678
|
-
for (const name of names) register(name, "on");
|
|
1679
|
-
}
|
|
1680
|
-
const from = (0, _seedcord_utils.formatFilePath)(relativePath);
|
|
1681
|
-
if (this.loading) this.loadedHandlers.push({
|
|
1682
|
-
name: handlerClass.name,
|
|
1683
|
-
from
|
|
1684
|
-
});
|
|
1685
|
-
}
|
|
1686
|
-
attachToClient() {
|
|
1687
|
-
for (const [eventName] of this.eventMap) this.attachListener(eventName);
|
|
1688
|
-
}
|
|
1689
|
-
attachListener(eventName) {
|
|
1690
|
-
if (this.attachedEvents.has(eventName)) return;
|
|
1691
|
-
this.attachedEvents.add(eventName);
|
|
1692
|
-
const handlerEntries = this.eventMap.get(eventName);
|
|
1693
|
-
this.logger.debug(`Attaching ${_seedcord_logger.paint.mint.bold(eventName)} to the client with ${_seedcord_logger.paint.mute(handlerEntries?.length ?? 0)} handler(s)`);
|
|
1694
|
-
this.core.bot.client.on(eventName, (...args) => {
|
|
1695
|
-
if (this.draining) return;
|
|
1696
|
-
this.core.bus[_seedcord_core_internal.PublishDefault]("anyEvent", {
|
|
1697
|
-
name: eventName,
|
|
1698
|
-
args
|
|
1699
|
-
});
|
|
1700
|
-
const run = this.processEvent(eventName, args).catch((caught) => {
|
|
1701
|
-
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
1702
|
-
this.logger.error(`[${_seedcord_logger.paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
1703
|
-
this.core.bus[_seedcord_core_internal.PublishDefault]("unhandledEventError", { error });
|
|
1704
|
-
});
|
|
1705
|
-
this.inFlight.add(run);
|
|
1706
|
-
run.finally(() => this.inFlight.delete(run));
|
|
1707
|
-
});
|
|
1708
|
-
}
|
|
1709
|
-
stopAccepting() {
|
|
1710
|
-
this.draining = true;
|
|
1711
|
-
}
|
|
1712
|
-
drain(timeoutMs) {
|
|
1713
|
-
return (0, _seedcord_core_node_internal.settleWithin)(Promise.allSettled(this.inFlight), timeoutMs);
|
|
1714
|
-
}
|
|
1715
|
-
async processEvent(eventName, args) {
|
|
1716
|
-
const handlerEntries = this.eventMap.get(eventName);
|
|
1717
|
-
if (!handlerEntries || handlerEntries.length === 0) return;
|
|
1718
|
-
const handlersToExecute = handlerEntries.filter((entry) => entry.frequency !== "once" || !this.executedOnceHandlers.has(entry.ctor));
|
|
1719
|
-
if (handlersToExecute.length === 0) return;
|
|
1720
|
-
if (!await this.runMiddlewares(eventName, args)) return;
|
|
1721
|
-
for (const entry of handlersToExecute) {
|
|
1722
|
-
if (entry.frequency === "once") {
|
|
1723
|
-
if (this.executedOnceHandlers.has(entry.ctor)) continue;
|
|
1724
|
-
this.executedOnceHandlers.add(entry.ctor);
|
|
1725
|
-
}
|
|
1726
|
-
await this.processHandler(eventName, entry.ctor, args);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
async processHandler(eventName, ctor, args) {
|
|
1730
|
-
try {
|
|
1731
|
-
this.logger.debug(`Processing ${_seedcord_logger.paint.mint.bold(eventName)} with ${_seedcord_logger.paint.mute(ctor.name)}`);
|
|
1732
|
-
const handler = new ctor(args, this.core, eventName);
|
|
1733
|
-
const eventCtx = eventGateContext(eventName, args, this.core);
|
|
1734
|
-
await (0, _seedcord_core_internal.runHandlerGates)(ctor, eventCtx);
|
|
1735
|
-
await handler.execute();
|
|
1736
|
-
} catch (caught) {
|
|
1737
|
-
handleEventFault(caught, String(eventName), ctor.name, args, this.core);
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
};
|
|
1741
|
-
|
|
1742
|
-
//#endregion
|
|
1743
|
-
//#region src/bot/defaults/UnhandledRepliable.ts
|
|
1744
|
-
var UnhandledRepliable = class extends InteractionHandler {
|
|
1745
|
-
async execute() {
|
|
1746
|
-
await this.reply("Feature not implemented yet.");
|
|
1747
|
-
}
|
|
1748
|
-
};
|
|
1749
|
-
|
|
1750
|
-
//#endregion
|
|
1751
|
-
//#region src/bot/defaults/UnhandledAutocomplete.ts
|
|
1752
|
-
var UnhandledAutocomplete = class extends AutocompleteHandler {
|
|
1753
|
-
async execute() {
|
|
1754
|
-
await this.respond([]);
|
|
1755
|
-
}
|
|
1756
|
-
};
|
|
1757
|
-
|
|
1758
|
-
//#endregion
|
|
1759
|
-
//#region src/bot/harmlessApiCodes.ts
|
|
1760
|
-
/**
|
|
1761
|
-
* Discord API error codes that mean a dead end on an interaction, a dead token, a double ack, or a gone
|
|
1762
|
-
* message. Nothing to show the user and no bug to report, so the boundary's fault-card send drops them.
|
|
1763
|
-
*
|
|
1764
|
-
* @internal
|
|
1765
|
-
*/
|
|
1766
|
-
const HARMLESS_API_CODES = /* @__PURE__ */ new Set([
|
|
1767
|
-
discord_js.RESTJSONErrorCodes.UnknownInteraction,
|
|
1768
|
-
discord_js.RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
|
|
1769
|
-
discord_js.RESTJSONErrorCodes.UnknownMessage
|
|
1770
|
-
]);
|
|
1771
|
-
|
|
1772
|
-
//#endregion
|
|
1773
|
-
//#region src/bot/handleInteractionFault.ts
|
|
1774
|
-
const logger = new _seedcord_logger.Logger("Faults", { channel: "errors" });
|
|
1775
|
-
async function handleInteractionFault(caught, interaction, core, routeId, sender) {
|
|
1776
|
-
if (caught instanceof _seedcord_core.Silence) {
|
|
1777
|
-
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger.debug(`Silence: ${caught.reason}`);
|
|
1778
|
-
return;
|
|
1779
|
-
}
|
|
1780
|
-
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
1781
|
-
const ignore = new Set(core.config.errors?.ignoreApiCodes ?? []);
|
|
1782
|
-
if (error instanceof discord_js.DiscordAPIError && ignore.has(error.code)) {
|
|
1783
|
-
logger.debug(`swallowed api code ${error.code}`);
|
|
1784
|
-
return;
|
|
1785
|
-
}
|
|
1786
|
-
if (interaction.isAutocomplete()) {
|
|
1787
|
-
extractErrorResponse(error, core, {
|
|
1788
|
-
guild: interaction.guild,
|
|
1789
|
-
user: interaction.user,
|
|
1790
|
-
metadata: interaction,
|
|
1791
|
-
route: slashRouteOf(interaction)
|
|
1792
|
-
});
|
|
1793
|
-
await sendEmptyChoices(interaction, core, routeId);
|
|
1794
|
-
return;
|
|
1795
|
-
}
|
|
1796
|
-
const { response } = extractErrorResponse(error, core, {
|
|
1797
|
-
interaction,
|
|
1798
|
-
guild: interaction.guild,
|
|
1799
|
-
user: interaction.user,
|
|
1800
|
-
metadata: interaction
|
|
1801
|
-
});
|
|
1802
|
-
await sendGuarded(sender ?? new ReplySender(interaction, routeId, core.bus), response, error instanceof _seedcord_core.Notice ? error.ephemeral : true);
|
|
1803
|
-
}
|
|
1804
|
-
async function sendEmptyChoices(interaction, core, routeId) {
|
|
1805
|
-
const telemetry = {
|
|
1806
|
-
bus: core.bus,
|
|
1807
|
-
interactionId: interaction.id
|
|
1808
|
-
};
|
|
1809
|
-
try {
|
|
1810
|
-
await (0, _seedcord_core_internal.reportedWrite)(telemetry, routeId, "respond", () => interaction.respond([]));
|
|
1811
|
-
} catch (error) {
|
|
1812
|
-
logger.debug(`autocomplete empty-choices send failed: ${String(error)}`);
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
async function sendGuarded(sender, response, ephemeral) {
|
|
1816
|
-
try {
|
|
1817
|
-
await sender.send(response, { ephemeral });
|
|
1818
|
-
} catch (error) {
|
|
1819
|
-
if (error instanceof discord_js.DiscordAPIError && HARMLESS_API_CODES.has(error.code)) {
|
|
1820
|
-
logger.debug(`reply send hit harmless code ${error.code}`);
|
|
1821
|
-
return;
|
|
1822
|
-
}
|
|
1823
|
-
logger.error("reply send failed", error);
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
//#endregion
|
|
1828
|
-
//#region src/bot/controllers/InteractionDispatcher.ts
|
|
1829
|
-
var InteractionDispatcher = class {
|
|
1830
|
-
core;
|
|
1831
|
-
logger = new _seedcord_logger.Logger("Interactions", { channel: "interactions" });
|
|
1832
|
-
isInitialized = false;
|
|
1833
|
-
slashMap = /* @__PURE__ */ new Map();
|
|
1834
|
-
buttonMap = /* @__PURE__ */ new Map();
|
|
1835
|
-
modalMap = /* @__PURE__ */ new Map();
|
|
1836
|
-
stringSelectMap = /* @__PURE__ */ new Map();
|
|
1837
|
-
userSelectMap = /* @__PURE__ */ new Map();
|
|
1838
|
-
roleSelectMap = /* @__PURE__ */ new Map();
|
|
1839
|
-
channelSelectMap = /* @__PURE__ */ new Map();
|
|
1840
|
-
mentionableSelectMap = /* @__PURE__ */ new Map();
|
|
1841
|
-
messageContextMenuMap = /* @__PURE__ */ new Map();
|
|
1842
|
-
userContextMenuMap = /* @__PURE__ */ new Map();
|
|
1843
|
-
autocompleteMap = /* @__PURE__ */ new Map();
|
|
1844
|
-
handlerFiles = /* @__PURE__ */ new Map();
|
|
1845
|
-
keysToIgnore = /* @__PURE__ */ new Set();
|
|
1846
|
-
middlewares = [];
|
|
1847
|
-
inFlight = /* @__PURE__ */ new Set();
|
|
1848
|
-
draining = false;
|
|
1849
|
-
loading = false;
|
|
1850
|
-
loadedHandlers = [];
|
|
1851
|
-
loadedMiddlewares = [];
|
|
1852
|
-
hmrHandler;
|
|
1853
|
-
routeTypes = [
|
|
1854
|
-
[_seedcord_core_internal.InteractionRoutes.Slash, this.slashMap],
|
|
1855
|
-
[_seedcord_core_internal.InteractionRoutes.Button, this.buttonMap],
|
|
1856
|
-
[_seedcord_core_internal.InteractionRoutes.Modal, this.modalMap],
|
|
1857
|
-
[_seedcord_core_internal.InteractionRoutes.StringMenu, this.stringSelectMap],
|
|
1858
|
-
[_seedcord_core_internal.InteractionRoutes.UserMenu, this.userSelectMap],
|
|
1859
|
-
[_seedcord_core_internal.InteractionRoutes.RoleMenu, this.roleSelectMap],
|
|
1860
|
-
[_seedcord_core_internal.InteractionRoutes.ChannelMenu, this.channelSelectMap],
|
|
1861
|
-
[_seedcord_core_internal.InteractionRoutes.MentionableMenu, this.mentionableSelectMap],
|
|
1862
|
-
[_seedcord_core_internal.InteractionRoutes.MessageContextMenu, this.messageContextMenuMap],
|
|
1863
|
-
[_seedcord_core_internal.InteractionRoutes.UserContextMenu, this.userContextMenuMap],
|
|
1864
|
-
[_seedcord_core_internal.InteractionRoutes.Autocomplete, this.autocompleteMap]
|
|
1865
|
-
];
|
|
1866
|
-
constructor(core) {
|
|
1867
|
-
this.core = core;
|
|
1868
|
-
const ignoredKeysFromConfig = (0, _seedcord_utils.hasKeys)(this.core.config.bot.interactions, ["ignoreCustomIds"]) ? this.core.config.bot.interactions.ignoreCustomIds : void 0;
|
|
1869
|
-
if (ignoredKeysFromConfig) for (const ignoredKey of ignoredKeysFromConfig) this.keysToIgnore.add(ignoredKey);
|
|
1870
|
-
this.keysToIgnore.add(CONFIRM_DEF);
|
|
1871
|
-
const interactionsDir = this.core.config.bot.interactions.path;
|
|
1872
|
-
if (!interactionsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["InteractionDispatcher", "interactions"]);
|
|
1873
|
-
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
1874
|
-
this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
|
|
1875
|
-
handlersDir: interactionsDir,
|
|
1876
|
-
...(0, _seedcord_utils.hasKeys)(this.core.config.bot.interactions, ["middlewares"]) && this.core.config.bot.interactions.middlewares && { middlewaresDir: this.core.config.bot.interactions.middlewares },
|
|
1877
|
-
isHandler: this.isHandlerClass.bind(this),
|
|
1878
|
-
isMiddleware: this.isMiddlewareClass.bind(this),
|
|
1879
|
-
registerHandler: this.registerHandler.bind(this),
|
|
1880
|
-
registerMiddleware: this.registerMiddleware.bind(this),
|
|
1881
|
-
unregisterHandler: this.unregisterHandler.bind(this),
|
|
1882
|
-
unregisterMiddleware: this.unregisterMiddleware.bind(this),
|
|
1883
|
-
getArtifacts: this.getArtifacts.bind(this),
|
|
1884
|
-
logger: this.logger
|
|
1885
|
-
});
|
|
1886
|
-
}
|
|
1887
|
-
warnUnhandledRoutes(commandLeaves) {
|
|
1888
|
-
for (const route of commandLeaves) if (!this.slashMap.has(route)) this.logger.warn(`Slash route ${_seedcord_logger.paint.sky.bold(route)} has no registered ${chalk.default.bold("@SlashRoute")} handler.`);
|
|
1889
|
-
}
|
|
1890
|
-
warnUnhandledContextMenuRoutes(leaves) {
|
|
1891
|
-
for (const name of leaves.user) if (!this.userContextMenuMap.has(name)) this.logger.warn(`User context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
|
|
1892
|
-
for (const name of leaves.message) if (!this.messageContextMenuMap.has(name)) this.logger.warn(`Message context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
|
|
1893
|
-
}
|
|
1894
|
-
getArtifacts(handlerClass) {
|
|
1895
|
-
const artifacts = [];
|
|
1896
|
-
for (const [routeType] of this.routeTypes) {
|
|
1897
|
-
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
1898
|
-
if ((0, _seedcord_core_internal.areRoutes)(meta)) artifacts.push({
|
|
1899
|
-
routeType,
|
|
1900
|
-
routes: meta
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
|
-
return artifacts;
|
|
1904
|
-
}
|
|
1905
|
-
async init() {
|
|
1906
|
-
if (this.isInitialized) return;
|
|
1907
|
-
this.isInitialized = true;
|
|
1908
|
-
const handlersDir = this.core.config.bot.interactions.path;
|
|
1909
|
-
if (!handlersDir) return;
|
|
1910
|
-
const middlewareDir = (0, _seedcord_utils.hasKeys)(this.core.config.bot.interactions, ["middlewares"]) ? this.core.config.bot.interactions.middlewares : void 0;
|
|
1911
|
-
this.loading = true;
|
|
1912
|
-
this.loadedHandlers.length = 0;
|
|
1913
|
-
this.loadedMiddlewares.length = 0;
|
|
1914
|
-
try {
|
|
1915
|
-
if (middlewareDir) await this.loadMiddlewares(middlewareDir);
|
|
1916
|
-
await this.loadHandlers(handlersDir);
|
|
1917
|
-
} finally {
|
|
1918
|
-
this.loading = false;
|
|
1919
|
-
}
|
|
1920
|
-
this.attachToClient();
|
|
1921
|
-
this.reportLoad();
|
|
1922
|
-
}
|
|
1923
|
-
reportLoad() {
|
|
1924
|
-
const { utils } = this.logger;
|
|
1925
|
-
utils.summary("Loaded", {
|
|
1926
|
-
"interaction handlers": this.loadedHandlers.length,
|
|
1927
|
-
middlewares: this.loadedMiddlewares.length
|
|
1928
|
-
}, "debug");
|
|
1929
|
-
if (this.loadedMiddlewares.length > 0) utils.block("Loaded middlewares", utils.entries(this.loadedMiddlewares), "debug");
|
|
1930
|
-
utils.block("Loaded interaction handlers", [...utils.entries(this.loadedHandlers), ...utils.counts({
|
|
1931
|
-
slash: this.slashMap.size,
|
|
1932
|
-
buttons: this.buttonMap.size,
|
|
1933
|
-
modals: this.modalMap.size,
|
|
1934
|
-
"string selects": this.stringSelectMap.size,
|
|
1935
|
-
"user selects": this.userSelectMap.size,
|
|
1936
|
-
"role selects": this.roleSelectMap.size,
|
|
1937
|
-
"channel selects": this.channelSelectMap.size,
|
|
1938
|
-
"mentionable selects": this.mentionableSelectMap.size,
|
|
1939
|
-
"message menus": this.messageContextMenuMap.size,
|
|
1940
|
-
"user menus": this.userContextMenuMap.size,
|
|
1941
|
-
autocomplete: this.autocompleteMap.size
|
|
1942
|
-
})], "debug");
|
|
1943
|
-
}
|
|
1944
|
-
async loadHandlers(dir) {
|
|
1945
|
-
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
1946
|
-
for (const val of Object.values(imported)) {
|
|
1947
|
-
if (!this.isHandlerClass(val)) continue;
|
|
1948
|
-
this.registerHandler(val, relativePath);
|
|
1949
|
-
this.hmrHandler?.trackHandler(fullPath, val);
|
|
1950
|
-
}
|
|
1951
|
-
});
|
|
1952
|
-
}
|
|
1953
|
-
async loadMiddlewares(dir) {
|
|
1954
|
-
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
1955
|
-
for (const val of Object.values(imported)) {
|
|
1956
|
-
if (!this.isMiddlewareClass(val)) continue;
|
|
1957
|
-
this.registerMiddleware(val, relativePath);
|
|
1958
|
-
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
1959
|
-
}
|
|
1960
|
-
});
|
|
1961
|
-
}
|
|
1962
|
-
registerMiddleware(middlewareCtor, relativePath) {
|
|
1963
|
-
const metadata = Reflect.getMetadata(_seedcord_core_internal.MiddlewareMetadataKey, middlewareCtor);
|
|
1964
|
-
if (metadata?.type !== "interaction") return;
|
|
1965
|
-
if (this.middlewares.some((entry) => entry.ctor === middlewareCtor)) return;
|
|
1966
|
-
if (this.middlewares.some((entry) => entry.ctor.name === middlewareCtor.name)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionDuplicateMiddleware, [middlewareCtor.name]);
|
|
1967
|
-
this.middlewares.push({
|
|
1968
|
-
ctor: middlewareCtor,
|
|
1969
|
-
priority: metadata.priority
|
|
1970
|
-
});
|
|
1971
|
-
this.middlewares.sort((a, b) => a.priority - b.priority);
|
|
1972
|
-
if (this.loading) this.loadedMiddlewares.push({
|
|
1973
|
-
name: `${middlewareCtor.name} (${metadata.priority})`,
|
|
1974
|
-
from: (0, _seedcord_utils.formatFilePath)(relativePath)
|
|
1975
|
-
});
|
|
1976
|
-
}
|
|
1977
|
-
isHandlerClass(obj) {
|
|
1978
|
-
if (typeof obj !== "function") return false;
|
|
1979
|
-
return obj.prototype instanceof InteractionHandler && Reflect.hasMetadata(_seedcord_core_internal.InteractionMetadataKey, obj) || obj.prototype instanceof AutocompleteHandler && Reflect.hasMetadata(_seedcord_core_internal.InteractionMetadataKey, obj);
|
|
1980
|
-
}
|
|
1981
|
-
isMiddlewareClass(obj) {
|
|
1982
|
-
if (typeof obj !== "function") return false;
|
|
1983
|
-
return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(_seedcord_core_internal.MiddlewareMetadataKey, obj);
|
|
1984
|
-
}
|
|
1985
|
-
registerHandler(handlerClass, relativePath) {
|
|
1986
|
-
const from = (0, _seedcord_utils.formatFilePath)(relativePath);
|
|
1987
|
-
const writes = [];
|
|
1988
|
-
for (const [routeType, map] of this.routeTypes) {
|
|
1989
|
-
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
1990
|
-
if (!(0, _seedcord_core_internal.areRoutes)(meta)) continue;
|
|
1991
|
-
for (const route of meta) {
|
|
1992
|
-
const existing = map.get(route);
|
|
1993
|
-
if (existing && existing !== handlerClass) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionDuplicateRoute, [
|
|
1994
|
-
`${routeType}:${route}`,
|
|
1995
|
-
`${existing.name} (${this.handlerFiles.get(existing) ?? "unknown file"})`,
|
|
1996
|
-
`${handlerClass.name} (${from})`
|
|
1997
|
-
]);
|
|
1998
|
-
writes.push([map, route]);
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
if (writes.length === 0) return;
|
|
2002
|
-
for (const [map, route] of writes) map.set(route, handlerClass);
|
|
2003
|
-
this.handlerFiles.set(handlerClass, from);
|
|
2004
|
-
if (this.loading) this.loadedHandlers.push({
|
|
2005
|
-
name: handlerClass.name,
|
|
2006
|
-
from
|
|
2007
|
-
});
|
|
2008
|
-
}
|
|
2009
|
-
async onHmr(event) {
|
|
2010
|
-
await this.hmrHandler?.handle(event);
|
|
2011
|
-
}
|
|
2012
|
-
unregisterHandler(handlerClass, artifacts) {
|
|
2013
|
-
this.handlerFiles.delete(handlerClass);
|
|
2014
|
-
if (artifacts) {
|
|
2015
|
-
for (const { routeType, routes } of artifacts) {
|
|
2016
|
-
const map = this.routeTypes.find(([type]) => type === routeType)?.[1];
|
|
2017
|
-
if (map) routes.forEach((route) => map.delete(route));
|
|
2018
|
-
}
|
|
2019
|
-
return;
|
|
2020
|
-
}
|
|
2021
|
-
for (const [routeType, map] of this.routeTypes) {
|
|
2022
|
-
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
2023
|
-
if (!(0, _seedcord_core_internal.areRoutes)(meta)) continue;
|
|
2024
|
-
meta.forEach((route) => map.delete(route));
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
unregisterMiddleware(middlewareCtor) {
|
|
2028
|
-
const index = this.middlewares.findIndex((entry) => entry.ctor === middlewareCtor);
|
|
2029
|
-
if (index !== -1) this.middlewares.splice(index, 1);
|
|
2030
|
-
}
|
|
2031
|
-
attachToClient() {
|
|
2032
|
-
this.core.bot.client.on(discord_js.Events.InteractionCreate, (interaction) => {
|
|
2033
|
-
if (this.draining) return;
|
|
2034
|
-
this.core.bus[_seedcord_core_internal.PublishDefault]("anyInteraction", { interaction });
|
|
2035
|
-
const run = this.handleInteraction(interaction).catch((caught) => {
|
|
2036
|
-
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
2037
|
-
this.logger.error(`[${_seedcord_logger.paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
2038
|
-
this.core.bus[_seedcord_core_internal.PublishDefault]("unhandledInteractionError", { error });
|
|
2039
|
-
});
|
|
2040
|
-
this.inFlight.add(run);
|
|
2041
|
-
run.finally(() => this.inFlight.delete(run));
|
|
2042
|
-
});
|
|
2043
|
-
}
|
|
2044
|
-
stopAccepting() {
|
|
2045
|
-
this.draining = true;
|
|
2046
|
-
}
|
|
2047
|
-
drain(timeoutMs) {
|
|
2048
|
-
return (0, _seedcord_core_node_internal.settleWithin)(Promise.allSettled(this.inFlight), timeoutMs);
|
|
2049
|
-
}
|
|
2050
|
-
async handleCustomIdInteraction(interaction, kind, getMap) {
|
|
2051
|
-
if ([...this.keysToIgnore].some((matcher) => matcher.owns(interaction.customId))) return;
|
|
2052
|
-
const prefix = (0, _seedcord_core_internal.prefixOf)(interaction.customId);
|
|
2053
|
-
if (!prefix) this.logger.warn(`${_seedcord_logger.paint.sky.bold(kind)} has invalid customId: ${_seedcord_logger.paint.mute(interaction.customId)}`);
|
|
2054
|
-
await this.processInteraction(interaction, kind, () => prefix, (key) => getMap().get(key));
|
|
2055
|
-
}
|
|
2056
|
-
async processInteraction(interaction, kind, extractKey, getHandler, fallback = UnhandledRepliable) {
|
|
2057
|
-
const key = extractKey(interaction);
|
|
2058
|
-
const startedAt = performance.now();
|
|
2059
|
-
const queuedMs = (0, _seedcord_core_internal.queuedMsFor)(interaction.id);
|
|
2060
|
-
const matched = getHandler(key);
|
|
2061
|
-
let routeId = `${kind}:${key || "unrouted"}`;
|
|
2062
|
-
let reported = false;
|
|
2063
|
-
const report = (outcome) => {
|
|
2064
|
-
if (reported) return;
|
|
2065
|
-
reported = true;
|
|
2066
|
-
(0, _seedcord_core_internal.reportDispatch)(this.core.bus, {
|
|
2067
|
-
routeId,
|
|
2068
|
-
interactionId: interaction.id,
|
|
2069
|
-
kind,
|
|
2070
|
-
outcome,
|
|
2071
|
-
startedAt,
|
|
2072
|
-
fallback: !matched,
|
|
2073
|
-
queuedMs
|
|
2074
|
-
});
|
|
2075
|
-
};
|
|
2076
|
-
let sender;
|
|
2077
|
-
try {
|
|
2078
|
-
if (!interaction.isAutocomplete()) await this.runMiddlewares(interaction);
|
|
2079
|
-
const HandlerCtor = matched ?? fallback;
|
|
2080
|
-
const dispatch = new _seedcord_core.DispatchContext((0, _seedcord_core_internal.routeIdOf)(HandlerCtor) ?? routeId);
|
|
2081
|
-
routeId = dispatch.routeId ?? routeId;
|
|
2082
|
-
const handler = this.buildHandler(HandlerCtor, interaction, dispatch, key, !matched);
|
|
2083
|
-
if (handler instanceof RepliableHandler) sender = handler.getSender();
|
|
2084
|
-
const refusal = interaction.isAutocomplete() ? null : await this.gateRefusal(HandlerCtor, interaction, dispatch.routeId);
|
|
2085
|
-
if (refusal) {
|
|
2086
|
-
await this.answer(refusal.caught, interaction, routeId, sender, report);
|
|
2087
|
-
return;
|
|
2088
|
-
}
|
|
2089
|
-
await handler.execute();
|
|
2090
|
-
report("handled");
|
|
2091
|
-
} catch (caught) {
|
|
2092
|
-
await this.answer(caught, interaction, routeId, sender, report);
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
async answer(caught, interaction, routeId, sender, report) {
|
|
2096
|
-
try {
|
|
2097
|
-
await handleInteractionFault(caught, interaction, this.core, routeId, sender);
|
|
2098
|
-
} finally {
|
|
2099
|
-
report((0, _seedcord_core_internal.outcomeFor)(caught));
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
buildHandler(HandlerCtor, interaction, dispatch, key, isFallback) {
|
|
2103
|
-
if (isFallback) this.logger.warn(`No handler found for key ${_seedcord_logger.paint.sky.bold(key)}. Falling back to ${HandlerCtor.name}.`);
|
|
2104
|
-
this.logger.debug(`Processing ${_seedcord_logger.paint.mint.bold(key)} with ${_seedcord_logger.paint.mute(HandlerCtor.name)}`);
|
|
2105
|
-
return new HandlerCtor(interaction, this.core, dispatch);
|
|
2106
|
-
}
|
|
2107
|
-
async runMiddlewares(interaction) {
|
|
2108
|
-
for (const { ctor } of this.middlewares) await new ctor(interaction, this.core).execute();
|
|
2109
|
-
}
|
|
2110
|
-
async gateRefusal(HandlerCtor, interaction, routeId) {
|
|
2111
|
-
const monitor = (0, _seedcord_core_internal.slowGateMonitor)();
|
|
2112
|
-
try {
|
|
2113
|
-
await (0, _seedcord_core_internal.runHandlerGates)(HandlerCtor, interactionGateContext(interaction, this.core), routeId ?? void 0, monitor?.observe);
|
|
2114
|
-
return null;
|
|
2115
|
-
} catch (caught) {
|
|
2116
|
-
return { caught };
|
|
2117
|
-
} finally {
|
|
2118
|
-
monitor?.report(routeId);
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
async handleInteraction(interaction) {
|
|
2122
|
-
switch (true) {
|
|
2123
|
-
case interaction.isChatInputCommand():
|
|
2124
|
-
await this.handleSlashCommand(interaction);
|
|
2125
|
-
break;
|
|
2126
|
-
case interaction.isButton():
|
|
2127
|
-
await this.handleButton(interaction);
|
|
2128
|
-
break;
|
|
2129
|
-
case interaction.isModalSubmit():
|
|
2130
|
-
await this.handleModal(interaction);
|
|
2131
|
-
break;
|
|
2132
|
-
case interaction.isStringSelectMenu():
|
|
2133
|
-
await this.handleStringSelectMenu(interaction);
|
|
2134
|
-
break;
|
|
2135
|
-
case interaction.isUserSelectMenu():
|
|
2136
|
-
await this.handleUserSelectMenu(interaction);
|
|
2137
|
-
break;
|
|
2138
|
-
case interaction.isRoleSelectMenu():
|
|
2139
|
-
await this.handleRoleSelectMenu(interaction);
|
|
2140
|
-
break;
|
|
2141
|
-
case interaction.isChannelSelectMenu():
|
|
2142
|
-
await this.handleChannelSelectMenu(interaction);
|
|
2143
|
-
break;
|
|
2144
|
-
case interaction.isMentionableSelectMenu():
|
|
2145
|
-
await this.handleMentionableSelectMenu(interaction);
|
|
2146
|
-
break;
|
|
2147
|
-
case interaction.isMessageContextMenuCommand():
|
|
2148
|
-
await this.handleMessageContextMenu(interaction);
|
|
2149
|
-
break;
|
|
2150
|
-
case interaction.isUserContextMenuCommand():
|
|
2151
|
-
await this.handleUserContextMenu(interaction);
|
|
2152
|
-
break;
|
|
2153
|
-
case interaction.isAutocomplete():
|
|
2154
|
-
await this.handleAutocomplete(interaction);
|
|
2155
|
-
break;
|
|
2156
|
-
default: this.logger.warn(`Unhandled interaction type: ${interaction.type}`);
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
async handleSlashCommand(interaction) {
|
|
2160
|
-
const route = slashRouteOf(interaction);
|
|
2161
|
-
await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Slash, () => route, (key) => this.slashMap.get(key));
|
|
2162
|
-
}
|
|
2163
|
-
async handleButton(interaction) {
|
|
2164
|
-
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Button, () => this.buttonMap);
|
|
2165
|
-
}
|
|
2166
|
-
async handleModal(interaction) {
|
|
2167
|
-
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Modal, () => this.modalMap);
|
|
2168
|
-
}
|
|
2169
|
-
async handleStringSelectMenu(interaction) {
|
|
2170
|
-
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.StringMenu, () => this.stringSelectMap);
|
|
2171
|
-
}
|
|
2172
|
-
async handleUserSelectMenu(interaction) {
|
|
2173
|
-
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.UserMenu, () => this.userSelectMap);
|
|
2174
|
-
}
|
|
2175
|
-
async handleRoleSelectMenu(interaction) {
|
|
2176
|
-
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.RoleMenu, () => this.roleSelectMap);
|
|
2177
|
-
}
|
|
2178
|
-
async handleChannelSelectMenu(interaction) {
|
|
2179
|
-
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.ChannelMenu, () => this.channelSelectMap);
|
|
2180
|
-
}
|
|
2181
|
-
async handleMentionableSelectMenu(interaction) {
|
|
2182
|
-
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.MentionableMenu, () => this.mentionableSelectMap);
|
|
2183
|
-
}
|
|
2184
|
-
async handleMessageContextMenu(interaction) {
|
|
2185
|
-
await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.MessageContextMenu, () => interaction.commandName, (key) => this.messageContextMenuMap.get(key));
|
|
2186
|
-
}
|
|
2187
|
-
async handleUserContextMenu(interaction) {
|
|
2188
|
-
await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.UserContextMenu, () => interaction.commandName, (key) => this.userContextMenuMap.get(key));
|
|
2189
|
-
}
|
|
2190
|
-
async handleAutocomplete(interaction) {
|
|
2191
|
-
const route = slashRouteOf(interaction);
|
|
2192
|
-
await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Autocomplete, () => route, (key) => this.autocompleteMap.get(key), UnhandledAutocomplete);
|
|
2193
|
-
}
|
|
2194
|
-
};
|
|
2195
|
-
|
|
2196
|
-
//#endregion
|
|
2197
|
-
//#region \0@oxc-project+runtime@0.142.0/helpers/esm/decorate.js
|
|
2198
|
-
function __decorate(decorators, target, key, desc) {
|
|
2199
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2200
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2201
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2202
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
//#endregion
|
|
2206
|
-
//#region src/bot/Bot.ts
|
|
2207
|
-
const DISPATCH_DRAIN_TIMEOUT_MS = 5e3;
|
|
2208
|
-
const DRAIN_TASK_TIMEOUT_MS = 6e3;
|
|
2209
|
-
const UNBIND_TIMEOUT_MS = 2e3;
|
|
2210
|
-
const LOGOUT_TIMEOUT_MS = 2e3;
|
|
2211
|
-
/**
|
|
2212
|
-
* The Discord client and its controllers. Access it through `core.bot`.
|
|
2213
|
-
*/
|
|
2214
|
-
var Bot = class {
|
|
2215
|
-
/** @internal */
|
|
2216
|
-
logger = new _seedcord_logger.Logger("Bot", { channel: "bot" });
|
|
2217
|
-
isInitialized = false;
|
|
2218
|
-
_client;
|
|
2219
|
-
interactions;
|
|
2220
|
-
events;
|
|
2221
|
-
emojiInjector;
|
|
2222
|
-
commandRegistry;
|
|
2223
|
-
/** @internal */
|
|
2224
|
-
async onHmr(event) {
|
|
2225
|
-
if (this.interactions) await this.interactions.onHmr(event);
|
|
2226
|
-
if (this.events) await this.events.onHmr(event);
|
|
2227
|
-
if (this.commandRegistry) await this.commandRegistry.onHmr(event);
|
|
2228
|
-
}
|
|
2229
|
-
/** @internal */
|
|
2230
|
-
constructor(core) {
|
|
2231
|
-
this._client = new discord_js.Client(core.config.bot.clientOptions);
|
|
2232
|
-
if (core.config.bot.interactions.path) this.interactions = new InteractionDispatcher(core);
|
|
2233
|
-
if (core.config.bot.events.path) this.events = new EventDispatcher(core);
|
|
2234
|
-
const commandsDir = core.config.bot.commands.path;
|
|
2235
|
-
if (commandsDir) {
|
|
2236
|
-
const injector = new _seedcord_core_internal.CommandInjector();
|
|
2237
|
-
const registry = new _seedcord_core_node_internal.CommandRegistry({
|
|
2238
|
-
dir: commandsDir,
|
|
2239
|
-
rest: this._client.rest,
|
|
2240
|
-
applicationId: () => this.applicationId(),
|
|
2241
|
-
onDeployed: (result) => {
|
|
2242
|
-
injector.inject(result, registry.allCommands());
|
|
2243
|
-
}
|
|
2244
|
-
});
|
|
2245
|
-
this.commandRegistry = registry;
|
|
2246
|
-
}
|
|
2247
|
-
this.emojiInjector = new EmojiInjector(core);
|
|
2248
|
-
this.registerShutdownTasks(core);
|
|
2249
|
-
}
|
|
2250
|
-
applicationId() {
|
|
2251
|
-
const { application } = this._client;
|
|
2252
|
-
if (!application) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreApplicationUnavailable);
|
|
2253
|
-
return application.id;
|
|
2254
|
-
}
|
|
2255
|
-
registerShutdownTasks(core) {
|
|
2256
|
-
core.shutdown.addTask(_seedcord_core_node_internal.ShutdownPhase.Unbind, "stop-dispatch", () => {
|
|
2257
|
-
this.stopAccepting();
|
|
2258
|
-
return Promise.resolve();
|
|
2259
|
-
}, UNBIND_TIMEOUT_MS);
|
|
2260
|
-
core.shutdown.addTask(_seedcord_core_node_internal.ShutdownPhase.Drain, "drain-dispatch", () => this.drain(), DRAIN_TASK_TIMEOUT_MS);
|
|
2261
|
-
core.shutdown.addTask(_seedcord_core_node_internal.ShutdownPhase.Logout, "stop-bot", async () => await this.stop(), LOGOUT_TIMEOUT_MS);
|
|
2262
|
-
}
|
|
2263
|
-
stopAccepting() {
|
|
2264
|
-
this.interactions?.stopAccepting();
|
|
2265
|
-
this.events?.stopAccepting();
|
|
2266
|
-
}
|
|
2267
|
-
async drain() {
|
|
2268
|
-
await Promise.allSettled([this.interactions?.drain(DISPATCH_DRAIN_TIMEOUT_MS), this.events?.drain(DISPATCH_DRAIN_TIMEOUT_MS)]);
|
|
2269
|
-
}
|
|
2270
|
-
/** @internal */
|
|
2271
|
-
async init() {
|
|
2272
|
-
if (this.isInitialized) return;
|
|
2273
|
-
this.isInitialized = true;
|
|
2274
|
-
const token = this.botToken;
|
|
2275
|
-
if (this.interactions) await this.interactions.init();
|
|
2276
|
-
if (this.events) await this.events.init();
|
|
2277
|
-
await this.login(token);
|
|
2278
|
-
await this.emojiInjector.init();
|
|
2279
|
-
if (this.commandRegistry) {
|
|
2280
|
-
await this.commandRegistry.init();
|
|
2281
|
-
await this.commandRegistry.setCommands();
|
|
2282
|
-
this.interactions?.warnUnhandledRoutes(this.commandRegistry.routeLeaves());
|
|
2283
|
-
this.interactions?.warnUnhandledContextMenuRoutes(this.commandRegistry.contextMenuLeaves());
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
/** @internal */
|
|
2287
|
-
async stop() {
|
|
2288
|
-
this._client.removeAllListeners();
|
|
2289
|
-
await this.logout();
|
|
2290
|
-
}
|
|
2291
|
-
async login(token) {
|
|
2292
|
-
const ready = Promise.withResolvers();
|
|
2293
|
-
this._client.once(discord_js.Events.ClientReady, () => ready.resolve());
|
|
2294
|
-
this._client.login(token);
|
|
2295
|
-
await ready.promise;
|
|
2296
|
-
this.logger.info(`Logged in as ${chalk.default.bold.magenta(this._client.user?.username)}!`);
|
|
2297
|
-
return this;
|
|
2298
|
-
}
|
|
2299
|
-
async logout() {
|
|
2300
|
-
await this._client.destroy();
|
|
2301
|
-
this.logger.info(chalk.default.bold.red("Logged out of Discord!"));
|
|
2302
|
-
}
|
|
2303
|
-
get client() {
|
|
2304
|
-
return this._client;
|
|
2305
|
-
}
|
|
2306
|
-
};
|
|
2307
|
-
__decorate([(0, envapt_legacy.Envapt)("DISCORD_BOT_TOKEN", { converter: (raw) => (0, _seedcord_errors_internal.validateDiscordToken)(raw) })], Bot.prototype, "botToken", void 0);
|
|
2308
|
-
|
|
2309
|
-
//#endregion
|
|
2310
|
-
//#region src/version.ts
|
|
2311
|
-
/** Package version */
|
|
2312
|
-
const version = "0.1.4";
|
|
2313
|
-
|
|
2314
|
-
//#endregion
|
|
2315
|
-
//#region src/Seedcord.ts
|
|
2316
|
-
/**
|
|
2317
|
-
* The gateway bot host. Opens a discord.js gateway session, discovers handlers, and runs
|
|
2318
|
-
* coordinated startup and shutdown. Attach plugins with `attach()`.
|
|
2319
|
-
*/
|
|
2320
|
-
var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
2321
|
-
config;
|
|
2322
|
-
/** @internal */
|
|
2323
|
-
[_seedcord_types_internal.SeedcordBrand] = true;
|
|
2324
|
-
/** @internal */
|
|
2325
|
-
augmentTarget = "@seedcord/gateway";
|
|
2326
|
-
/** @internal */
|
|
2327
|
-
version = version;
|
|
2328
|
-
healthCheck;
|
|
2329
|
-
hmrManager;
|
|
2330
|
-
/** @see {@link Bus} */
|
|
2331
|
-
bus;
|
|
2332
|
-
subscribers;
|
|
2333
|
-
/** @see {@link Bot} */
|
|
2334
|
-
bot;
|
|
2335
|
-
/** The discord.js client's REST client. Its token is set during the Login phase. */
|
|
2336
|
-
rest;
|
|
2337
|
-
/** @see {@link IRateLimiter} */
|
|
2338
|
-
rateLimiter;
|
|
2339
|
-
/**
|
|
2340
|
-
* @param config - Bot configuration including paths and Discord client options
|
|
2341
|
-
* @throws A **SeedcordError** When attempting to create multiple instances (singleton)
|
|
2342
|
-
*/
|
|
2343
|
-
constructor(config) {
|
|
2344
|
-
super(new _seedcord_core_node_internal.CoordinatedShutdown(), new _seedcord_core_node_internal.CoordinatedStartup());
|
|
2345
|
-
this.config = config;
|
|
2346
|
-
(0, _seedcord_logger_node.installNodeDefaults)(config.logger);
|
|
2347
|
-
(0, _seedcord_core_internal.setBotColor)(config.botColor);
|
|
2348
|
-
this.hmrManager = new _seedcord_core_internal.HmrManager();
|
|
2349
|
-
this.hmrManager.init();
|
|
2350
|
-
this.bus = new _seedcord_core.Bus(this);
|
|
2351
|
-
this.subscribers = new _seedcord_core_node_internal.SubscriberLoader(this.bus, config.subscribers.path);
|
|
2352
|
-
this.bot = new Bot(this);
|
|
2353
|
-
this.rest = this.bot.client.rest;
|
|
2354
|
-
this.rateLimiter = config.store ?? new _seedcord_rate_limiter.MemoryRateLimiter();
|
|
2355
|
-
this.healthCheck = _seedcord_core_node_internal.HealthCheck.fromOption(this.shutdown, config.healthCheck);
|
|
2356
|
-
this.registerStartupTasks();
|
|
2357
|
-
}
|
|
2358
|
-
/** The bot's discord username, populated after login. */
|
|
2359
|
-
get username() {
|
|
2360
|
-
return this.bot.client.user?.username;
|
|
2361
|
-
}
|
|
2362
|
-
static reset() {
|
|
2363
|
-
super.reset();
|
|
2364
|
-
_seedcord_logger.LoggerChannelRegistry.instance.configure({});
|
|
2365
|
-
}
|
|
2366
|
-
registerStartupTasks() {
|
|
2367
|
-
if (envapt.Envapter.isDevelopment || envapt.Envapter.isTest) this.registerHmrAwareModules();
|
|
2368
|
-
this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Configuration, "bus-initialization", async () => {
|
|
2369
|
-
this.bus.logger.utils.initialization("Subscribers", "start");
|
|
2370
|
-
await this.subscribers.init();
|
|
2371
|
-
this.bus.logger.utils.initialization("Subscribers", "end");
|
|
2372
|
-
});
|
|
2373
|
-
this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Login, "bot-initialization", async () => {
|
|
2374
|
-
this.bot.logger.utils.initialization("Bot", "start");
|
|
2375
|
-
await this.bot.init();
|
|
2376
|
-
this.bot.logger.utils.initialization("Bot", "end");
|
|
2377
|
-
});
|
|
2378
|
-
const { healthCheck } = this;
|
|
2379
|
-
if (healthCheck) this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Ready, "health-check", async () => {
|
|
2380
|
-
healthCheck.logger.utils.initialization("HealthCheck", "start");
|
|
2381
|
-
await healthCheck.init();
|
|
2382
|
-
healthCheck.logger.utils.initialization("HealthCheck", "end");
|
|
2383
|
-
});
|
|
2384
|
-
}
|
|
2385
|
-
/**
|
|
2386
|
-
* Starts the bot and runs all initialization tasks
|
|
2387
|
-
*
|
|
2388
|
-
* @returns This Seedcord instance when initialized
|
|
2389
|
-
*/
|
|
2390
|
-
async start() {
|
|
2391
|
-
try {
|
|
2392
|
-
await super.init();
|
|
2393
|
-
} catch (caught) {
|
|
2394
|
-
await this.shutdown.run(1, false);
|
|
2395
|
-
Seedcord.reset();
|
|
2396
|
-
throw caught;
|
|
2397
|
-
}
|
|
2398
|
-
return this;
|
|
2399
|
-
}
|
|
2400
|
-
registerHmrAwareModules() {
|
|
2401
|
-
this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Configuration, "hmr-registration", async () => {
|
|
2402
|
-
this.hmrManager.register(this.bot);
|
|
2403
|
-
this.hmrManager.register(this.subscribers);
|
|
2404
|
-
for (const plugin of this.plugins) this.hmrManager.register(plugin);
|
|
2405
|
-
await Promise.resolve();
|
|
2406
|
-
});
|
|
2407
|
-
}
|
|
2408
|
-
};
|
|
2409
|
-
|
|
2410
|
-
//#endregion
|
|
2411
|
-
exports.ArraySource = ArraySource;
|
|
2412
|
-
exports.AutocompleteHandler = AutocompleteHandler;
|
|
2413
|
-
exports.BaseHandler = BaseHandler;
|
|
2414
|
-
exports.ButtonHandler = ButtonHandler;
|
|
2415
|
-
exports.ComponentHandler = ComponentHandler;
|
|
2416
|
-
exports.ContextMenuHandler = ContextMenuHandler;
|
|
2417
|
-
exports.CursorSource = CursorSource;
|
|
2418
|
-
exports.Emojis = Emojis;
|
|
2419
|
-
exports.EventHandler = EventHandler;
|
|
2420
|
-
exports.EventMiddleware = EventMiddleware;
|
|
2421
|
-
exports.Gated = Gated;
|
|
2422
|
-
exports.IgnoreBots = IgnoreBots;
|
|
2423
|
-
exports.InteractionHandler = InteractionHandler;
|
|
2424
|
-
exports.InteractionMiddleware = InteractionMiddleware;
|
|
2425
|
-
exports.Middleware = Middleware;
|
|
2426
|
-
exports.MiddlewareType = MiddlewareType;
|
|
2427
|
-
exports.ModalHandler = ModalHandler;
|
|
2428
|
-
exports.Nsfw = Nsfw;
|
|
2429
|
-
exports.Paginator = Paginator;
|
|
2430
|
-
exports.Plugin = Plugin;
|
|
2431
|
-
exports.RegisterEvent = RegisterEvent;
|
|
2432
|
-
exports.RepliableHandler = RepliableHandler;
|
|
2433
|
-
exports.Seedcord = Seedcord;
|
|
2434
|
-
exports.SelectMenuHandler = SelectMenuHandler;
|
|
2435
|
-
exports.SlashHandler = SlashHandler;
|
|
2436
|
-
exports.Subscriber = Subscriber;
|
|
2437
|
-
exports.WebhookLog = WebhookLog;
|
|
2438
|
-
exports.checkBotPermissions = checkBotPermissions;
|
|
2439
|
-
exports.checkPermissions = checkPermissions;
|
|
2440
|
-
exports.getConfirmation = getConfirmation;
|
|
2441
|
-
exports.hasPermsToAssign = hasPermsToAssign;
|
|
2442
|
-
exports.version = version;
|
|
2443
|
-
Object.keys(_seedcord_core).forEach(function (k) {
|
|
2444
|
-
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2445
|
-
enumerable: true,
|
|
2446
|
-
get: function () { return _seedcord_core[k]; }
|
|
2447
|
-
});
|
|
2448
|
-
});
|
|
2449
|
-
|
|
2450
|
-
Object.keys(_seedcord_errors).forEach(function (k) {
|
|
2451
|
-
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2452
|
-
enumerable: true,
|
|
2453
|
-
get: function () { return _seedcord_errors[k]; }
|
|
2454
|
-
});
|
|
2455
|
-
});
|
|
2456
|
-
|
|
2457
|
-
var _seedcord_event_emitter = require("@seedcord/event-emitter");
|
|
2458
|
-
Object.keys(_seedcord_event_emitter).forEach(function (k) {
|
|
2459
|
-
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2460
|
-
enumerable: true,
|
|
2461
|
-
get: function () { return _seedcord_event_emitter[k]; }
|
|
2462
|
-
});
|
|
2463
|
-
});
|
|
2464
|
-
|
|
2465
|
-
Object.keys(_seedcord_logger).forEach(function (k) {
|
|
2466
|
-
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2467
|
-
enumerable: true,
|
|
2468
|
-
get: function () { return _seedcord_logger[k]; }
|
|
2469
|
-
});
|
|
2470
|
-
});
|
|
2471
|
-
|
|
2472
|
-
Object.keys(_seedcord_rate_limiter).forEach(function (k) {
|
|
2473
|
-
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2474
|
-
enumerable: true,
|
|
2475
|
-
get: function () { return _seedcord_rate_limiter[k]; }
|
|
2476
|
-
});
|
|
2477
|
-
});
|
|
2478
|
-
|
|
2479
|
-
Object.keys(_seedcord_utils).forEach(function (k) {
|
|
2480
|
-
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2481
|
-
enumerable: true,
|
|
2482
|
-
get: function () { return _seedcord_utils[k]; }
|
|
2483
|
-
});
|
|
2484
|
-
});
|
|
2485
|
-
|
|
2486
|
-
//# sourceMappingURL=index.cjs.map
|