seedcord 0.1.0-alpha.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/LICENSE +190 -0
- package/dist/index.cjs +2279 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +824 -0
- package/dist/index.d.mts +824 -0
- package/dist/index.d.ts +824 -0
- package/dist/index.mjs +2190 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +66 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2190 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import chalk6 from 'chalk';
|
|
3
|
+
import { ContextMenuCommandBuilder, SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandBuilder, ModalBuilder, RoleSelectMenuBuilder, MentionableSelectMenuBuilder, ChannelSelectMenuBuilder, UserSelectMenuBuilder, StringSelectMenuOptionBuilder, StringSelectMenuBuilder, ButtonBuilder, EmbedBuilder, SlashCommandBuilder, InteractionContextType, ActionRowBuilder, TextInputBuilder, MessageFlags, Events, Client, WebhookClient, DiscordAPIError, SnowflakeUtil, Message } from 'discord.js';
|
|
4
|
+
import { Envapter, Envapt } from 'envapt';
|
|
5
|
+
import { readdir } from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { format, transports, createLogger } from 'winston';
|
|
8
|
+
import * as crypto2 from 'crypto';
|
|
9
|
+
import { EventEmitter } from 'events';
|
|
10
|
+
import { createServer } from 'http';
|
|
11
|
+
import mongoose2 from 'mongoose';
|
|
12
|
+
|
|
13
|
+
var __defProp = Object.defineProperty;
|
|
14
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
16
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
17
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
18
|
+
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;
|
|
19
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
20
|
+
}
|
|
21
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
22
|
+
function _ts_metadata(k, v) {
|
|
23
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
24
|
+
}
|
|
25
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
26
|
+
var Globals = class extends Envapter {
|
|
27
|
+
static {
|
|
28
|
+
__name(this, "Globals");
|
|
29
|
+
}
|
|
30
|
+
static botToken;
|
|
31
|
+
// Healthcheck
|
|
32
|
+
static healthCheckPort;
|
|
33
|
+
static healthCheckPath;
|
|
34
|
+
// Coordinated Shutdown
|
|
35
|
+
static shutdownIsEnabled;
|
|
36
|
+
// Unknown Exception Webhook URL
|
|
37
|
+
static unknownExceptionWebhookUrl;
|
|
38
|
+
// Variables
|
|
39
|
+
/**
|
|
40
|
+
* The default color of the bot's embeds. Can simply override by `Globals.botColor =`
|
|
41
|
+
*/
|
|
42
|
+
static botColor = this.isProduction ? "#fe565a" : "#3fa045";
|
|
43
|
+
};
|
|
44
|
+
_ts_decorate([
|
|
45
|
+
Envapt("DISCORD_BOT_TOKEN", {
|
|
46
|
+
fallback: void 0
|
|
47
|
+
}),
|
|
48
|
+
_ts_metadata("design:type", String)
|
|
49
|
+
], Globals, "botToken", void 0);
|
|
50
|
+
_ts_decorate([
|
|
51
|
+
Envapt("HEALTH_CHECK_PORT", {
|
|
52
|
+
fallback: 6956
|
|
53
|
+
}),
|
|
54
|
+
_ts_metadata("design:type", Number)
|
|
55
|
+
], Globals, "healthCheckPort", void 0);
|
|
56
|
+
_ts_decorate([
|
|
57
|
+
Envapt("HEALTH_CHECK_PATH", {
|
|
58
|
+
fallback: "/healthcheck"
|
|
59
|
+
}),
|
|
60
|
+
_ts_metadata("design:type", String)
|
|
61
|
+
], Globals, "healthCheckPath", void 0);
|
|
62
|
+
_ts_decorate([
|
|
63
|
+
Envapt("SHUTDOWN_IS_ENABLED", {
|
|
64
|
+
fallback: false
|
|
65
|
+
}),
|
|
66
|
+
_ts_metadata("design:type", Boolean)
|
|
67
|
+
], Globals, "shutdownIsEnabled", void 0);
|
|
68
|
+
_ts_decorate([
|
|
69
|
+
Envapt("UNKNOWN_EXCEPTION_WEBHOOK_URL", {
|
|
70
|
+
fallback: void 0
|
|
71
|
+
}),
|
|
72
|
+
_ts_metadata("design:type", String)
|
|
73
|
+
], Globals, "unknownExceptionWebhookUrl", void 0);
|
|
74
|
+
|
|
75
|
+
// src/interfaces/Components.ts
|
|
76
|
+
var BuilderTypes = {
|
|
77
|
+
command: SlashCommandBuilder,
|
|
78
|
+
embed: EmbedBuilder,
|
|
79
|
+
button: ButtonBuilder,
|
|
80
|
+
menu_string: StringSelectMenuBuilder,
|
|
81
|
+
menu_option_string: StringSelectMenuOptionBuilder,
|
|
82
|
+
menu_user: UserSelectMenuBuilder,
|
|
83
|
+
menu_channel: ChannelSelectMenuBuilder,
|
|
84
|
+
menu_mentionable: MentionableSelectMenuBuilder,
|
|
85
|
+
menu_role: RoleSelectMenuBuilder,
|
|
86
|
+
modal: ModalBuilder,
|
|
87
|
+
subcommand: SlashCommandSubcommandBuilder,
|
|
88
|
+
group: SlashCommandSubcommandGroupBuilder,
|
|
89
|
+
context_menu: ContextMenuCommandBuilder
|
|
90
|
+
};
|
|
91
|
+
var RowTypes = {
|
|
92
|
+
button: ActionRowBuilder,
|
|
93
|
+
menu_string: ActionRowBuilder,
|
|
94
|
+
menu_user: ActionRowBuilder,
|
|
95
|
+
menu_channel: ActionRowBuilder,
|
|
96
|
+
menu_mentionable: ActionRowBuilder,
|
|
97
|
+
menu_role: ActionRowBuilder,
|
|
98
|
+
modal: ActionRowBuilder
|
|
99
|
+
};
|
|
100
|
+
var ModalTypes = {
|
|
101
|
+
text: TextInputBuilder
|
|
102
|
+
};
|
|
103
|
+
var BaseComponent = class BaseComponent2 {
|
|
104
|
+
static {
|
|
105
|
+
__name(this, "BaseComponent");
|
|
106
|
+
}
|
|
107
|
+
_component;
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
109
|
+
constructor(ComponentClass) {
|
|
110
|
+
this._component = new ComponentClass();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* @description Returns the instantiated component
|
|
114
|
+
* @note Use this for configuring the component using its instance setters.
|
|
115
|
+
* @usage `this.instance.someMethod()`
|
|
116
|
+
*/
|
|
117
|
+
get instance() {
|
|
118
|
+
return this._component;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var BuilderComponent = class extends BaseComponent {
|
|
122
|
+
static {
|
|
123
|
+
__name(this, "BuilderComponent");
|
|
124
|
+
}
|
|
125
|
+
constructor(type) {
|
|
126
|
+
const ComponentClass = BuilderTypes[type];
|
|
127
|
+
super(ComponentClass);
|
|
128
|
+
if (this.instance instanceof EmbedBuilder) this.instance.setColor(Globals.botColor);
|
|
129
|
+
if (this.instance instanceof SlashCommandBuilder || this.instance instanceof ContextMenuCommandBuilder) {
|
|
130
|
+
this.instance.setContexts(InteractionContextType.Guild);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
get component() {
|
|
134
|
+
return this.instance;
|
|
135
|
+
}
|
|
136
|
+
buildCustomId(prefix, ...args) {
|
|
137
|
+
if (args.length === 0) return prefix;
|
|
138
|
+
return `${prefix}:${args.join("-")}`;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var RowComponent = class extends BaseComponent {
|
|
142
|
+
static {
|
|
143
|
+
__name(this, "RowComponent");
|
|
144
|
+
}
|
|
145
|
+
constructor(type) {
|
|
146
|
+
const ComponentClass = RowTypes[type];
|
|
147
|
+
super(ComponentClass);
|
|
148
|
+
}
|
|
149
|
+
get component() {
|
|
150
|
+
return this.instance;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
var ModalRow = class ModalRow2 extends RowComponent {
|
|
154
|
+
static {
|
|
155
|
+
__name(this, "ModalRow");
|
|
156
|
+
}
|
|
157
|
+
constructor(component) {
|
|
158
|
+
super("modal");
|
|
159
|
+
this.instance.addComponents(component);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
var ModalComponent = class extends BaseComponent {
|
|
163
|
+
static {
|
|
164
|
+
__name(this, "ModalComponent");
|
|
165
|
+
}
|
|
166
|
+
constructor(type) {
|
|
167
|
+
const ComponentClass = ModalTypes[type];
|
|
168
|
+
super(ComponentClass);
|
|
169
|
+
}
|
|
170
|
+
get component() {
|
|
171
|
+
return new ModalRow(this.instance).component;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
var BaseErrorEmbed = class extends BuilderComponent {
|
|
175
|
+
static {
|
|
176
|
+
__name(this, "BaseErrorEmbed");
|
|
177
|
+
}
|
|
178
|
+
constructor() {
|
|
179
|
+
super("embed");
|
|
180
|
+
this.instance.setTitle("Cannot Proceed");
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var CustomError = class extends Error {
|
|
184
|
+
static {
|
|
185
|
+
__name(this, "CustomError");
|
|
186
|
+
}
|
|
187
|
+
message;
|
|
188
|
+
_emit = false;
|
|
189
|
+
response = new BaseErrorEmbed().component;
|
|
190
|
+
constructor(message) {
|
|
191
|
+
super(message), this.message = message;
|
|
192
|
+
Error.captureStackTrace(this, this.constructor);
|
|
193
|
+
}
|
|
194
|
+
get emit() {
|
|
195
|
+
return this._emit;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/bot/errors/Database.ts
|
|
200
|
+
var DatabaseConnectionFailure = class extends CustomError {
|
|
201
|
+
static {
|
|
202
|
+
__name(this, "DatabaseConnectionFailure");
|
|
203
|
+
}
|
|
204
|
+
constructor(message = "Failed to connect to the database.") {
|
|
205
|
+
super(message);
|
|
206
|
+
this.response.setDescription("Failed to connect to the database.");
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
var DatabaseError = class extends CustomError {
|
|
210
|
+
static {
|
|
211
|
+
__name(this, "DatabaseError");
|
|
212
|
+
}
|
|
213
|
+
uuid;
|
|
214
|
+
_emit = true;
|
|
215
|
+
constructor(message, uuid) {
|
|
216
|
+
super(message), this.uuid = uuid;
|
|
217
|
+
this.name = "DatabaseError";
|
|
218
|
+
this.response.setTitle("Database Error").setDescription(`An error occurred while interacting with the database.
|
|
219
|
+
### UUID: \`${this.uuid}\``);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
var Logger = class _Logger {
|
|
223
|
+
static {
|
|
224
|
+
__name(this, "Logger");
|
|
225
|
+
}
|
|
226
|
+
static instances = /* @__PURE__ */ new Map();
|
|
227
|
+
static instance(prefix) {
|
|
228
|
+
let instance = this.instances.get(prefix);
|
|
229
|
+
if (!instance) {
|
|
230
|
+
instance = new _Logger(prefix);
|
|
231
|
+
this.instances.set(prefix, instance);
|
|
232
|
+
}
|
|
233
|
+
return instance;
|
|
234
|
+
}
|
|
235
|
+
constructor(transportName) {
|
|
236
|
+
const consoleTransport = this.createConsoleTransport(transportName);
|
|
237
|
+
this.initializeLogger(consoleTransport);
|
|
238
|
+
}
|
|
239
|
+
getFormatCustomizations() {
|
|
240
|
+
const padding = 7;
|
|
241
|
+
return [
|
|
242
|
+
format.errors({
|
|
243
|
+
stack: true
|
|
244
|
+
}),
|
|
245
|
+
format.splat(),
|
|
246
|
+
format.colorize({
|
|
247
|
+
level: true
|
|
248
|
+
}),
|
|
249
|
+
format.timestamp({
|
|
250
|
+
format: "D MMM, hh:mm:ss a"
|
|
251
|
+
}),
|
|
252
|
+
format.printf((info) => {
|
|
253
|
+
const ts = String(info.timestamp ?? "");
|
|
254
|
+
const lvl = String(info.level).padEnd(padding);
|
|
255
|
+
const lbl = String(info.label ?? "");
|
|
256
|
+
const msg = String(info.message ?? "");
|
|
257
|
+
const base = `${ts} [${lvl}]: ${lbl} - ${msg}`;
|
|
258
|
+
const splatSym = Symbol.for("splat");
|
|
259
|
+
const raw = info[splatSym];
|
|
260
|
+
const extras = Array.isArray(raw) ? raw : [];
|
|
261
|
+
const cleaned = extras.filter((x) => !(x instanceof Error)).filter((x) => {
|
|
262
|
+
if (!x) return false;
|
|
263
|
+
if (typeof x !== "object") return true;
|
|
264
|
+
return Object.keys(x).length > 0;
|
|
265
|
+
});
|
|
266
|
+
let rendered = base;
|
|
267
|
+
if (typeof info.stack === "string") {
|
|
268
|
+
rendered += `
|
|
269
|
+
${String(info.stack)}`;
|
|
270
|
+
}
|
|
271
|
+
if (cleaned.length) {
|
|
272
|
+
const parts = [];
|
|
273
|
+
for (const x of cleaned) {
|
|
274
|
+
if (typeof x === "string") parts.push(x);
|
|
275
|
+
else {
|
|
276
|
+
try {
|
|
277
|
+
parts.push(JSON.stringify(x, null, 2));
|
|
278
|
+
} catch {
|
|
279
|
+
parts.push(String(x));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
rendered += `
|
|
284
|
+
${parts.join(" ")}`;
|
|
285
|
+
}
|
|
286
|
+
return rendered;
|
|
287
|
+
})
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
createConsoleTransport(transportName) {
|
|
291
|
+
return new transports.Console({
|
|
292
|
+
format: format.combine(format.label({
|
|
293
|
+
label: transportName
|
|
294
|
+
}), ...this.getFormatCustomizations()),
|
|
295
|
+
level: Globals.isDevelopment ? "silly" : Globals.isStaging ? "debug" : "info"
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
initializeLogger(consoleTransport) {
|
|
299
|
+
const transportsArray = [
|
|
300
|
+
consoleTransport
|
|
301
|
+
];
|
|
302
|
+
if (Globals.isDevelopment) {
|
|
303
|
+
const maxSizeInMB = 10;
|
|
304
|
+
transportsArray.push(new transports.File({
|
|
305
|
+
filename: "logs/application.log",
|
|
306
|
+
level: "debug",
|
|
307
|
+
format: format.combine(format.uncolorize(), format.errors({
|
|
308
|
+
stack: true
|
|
309
|
+
}), format.timestamp(), format.json({
|
|
310
|
+
bigint: true,
|
|
311
|
+
space: 2
|
|
312
|
+
})),
|
|
313
|
+
maxsize: maxSizeInMB * 1024 * 1024,
|
|
314
|
+
maxFiles: 5,
|
|
315
|
+
tailable: true
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
this.logger = createLogger({
|
|
319
|
+
transports: transportsArray
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
error(msg, ...args) {
|
|
323
|
+
this.logger.error(msg, ...args);
|
|
324
|
+
}
|
|
325
|
+
warn(msg, ...args) {
|
|
326
|
+
this.logger.warn(msg, ...args);
|
|
327
|
+
}
|
|
328
|
+
info(msg, ...args) {
|
|
329
|
+
this.logger.info(msg, ...args);
|
|
330
|
+
}
|
|
331
|
+
http(msg, ...args) {
|
|
332
|
+
this.logger.http(msg, ...args);
|
|
333
|
+
}
|
|
334
|
+
verbose(msg, ...args) {
|
|
335
|
+
this.logger.verbose(msg, ...args);
|
|
336
|
+
}
|
|
337
|
+
debug(msg, ...args) {
|
|
338
|
+
this.logger.debug(msg, ...args);
|
|
339
|
+
}
|
|
340
|
+
silly(msg, ...args) {
|
|
341
|
+
this.logger.silly(msg, ...args);
|
|
342
|
+
}
|
|
343
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
344
|
+
static Error(prefix, msg, ...args) {
|
|
345
|
+
const logger = this.instance(prefix);
|
|
346
|
+
logger.error(msg, ...args);
|
|
347
|
+
}
|
|
348
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
349
|
+
static Info(prefix, msg, ...args) {
|
|
350
|
+
const logger = this.instance(prefix);
|
|
351
|
+
logger.info(msg, ...args);
|
|
352
|
+
}
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
354
|
+
static Warn(prefix, msg, ...args) {
|
|
355
|
+
const logger = this.instance(prefix);
|
|
356
|
+
logger.warn(msg, ...args);
|
|
357
|
+
}
|
|
358
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
359
|
+
static Debug(prefix, msg, ...args) {
|
|
360
|
+
const logger = this.instance(prefix);
|
|
361
|
+
logger.debug(msg, ...args);
|
|
362
|
+
}
|
|
363
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
364
|
+
static Silly(prefix, msg, ...args) {
|
|
365
|
+
const logger = this.instance(prefix);
|
|
366
|
+
logger.silly(msg, ...args);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/library/Helpers.ts
|
|
371
|
+
function numberFixer(num, decimalPlaces) {
|
|
372
|
+
const factor = Math.pow(10, decimalPlaces);
|
|
373
|
+
return Math.round((num + Number.EPSILON) * factor) / factor;
|
|
374
|
+
}
|
|
375
|
+
__name(numberFixer, "numberFixer");
|
|
376
|
+
function percentage(num1, num2) {
|
|
377
|
+
return Number((num1 / num2 * 100).toFixed(2));
|
|
378
|
+
}
|
|
379
|
+
__name(percentage, "percentage");
|
|
380
|
+
function generateAsciiTable(data) {
|
|
381
|
+
if (data.length === 0) return "";
|
|
382
|
+
const firstRow = data[0];
|
|
383
|
+
if (!firstRow || firstRow.length === 0) return "";
|
|
384
|
+
let table = "";
|
|
385
|
+
const columnWidths = [];
|
|
386
|
+
for (let i = 0; i < firstRow.length; i++) {
|
|
387
|
+
let maxWidth = 0;
|
|
388
|
+
for (const row of data) {
|
|
389
|
+
const cell = row[i];
|
|
390
|
+
if (cell !== void 0) maxWidth = Math.max(maxWidth, cell.length);
|
|
391
|
+
}
|
|
392
|
+
columnWidths.push(maxWidth);
|
|
393
|
+
}
|
|
394
|
+
const createLine = /* @__PURE__ */ __name((char, left, intersect, right) => {
|
|
395
|
+
let line = left;
|
|
396
|
+
columnWidths.forEach((width, index) => {
|
|
397
|
+
line += char.repeat(width + 2);
|
|
398
|
+
if (index < columnWidths.length - 1) line += intersect;
|
|
399
|
+
else line += right;
|
|
400
|
+
});
|
|
401
|
+
line += "\n";
|
|
402
|
+
return line;
|
|
403
|
+
}, "createLine");
|
|
404
|
+
table += createLine("\u2550", "\u2554", "\u2566", "\u2557");
|
|
405
|
+
data.forEach((row, rowIndex) => {
|
|
406
|
+
table += "\u2551";
|
|
407
|
+
row.forEach((cell, columnIndex) => {
|
|
408
|
+
const columnWidth = columnWidths[columnIndex];
|
|
409
|
+
if (columnWidth !== void 0) table += ` ${cell.padEnd(columnWidth)} \u2551`;
|
|
410
|
+
});
|
|
411
|
+
table += "\n";
|
|
412
|
+
if (rowIndex < data.length - 1) table += createLine("\u2500", "\u2560", "\u256C", "\u2563");
|
|
413
|
+
else table += createLine("\u2550", "\u255A", "\u2569", "\u255D");
|
|
414
|
+
});
|
|
415
|
+
return table;
|
|
416
|
+
}
|
|
417
|
+
__name(generateAsciiTable, "generateAsciiTable");
|
|
418
|
+
function formatWord(word) {
|
|
419
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
420
|
+
}
|
|
421
|
+
__name(formatWord, "formatWord");
|
|
422
|
+
function longestStringLength(arr) {
|
|
423
|
+
return Math.max(...arr.map((el) => el.toString().length));
|
|
424
|
+
}
|
|
425
|
+
__name(longestStringLength, "longestStringLength");
|
|
426
|
+
function currentTime() {
|
|
427
|
+
return Math.floor(Date.now() / 1e3);
|
|
428
|
+
}
|
|
429
|
+
__name(currentTime, "currentTime");
|
|
430
|
+
function ordinal(n) {
|
|
431
|
+
const s = [
|
|
432
|
+
"th",
|
|
433
|
+
"st",
|
|
434
|
+
"nd",
|
|
435
|
+
"rd"
|
|
436
|
+
];
|
|
437
|
+
const v = n % 100;
|
|
438
|
+
const index = (v - 20) % 10;
|
|
439
|
+
const suffix = s[index] ?? s[v] ?? s[0];
|
|
440
|
+
if (!suffix) return `${n}th`;
|
|
441
|
+
return `${n}${suffix}`;
|
|
442
|
+
}
|
|
443
|
+
__name(ordinal, "ordinal");
|
|
444
|
+
function prettyDifference(numBefore, numAfter) {
|
|
445
|
+
return (numAfter - numBefore > 0 ? `+${numAfter - numBefore}` : numAfter - numBefore).toString();
|
|
446
|
+
}
|
|
447
|
+
__name(prettyDifference, "prettyDifference");
|
|
448
|
+
function isTsOrJsFile(entry) {
|
|
449
|
+
return entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js")) && !entry.name.endsWith(".d.ts") && !entry.name.endsWith(".map");
|
|
450
|
+
}
|
|
451
|
+
__name(isTsOrJsFile, "isTsOrJsFile");
|
|
452
|
+
async function traverseDirectory(dir, callback) {
|
|
453
|
+
let entries;
|
|
454
|
+
try {
|
|
455
|
+
entries = await readdir(dir, {
|
|
456
|
+
withFileTypes: true
|
|
457
|
+
});
|
|
458
|
+
} catch {
|
|
459
|
+
Logger.Error("Failed to read directory", dir);
|
|
460
|
+
entries = [];
|
|
461
|
+
}
|
|
462
|
+
for (const entry of entries) {
|
|
463
|
+
const fullPath = path.join(dir, entry.name);
|
|
464
|
+
const relativePath = path.relative(process.cwd(), fullPath);
|
|
465
|
+
if (entry.isDirectory()) {
|
|
466
|
+
await traverseDirectory(fullPath, callback);
|
|
467
|
+
} else if (isTsOrJsFile(entry)) {
|
|
468
|
+
const imported = await import(fullPath);
|
|
469
|
+
await callback(fullPath, relativePath, imported);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
__name(traverseDirectory, "traverseDirectory");
|
|
474
|
+
function throwCustomError(error, message, customError) {
|
|
475
|
+
const uuid = crypto.randomUUID();
|
|
476
|
+
Logger.Error("Throwing Custom Error", error.name);
|
|
477
|
+
if (typeof customError === typeof DatabaseError) {
|
|
478
|
+
const errorMessage = error instanceof Error ? error.message : message;
|
|
479
|
+
throw new customError(errorMessage, uuid);
|
|
480
|
+
} else {
|
|
481
|
+
if (error instanceof Error) {
|
|
482
|
+
throw new customError(`${message}: ${error.message ? error.message : error.toString()}`);
|
|
483
|
+
} else {
|
|
484
|
+
throw new customError(message);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
__name(throwCustomError, "throwCustomError");
|
|
489
|
+
function prettify(key) {
|
|
490
|
+
return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").trim();
|
|
491
|
+
}
|
|
492
|
+
__name(prettify, "prettify");
|
|
493
|
+
function fyShuffle(items) {
|
|
494
|
+
const array = items.slice();
|
|
495
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
496
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
497
|
+
[array[i], array[j]] = [
|
|
498
|
+
array[j],
|
|
499
|
+
array[i]
|
|
500
|
+
];
|
|
501
|
+
}
|
|
502
|
+
return array;
|
|
503
|
+
}
|
|
504
|
+
__name(fyShuffle, "fyShuffle");
|
|
505
|
+
|
|
506
|
+
// src/bot/decorators/CommandRegisterable.ts
|
|
507
|
+
var CommandMetadataKey = Symbol("command:metadata");
|
|
508
|
+
function RegisterCommand(scope, guilds = []) {
|
|
509
|
+
return (ctor) => {
|
|
510
|
+
const meta = scope === "global" ? {
|
|
511
|
+
scope
|
|
512
|
+
} : {
|
|
513
|
+
scope,
|
|
514
|
+
guilds
|
|
515
|
+
};
|
|
516
|
+
Reflect.defineMetadata(CommandMetadataKey, meta, ctor);
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
__name(RegisterCommand, "RegisterCommand");
|
|
520
|
+
|
|
521
|
+
// src/bot/controllers/CommandRegistry.ts
|
|
522
|
+
var CommandRegistry = class {
|
|
523
|
+
static {
|
|
524
|
+
__name(this, "CommandRegistry");
|
|
525
|
+
}
|
|
526
|
+
core;
|
|
527
|
+
logger = new Logger("Commands");
|
|
528
|
+
isInitialised = false;
|
|
529
|
+
globalCommands = [];
|
|
530
|
+
guildCommands = /* @__PURE__ */ new Map();
|
|
531
|
+
constructor(core) {
|
|
532
|
+
this.core = core;
|
|
533
|
+
}
|
|
534
|
+
async init() {
|
|
535
|
+
if (this.isInitialised) return;
|
|
536
|
+
this.isInitialised = true;
|
|
537
|
+
this.logger.info(chalk6.bold(this.core.config.bot.commands.path));
|
|
538
|
+
await this.loadCommands(this.core.config.bot.commands.path);
|
|
539
|
+
this.logger.info(`${chalk6.bold.green("Loaded")}: ${chalk6.magenta.bold(this.globalCommands.length)} global, ${chalk6.magenta.bold(this.guildCommands.size)} guild groups`);
|
|
540
|
+
}
|
|
541
|
+
async loadCommands(dir) {
|
|
542
|
+
await traverseDirectory(dir, (_full, rel, mod) => {
|
|
543
|
+
for (const exported of Object.values(mod)) if (this.isCommandClass(exported)) this.registerCommand(exported, rel);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
isCommandClass(obj) {
|
|
547
|
+
if (typeof obj !== "function") return false;
|
|
548
|
+
return obj.prototype instanceof BuilderComponent && Reflect.hasMetadata(CommandMetadataKey, obj);
|
|
549
|
+
}
|
|
550
|
+
registerCommand(ctor, rel) {
|
|
551
|
+
const meta = Reflect.getMetadata(CommandMetadataKey, ctor);
|
|
552
|
+
if (!meta) return;
|
|
553
|
+
const instance = new ctor();
|
|
554
|
+
const comp = instance.component;
|
|
555
|
+
const commandType = comp instanceof SlashCommandBuilder ? "slash command" : "context menu command";
|
|
556
|
+
if (meta.scope === "global") {
|
|
557
|
+
this.globalCommands.push(comp);
|
|
558
|
+
this.logger.info(`${chalk6.italic("Registered")} ${chalk6.bold.yellow(ctor.name)} from ${chalk6.gray(rel)}`);
|
|
559
|
+
this.logger.info(` \u2192 Global ${commandType}: ${chalk6.bold.cyan(comp.name)}`);
|
|
560
|
+
} else {
|
|
561
|
+
for (const g of meta.guilds) {
|
|
562
|
+
const arr = this.guildCommands.get(g) ?? [];
|
|
563
|
+
arr.push(comp);
|
|
564
|
+
this.guildCommands.set(g, arr);
|
|
565
|
+
}
|
|
566
|
+
this.logger.info(`${chalk6.italic("Registered")} ${chalk6.bold.yellow(ctor.name)} from ${chalk6.gray(rel)}`);
|
|
567
|
+
this.logger.info(` \u2192 Guild ${commandType}: ${chalk6.bold.cyan(comp.name)} for ${chalk6.magenta.bold(meta.guilds.length)} guild(s)`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
async setCommands() {
|
|
571
|
+
if (this.globalCommands.length > 0) {
|
|
572
|
+
await this.core.bot.client.application?.commands.set(this.globalCommands);
|
|
573
|
+
const tag = this.globalCommands.length === 1 ? "command" : "commands";
|
|
574
|
+
this.logger.info(`${chalk6.bold.green("Configured")} ${chalk6.magenta.bold(this.globalCommands.length)} global ${tag}`);
|
|
575
|
+
this.logger.info(` \u2192 ${this.globalCommands.map((command) => chalk6.bold.cyan(command.name)).join(", ")}`);
|
|
576
|
+
}
|
|
577
|
+
for (const [guildId, commands] of this.guildCommands.entries()) {
|
|
578
|
+
const guild = this.core.bot.client.guilds.cache.get(guildId);
|
|
579
|
+
if (!guild) {
|
|
580
|
+
this.logger.warn(`Guild with ID ${guildId} not found, skipping command registration.`);
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
await guild.commands.set(commands);
|
|
584
|
+
const tag = commands.length === 1 ? "command" : "commands";
|
|
585
|
+
this.logger.info(`${chalk6.bold.green("Configured")} ${chalk6.magenta.bold(commands.length)} ${tag} for guild ${chalk6.bold.yellow(guild.name)}`);
|
|
586
|
+
this.logger.info(` \u2192 ${commands.map((command) => chalk6.bold.cyan(command.name)).join(", ")}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
// src/interfaces/Handler.ts
|
|
592
|
+
var BaseHandler = class BaseHandler2 {
|
|
593
|
+
static {
|
|
594
|
+
__name(this, "BaseHandler");
|
|
595
|
+
}
|
|
596
|
+
core;
|
|
597
|
+
checkable = false;
|
|
598
|
+
break = false;
|
|
599
|
+
errored = false;
|
|
600
|
+
event;
|
|
601
|
+
args = [];
|
|
602
|
+
constructor(event, core, args) {
|
|
603
|
+
this.core = core;
|
|
604
|
+
this.event = event;
|
|
605
|
+
this.args = args ?? [];
|
|
606
|
+
}
|
|
607
|
+
hasChecks() {
|
|
608
|
+
return this.checkable;
|
|
609
|
+
}
|
|
610
|
+
hasErrors() {
|
|
611
|
+
return this.errored;
|
|
612
|
+
}
|
|
613
|
+
setErrored() {
|
|
614
|
+
this.errored = true;
|
|
615
|
+
}
|
|
616
|
+
shouldBreak() {
|
|
617
|
+
return this.break;
|
|
618
|
+
}
|
|
619
|
+
getEvent() {
|
|
620
|
+
return this.event;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Get the arguments passed from the customId
|
|
624
|
+
* For example, if customId is "accept:user123-guild456", args will be ["user123", "guild456"]
|
|
625
|
+
*/
|
|
626
|
+
getArgs() {
|
|
627
|
+
return this.args;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Get a specific argument by index
|
|
631
|
+
*/
|
|
632
|
+
getArg(index) {
|
|
633
|
+
return this.args[index];
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
var InteractionHandler = class extends BaseHandler {
|
|
637
|
+
static {
|
|
638
|
+
__name(this, "InteractionHandler");
|
|
639
|
+
}
|
|
640
|
+
constructor(event, core, args) {
|
|
641
|
+
super(event, core, args);
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
var InteractionMiddleware = class extends BaseHandler {
|
|
645
|
+
static {
|
|
646
|
+
__name(this, "InteractionMiddleware");
|
|
647
|
+
}
|
|
648
|
+
constructor(event, core, args) {
|
|
649
|
+
super(event, core, args);
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
var AutocompleteHandler = class extends BaseHandler {
|
|
653
|
+
static {
|
|
654
|
+
__name(this, "AutocompleteHandler");
|
|
655
|
+
}
|
|
656
|
+
focused;
|
|
657
|
+
constructor(event, core, args) {
|
|
658
|
+
super(event, core, args);
|
|
659
|
+
this.focused = this.event.options.getFocused(true);
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
var EventHandler = class extends BaseHandler {
|
|
663
|
+
static {
|
|
664
|
+
__name(this, "EventHandler");
|
|
665
|
+
}
|
|
666
|
+
constructor(event, core, args) {
|
|
667
|
+
super(event, core, args);
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// src/bot/decorators/EventRegisterable.ts
|
|
672
|
+
var EventMetadataKey = Symbol("event:metadata");
|
|
673
|
+
function RegisterEvent(eventName) {
|
|
674
|
+
return function(constructor) {
|
|
675
|
+
Reflect.defineMetadata(EventMetadataKey, eventName, constructor);
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
__name(RegisterEvent, "RegisterEvent");
|
|
679
|
+
|
|
680
|
+
// src/bot/controllers/EventController.ts
|
|
681
|
+
var EventController = class {
|
|
682
|
+
static {
|
|
683
|
+
__name(this, "EventController");
|
|
684
|
+
}
|
|
685
|
+
core;
|
|
686
|
+
logger = new Logger("Events");
|
|
687
|
+
isInitialized = false;
|
|
688
|
+
eventMap = /* @__PURE__ */ new Map();
|
|
689
|
+
constructor(core) {
|
|
690
|
+
this.core = core;
|
|
691
|
+
}
|
|
692
|
+
async init() {
|
|
693
|
+
if (this.isInitialized) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
this.isInitialized = true;
|
|
697
|
+
const handlersDir = this.core.config.bot.events.path;
|
|
698
|
+
this.logger.info(chalk6.bold(handlersDir));
|
|
699
|
+
await this.loadHandlers(handlersDir);
|
|
700
|
+
this.attachToClient();
|
|
701
|
+
const loadedEventsArray = [];
|
|
702
|
+
this.eventMap.forEach((handlers, eventName) => {
|
|
703
|
+
loadedEventsArray.push(`${chalk6.magenta.bold(handlers.length)} ${eventName}`);
|
|
704
|
+
});
|
|
705
|
+
this.logger.info(`${chalk6.bold.green("Loaded")}: ${this.eventMap.size > 0 ? loadedEventsArray.join(", ") : "none"}`);
|
|
706
|
+
}
|
|
707
|
+
async loadHandlers(dir) {
|
|
708
|
+
await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
|
|
709
|
+
for (const val of Object.values(imported)) {
|
|
710
|
+
if (this.isEventHandlerClass(val)) {
|
|
711
|
+
this.registerHandler(val);
|
|
712
|
+
this.logger.info(`${chalk6.italic("Registered")} ${chalk6.bold.yellow(val.name)} from ${chalk6.gray(relativePath)}`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
isEventHandlerClass(obj) {
|
|
718
|
+
if (typeof obj !== "function") return false;
|
|
719
|
+
return obj.prototype instanceof EventHandler && Reflect.hasMetadata(EventMetadataKey, obj);
|
|
720
|
+
}
|
|
721
|
+
registerHandler(handlerClass) {
|
|
722
|
+
const eventName = Reflect.getMetadata(EventMetadataKey, handlerClass);
|
|
723
|
+
if (!eventName) return;
|
|
724
|
+
let handlers = this.eventMap.get(eventName);
|
|
725
|
+
if (!handlers) {
|
|
726
|
+
handlers = [];
|
|
727
|
+
this.eventMap.set(eventName, handlers);
|
|
728
|
+
}
|
|
729
|
+
handlers.push(handlerClass);
|
|
730
|
+
}
|
|
731
|
+
attachToClient() {
|
|
732
|
+
for (const [eventName] of this.eventMap) {
|
|
733
|
+
this.logger.debug(`Attaching ${chalk6.bold.green(eventName)} to ${chalk6.bold.yellow(this.core.bot.client.user?.username)}`);
|
|
734
|
+
this.core.bot.client.on(eventName, (...args) => {
|
|
735
|
+
void (async () => {
|
|
736
|
+
await this.processEvent(eventName, args);
|
|
737
|
+
})();
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
async processEvent(eventName, args) {
|
|
742
|
+
const handlerCtors = this.eventMap.get(eventName);
|
|
743
|
+
if (!handlerCtors || handlerCtors.length === 0) return;
|
|
744
|
+
for (const HandlerCtor of handlerCtors) {
|
|
745
|
+
try {
|
|
746
|
+
this.logger.debug(`Processing ${chalk6.bold.green(eventName)} with ${chalk6.gray(HandlerCtor.name)}`);
|
|
747
|
+
const handler = new HandlerCtor(args, this.core);
|
|
748
|
+
if (handler.hasChecks()) {
|
|
749
|
+
await handler.runChecks();
|
|
750
|
+
}
|
|
751
|
+
if (handler.shouldBreak()) return;
|
|
752
|
+
if (!handler.hasErrors()) {
|
|
753
|
+
await handler.execute();
|
|
754
|
+
}
|
|
755
|
+
} catch (err) {
|
|
756
|
+
this.logger.error(`Error in event ${String(eventName)} handler ${HandlerCtor.name}:`, err);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// src/bot/decorators/InteractionConfigurable.ts
|
|
763
|
+
var InteractionRoutes = /* @__PURE__ */ (function(InteractionRoutes2) {
|
|
764
|
+
InteractionRoutes2["Slash"] = "interaction:slash";
|
|
765
|
+
InteractionRoutes2["Button"] = "interaction:button";
|
|
766
|
+
InteractionRoutes2["Modal"] = "interaction:modal";
|
|
767
|
+
InteractionRoutes2["StringMenu"] = "interaction:stringMenu";
|
|
768
|
+
InteractionRoutes2["UserMenu"] = "interaction:userMenu";
|
|
769
|
+
InteractionRoutes2["RoleMenu"] = "interaction:roleMenu";
|
|
770
|
+
InteractionRoutes2["ChannelMenu"] = "interaction:channelMenu";
|
|
771
|
+
InteractionRoutes2["MentionableMenu"] = "interaction:mentionableMenu";
|
|
772
|
+
InteractionRoutes2["MessageContextMenu"] = "interaction:messageContextMenu";
|
|
773
|
+
InteractionRoutes2["UserContextMenu"] = "interaction:userContextMenu";
|
|
774
|
+
InteractionRoutes2["Autocomplete"] = "interaction:autocomplete";
|
|
775
|
+
return InteractionRoutes2;
|
|
776
|
+
})({});
|
|
777
|
+
var SelectMenuType = /* @__PURE__ */ (function(SelectMenuType2) {
|
|
778
|
+
SelectMenuType2["String"] = "string";
|
|
779
|
+
SelectMenuType2["User"] = "user";
|
|
780
|
+
SelectMenuType2["Role"] = "role";
|
|
781
|
+
SelectMenuType2["Channel"] = "channel";
|
|
782
|
+
SelectMenuType2["Mentionable"] = "mentionable";
|
|
783
|
+
return SelectMenuType2;
|
|
784
|
+
})({});
|
|
785
|
+
var InteractionMetadataKey = Symbol("interaction:metadata");
|
|
786
|
+
function SlashRoute(routeOrRoutes) {
|
|
787
|
+
return function(constructor) {
|
|
788
|
+
storeMetadata("interaction:slash", routeOrRoutes, constructor);
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
__name(SlashRoute, "SlashRoute");
|
|
792
|
+
function ButtonRoute(routeOrRoutes) {
|
|
793
|
+
return function(constructor) {
|
|
794
|
+
storeMetadata("interaction:button", routeOrRoutes, constructor);
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
__name(ButtonRoute, "ButtonRoute");
|
|
798
|
+
function ModalRoute(routeOrRoutes) {
|
|
799
|
+
return function(constructor) {
|
|
800
|
+
storeMetadata("interaction:modal", routeOrRoutes, constructor);
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
__name(ModalRoute, "ModalRoute");
|
|
804
|
+
function ContextMenuRoute(type, routeOrRoutes) {
|
|
805
|
+
return function(constructor) {
|
|
806
|
+
const routeType = type === "message" ? "interaction:messageContextMenu" : "interaction:userContextMenu";
|
|
807
|
+
storeMetadata(routeType, routeOrRoutes, constructor);
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
__name(ContextMenuRoute, "ContextMenuRoute");
|
|
811
|
+
function AutocompleteRoute(commandRoutes, focusedFields) {
|
|
812
|
+
return function(constructor) {
|
|
813
|
+
const routes = Array.isArray(commandRoutes) ? commandRoutes : [
|
|
814
|
+
commandRoutes
|
|
815
|
+
];
|
|
816
|
+
const fields = Array.isArray(focusedFields) ? focusedFields : [
|
|
817
|
+
focusedFields
|
|
818
|
+
];
|
|
819
|
+
routes.forEach((route) => {
|
|
820
|
+
fields.forEach((field) => {
|
|
821
|
+
const autocompleteKey = `${route}:${field}`;
|
|
822
|
+
storeMetadata("interaction:autocomplete", autocompleteKey, constructor);
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
__name(AutocompleteRoute, "AutocompleteRoute");
|
|
828
|
+
function SelectMenuRoute(type, routeOrRoutes) {
|
|
829
|
+
return function(constructor) {
|
|
830
|
+
const routeMap = {
|
|
831
|
+
["string"]: "interaction:stringMenu",
|
|
832
|
+
["user"]: "interaction:userMenu",
|
|
833
|
+
["role"]: "interaction:roleMenu",
|
|
834
|
+
["channel"]: "interaction:channelMenu",
|
|
835
|
+
["mentionable"]: "interaction:mentionableMenu"
|
|
836
|
+
};
|
|
837
|
+
storeMetadata(routeMap[type], routeOrRoutes, constructor);
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
__name(SelectMenuRoute, "SelectMenuRoute");
|
|
841
|
+
function storeMetadata(symbol, routes, constructor) {
|
|
842
|
+
const areRoutes = /* @__PURE__ */ __name((routes2) => {
|
|
843
|
+
return Array.isArray(routes2) && routes2.every((r) => typeof r === "string");
|
|
844
|
+
}, "areRoutes");
|
|
845
|
+
const savedRoutes = Reflect.getMetadata(symbol, constructor);
|
|
846
|
+
const existing = areRoutes(savedRoutes) ? savedRoutes : [];
|
|
847
|
+
const toStore = Array.isArray(routes) ? routes : [
|
|
848
|
+
routes
|
|
849
|
+
];
|
|
850
|
+
Reflect.defineMetadata(symbol, [
|
|
851
|
+
...existing,
|
|
852
|
+
...toStore
|
|
853
|
+
], constructor);
|
|
854
|
+
Reflect.defineMetadata(InteractionMetadataKey, true, constructor);
|
|
855
|
+
}
|
|
856
|
+
__name(storeMetadata, "storeMetadata");
|
|
857
|
+
var ErrorHandlingUtils = class {
|
|
858
|
+
static {
|
|
859
|
+
__name(this, "ErrorHandlingUtils");
|
|
860
|
+
}
|
|
861
|
+
static logger = new Logger("Errors");
|
|
862
|
+
static handleError(error, core, guild, user) {
|
|
863
|
+
const uuid = crypto2.randomUUID();
|
|
864
|
+
if (error instanceof CustomError) {
|
|
865
|
+
if (error instanceof DatabaseError) {
|
|
866
|
+
core.hooks.emit("unknownException", {
|
|
867
|
+
uuid,
|
|
868
|
+
error,
|
|
869
|
+
guild,
|
|
870
|
+
user
|
|
871
|
+
});
|
|
872
|
+
this.logger.error(`DatabaseError: ${error.uuid}`);
|
|
873
|
+
} else if (error.emit) {
|
|
874
|
+
this.logger.error(`${error.name}: ${error.message}`, error);
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
uuid,
|
|
878
|
+
response: error.response
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
this.logger.error(uuid, error);
|
|
882
|
+
core.hooks.emit("unknownException", {
|
|
883
|
+
uuid,
|
|
884
|
+
error,
|
|
885
|
+
guild,
|
|
886
|
+
user
|
|
887
|
+
});
|
|
888
|
+
return {
|
|
889
|
+
uuid,
|
|
890
|
+
response: new GenericError(uuid).response
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
var GenericError = class GenericError2 extends CustomError {
|
|
895
|
+
static {
|
|
896
|
+
__name(this, "GenericError");
|
|
897
|
+
}
|
|
898
|
+
uuid;
|
|
899
|
+
constructor(uuid) {
|
|
900
|
+
super("An unknown error occurred"), this.uuid = uuid;
|
|
901
|
+
this.response.setTitle("Error").setDescription(`An unknown error occurred. Please reach out to the developer with a way to reproduce the error and the following:
|
|
902
|
+
### UUID: \`${this.uuid}\``);
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// src/bot/decorators/Catchable.ts
|
|
907
|
+
function Catchable(options) {
|
|
908
|
+
return function(_target, _propertyKey, descriptor) {
|
|
909
|
+
const log = options?.log ?? false;
|
|
910
|
+
const forceFollowup = options?.forceFollowup ?? false;
|
|
911
|
+
const originalMethod = descriptor.value;
|
|
912
|
+
descriptor.value = async function(...args) {
|
|
913
|
+
const interaction = this.getEvent();
|
|
914
|
+
if (!originalMethod) throw new Error("Method not found");
|
|
915
|
+
try {
|
|
916
|
+
await originalMethod.apply(this, args);
|
|
917
|
+
} catch (error) {
|
|
918
|
+
if (!(error instanceof Error)) throw error;
|
|
919
|
+
this.setErrored();
|
|
920
|
+
if (log) console.error(error);
|
|
921
|
+
const { response } = ErrorHandlingUtils.handleError(error, this.core, interaction.guild, interaction.user);
|
|
922
|
+
const res = {
|
|
923
|
+
embeds: [
|
|
924
|
+
response
|
|
925
|
+
],
|
|
926
|
+
components: []
|
|
927
|
+
};
|
|
928
|
+
if (forceFollowup) {
|
|
929
|
+
await interaction.followUp({
|
|
930
|
+
flags: MessageFlags.Ephemeral,
|
|
931
|
+
...res
|
|
932
|
+
});
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (interaction.replied) {
|
|
936
|
+
await interaction.followUp({
|
|
937
|
+
flags: MessageFlags.Ephemeral,
|
|
938
|
+
...res
|
|
939
|
+
});
|
|
940
|
+
} else if (interaction.deferred) {
|
|
941
|
+
await interaction.editReply(res);
|
|
942
|
+
} else {
|
|
943
|
+
await interaction.reply({
|
|
944
|
+
flags: MessageFlags.Ephemeral,
|
|
945
|
+
...res
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
__name(Catchable, "Catchable");
|
|
953
|
+
|
|
954
|
+
// src/bot/defaults/UnhandledEvent.ts
|
|
955
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
956
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
957
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
958
|
+
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;
|
|
959
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
960
|
+
}
|
|
961
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
962
|
+
function _ts_metadata2(k, v) {
|
|
963
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
964
|
+
}
|
|
965
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
966
|
+
var UnhandledEvent = class extends InteractionHandler {
|
|
967
|
+
static {
|
|
968
|
+
__name(this, "UnhandledEvent");
|
|
969
|
+
}
|
|
970
|
+
async execute() {
|
|
971
|
+
await this.event.reply({
|
|
972
|
+
content: `Feature not implemented yet.`,
|
|
973
|
+
flags: MessageFlags.Ephemeral
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
_ts_decorate2([
|
|
978
|
+
Catchable(),
|
|
979
|
+
_ts_metadata2("design:type", Function),
|
|
980
|
+
_ts_metadata2("design:paramtypes", []),
|
|
981
|
+
_ts_metadata2("design:returntype", Promise)
|
|
982
|
+
], UnhandledEvent.prototype, "execute", null);
|
|
983
|
+
|
|
984
|
+
// src/bot/controllers/InteractionController.ts
|
|
985
|
+
var InteractionController = class {
|
|
986
|
+
static {
|
|
987
|
+
__name(this, "InteractionController");
|
|
988
|
+
}
|
|
989
|
+
core;
|
|
990
|
+
logger = new Logger("Interactions");
|
|
991
|
+
isInitialized = false;
|
|
992
|
+
slashMap = /* @__PURE__ */ new Map();
|
|
993
|
+
buttonMap = /* @__PURE__ */ new Map();
|
|
994
|
+
modalMap = /* @__PURE__ */ new Map();
|
|
995
|
+
stringSelectMap = /* @__PURE__ */ new Map();
|
|
996
|
+
userSelectMap = /* @__PURE__ */ new Map();
|
|
997
|
+
roleSelectMap = /* @__PURE__ */ new Map();
|
|
998
|
+
channelSelectMap = /* @__PURE__ */ new Map();
|
|
999
|
+
mentionableSelectMap = /* @__PURE__ */ new Map();
|
|
1000
|
+
messageContextMenuMap = /* @__PURE__ */ new Map();
|
|
1001
|
+
userContextMenuMap = /* @__PURE__ */ new Map();
|
|
1002
|
+
autocompleteMap = /* @__PURE__ */ new Map();
|
|
1003
|
+
keysToIgnore = /* @__PURE__ */ new Set([
|
|
1004
|
+
"confirm!confirmable",
|
|
1005
|
+
"cancel!confirmable"
|
|
1006
|
+
]);
|
|
1007
|
+
middlewares = [];
|
|
1008
|
+
constructor(core) {
|
|
1009
|
+
this.core = core;
|
|
1010
|
+
}
|
|
1011
|
+
async init() {
|
|
1012
|
+
if (this.isInitialized) return;
|
|
1013
|
+
this.isInitialized = true;
|
|
1014
|
+
const handlersDir = this.core.config.bot.interactions.path;
|
|
1015
|
+
this.logger.info(chalk6.bold(handlersDir));
|
|
1016
|
+
await this.loadHandlers(handlersDir);
|
|
1017
|
+
this.attachToClient();
|
|
1018
|
+
this.logger.info(`${chalk6.bold.green("Loaded interaction handlers:")}`);
|
|
1019
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.slashMap.size)} slash commands`);
|
|
1020
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.buttonMap.size)} buttons`);
|
|
1021
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.modalMap.size)} modals`);
|
|
1022
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.stringSelectMap.size)} string selects`);
|
|
1023
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.userSelectMap.size)} user selects`);
|
|
1024
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.roleSelectMap.size)} role selects`);
|
|
1025
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.channelSelectMap.size)} channel selects`);
|
|
1026
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.mentionableSelectMap.size)} mentionable selects`);
|
|
1027
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.messageContextMenuMap.size)} message context menus`);
|
|
1028
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.userContextMenuMap.size)} user context menus`);
|
|
1029
|
+
this.logger.info(`\u2192 ${chalk6.magenta.bold(this.autocompleteMap.size)} autocomplete`);
|
|
1030
|
+
}
|
|
1031
|
+
async loadHandlers(dir) {
|
|
1032
|
+
await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
|
|
1033
|
+
for (const val of Object.values(imported)) {
|
|
1034
|
+
if (this.isHandlerClass(val)) {
|
|
1035
|
+
this.registerHandler(val);
|
|
1036
|
+
this.logger.info(`${chalk6.italic("Registered")} ${chalk6.bold.yellow(val.name)} from ${chalk6.gray(relativePath)}`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
isHandlerClass(obj) {
|
|
1042
|
+
if (typeof obj !== "function") return false;
|
|
1043
|
+
return obj.prototype instanceof InteractionHandler && Reflect.hasMetadata(InteractionMetadataKey, obj) || obj.prototype instanceof AutocompleteHandler && Reflect.hasMetadata(InteractionMetadataKey, obj);
|
|
1044
|
+
}
|
|
1045
|
+
registerHandler(handlerClass) {
|
|
1046
|
+
const areRoutes = /* @__PURE__ */ __name((routes) => {
|
|
1047
|
+
return Array.isArray(routes) && routes.every((r) => typeof r === "string");
|
|
1048
|
+
}, "areRoutes");
|
|
1049
|
+
const routeTypes = [
|
|
1050
|
+
[
|
|
1051
|
+
InteractionRoutes.Slash,
|
|
1052
|
+
this.slashMap
|
|
1053
|
+
],
|
|
1054
|
+
[
|
|
1055
|
+
InteractionRoutes.Button,
|
|
1056
|
+
this.buttonMap
|
|
1057
|
+
],
|
|
1058
|
+
[
|
|
1059
|
+
InteractionRoutes.Modal,
|
|
1060
|
+
this.modalMap
|
|
1061
|
+
],
|
|
1062
|
+
[
|
|
1063
|
+
InteractionRoutes.StringMenu,
|
|
1064
|
+
this.stringSelectMap
|
|
1065
|
+
],
|
|
1066
|
+
[
|
|
1067
|
+
InteractionRoutes.UserMenu,
|
|
1068
|
+
this.userSelectMap
|
|
1069
|
+
],
|
|
1070
|
+
[
|
|
1071
|
+
InteractionRoutes.RoleMenu,
|
|
1072
|
+
this.roleSelectMap
|
|
1073
|
+
],
|
|
1074
|
+
[
|
|
1075
|
+
InteractionRoutes.ChannelMenu,
|
|
1076
|
+
this.channelSelectMap
|
|
1077
|
+
],
|
|
1078
|
+
[
|
|
1079
|
+
InteractionRoutes.MentionableMenu,
|
|
1080
|
+
this.mentionableSelectMap
|
|
1081
|
+
],
|
|
1082
|
+
[
|
|
1083
|
+
InteractionRoutes.MessageContextMenu,
|
|
1084
|
+
this.messageContextMenuMap
|
|
1085
|
+
],
|
|
1086
|
+
[
|
|
1087
|
+
InteractionRoutes.UserContextMenu,
|
|
1088
|
+
this.userContextMenuMap
|
|
1089
|
+
],
|
|
1090
|
+
[
|
|
1091
|
+
InteractionRoutes.Autocomplete,
|
|
1092
|
+
this.autocompleteMap
|
|
1093
|
+
]
|
|
1094
|
+
];
|
|
1095
|
+
for (const [routeType, map] of routeTypes) {
|
|
1096
|
+
const meta = Reflect.getMetadata(routeType, handlerClass);
|
|
1097
|
+
if (!areRoutes(meta)) continue;
|
|
1098
|
+
const routes = meta;
|
|
1099
|
+
routes.forEach((route) => map.set(route, handlerClass));
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
attachToClient() {
|
|
1103
|
+
this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
|
|
1104
|
+
this.handleInteraction(interaction).catch((err) => {
|
|
1105
|
+
this.logger.error(`[${chalk6.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
parseCustomId(customId) {
|
|
1110
|
+
const parts = customId.split(":");
|
|
1111
|
+
const prefix = parts[0] ?? "";
|
|
1112
|
+
const argString = parts[1] ?? "";
|
|
1113
|
+
const args = argString ? argString.split("-") : [];
|
|
1114
|
+
return {
|
|
1115
|
+
prefix,
|
|
1116
|
+
args
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
async handleCustomIdInteraction(interaction, getMap, interactionType) {
|
|
1120
|
+
const { prefix, args } = this.parseCustomId(interaction.customId);
|
|
1121
|
+
if (!prefix) return this.logger.warn(`${interactionType} has invalid customId: ${interaction.customId}`);
|
|
1122
|
+
await this.processInteraction(interaction, () => prefix, (key) => getMap().get(key), args);
|
|
1123
|
+
}
|
|
1124
|
+
async processInteraction(interaction, extractKey, getHandler, args) {
|
|
1125
|
+
const key = extractKey(interaction);
|
|
1126
|
+
if (this.keysToIgnore.has(key)) return;
|
|
1127
|
+
for (const MiddlewareCtor of this.middlewares) {
|
|
1128
|
+
const middleware = new MiddlewareCtor(interaction, this.core, args);
|
|
1129
|
+
await middleware.execute();
|
|
1130
|
+
if (middleware.hasErrors()) return;
|
|
1131
|
+
}
|
|
1132
|
+
let HandlerCtor = getHandler(key);
|
|
1133
|
+
if (!HandlerCtor) {
|
|
1134
|
+
this.logger.warn(`No handler found for key ${chalk6.bold.cyan(key)}. Falling back to UnhandledEvent.`);
|
|
1135
|
+
HandlerCtor = UnhandledEvent;
|
|
1136
|
+
}
|
|
1137
|
+
this.logger.debug(`Processing ${chalk6.bold.green(key)} with ${chalk6.gray(HandlerCtor.name)}`);
|
|
1138
|
+
const handler = new HandlerCtor(interaction, this.core, args);
|
|
1139
|
+
if (handler.hasChecks()) await handler.runChecks();
|
|
1140
|
+
if (handler.shouldBreak()) return;
|
|
1141
|
+
if (!handler.hasErrors()) await handler.execute();
|
|
1142
|
+
}
|
|
1143
|
+
async handleInteraction(interaction) {
|
|
1144
|
+
switch (true) {
|
|
1145
|
+
case interaction.isChatInputCommand():
|
|
1146
|
+
await this.handleSlashCommand(interaction);
|
|
1147
|
+
break;
|
|
1148
|
+
case interaction.isButton():
|
|
1149
|
+
await this.handleButton(interaction);
|
|
1150
|
+
break;
|
|
1151
|
+
case interaction.isModalSubmit():
|
|
1152
|
+
await this.handleModal(interaction);
|
|
1153
|
+
break;
|
|
1154
|
+
case interaction.isStringSelectMenu():
|
|
1155
|
+
await this.handleStringSelectMenu(interaction);
|
|
1156
|
+
break;
|
|
1157
|
+
case interaction.isUserSelectMenu():
|
|
1158
|
+
await this.handleUserSelectMenu(interaction);
|
|
1159
|
+
break;
|
|
1160
|
+
case interaction.isRoleSelectMenu():
|
|
1161
|
+
await this.handleRoleSelectMenu(interaction);
|
|
1162
|
+
break;
|
|
1163
|
+
case interaction.isChannelSelectMenu():
|
|
1164
|
+
await this.handleChannelSelectMenu(interaction);
|
|
1165
|
+
break;
|
|
1166
|
+
case interaction.isMentionableSelectMenu():
|
|
1167
|
+
await this.handleMentionableSelectMenu(interaction);
|
|
1168
|
+
break;
|
|
1169
|
+
case interaction.isMessageContextMenuCommand():
|
|
1170
|
+
await this.handleMessageContextMenu(interaction);
|
|
1171
|
+
break;
|
|
1172
|
+
case interaction.isUserContextMenuCommand():
|
|
1173
|
+
await this.handleUserContextMenu(interaction);
|
|
1174
|
+
break;
|
|
1175
|
+
case interaction.isAutocomplete():
|
|
1176
|
+
await this.handleAutocomplete(interaction);
|
|
1177
|
+
break;
|
|
1178
|
+
default:
|
|
1179
|
+
this.logger.warn(`Unhandled interaction type: ${interaction.type}`);
|
|
1180
|
+
break;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
async handleSlashCommand(interaction) {
|
|
1184
|
+
const route = this.buildSlashRoute(interaction);
|
|
1185
|
+
await this.processInteraction(interaction, () => route, (key) => this.slashMap.get(key));
|
|
1186
|
+
}
|
|
1187
|
+
async handleButton(interaction) {
|
|
1188
|
+
await this.handleCustomIdInteraction(interaction, () => this.buttonMap, "Button");
|
|
1189
|
+
}
|
|
1190
|
+
async handleModal(interaction) {
|
|
1191
|
+
await this.handleCustomIdInteraction(interaction, () => this.modalMap, "Modal");
|
|
1192
|
+
}
|
|
1193
|
+
async handleStringSelectMenu(interaction) {
|
|
1194
|
+
await this.handleCustomIdInteraction(interaction, () => this.stringSelectMap, "String select menu");
|
|
1195
|
+
}
|
|
1196
|
+
async handleUserSelectMenu(interaction) {
|
|
1197
|
+
await this.handleCustomIdInteraction(interaction, () => this.userSelectMap, "User select menu");
|
|
1198
|
+
}
|
|
1199
|
+
async handleRoleSelectMenu(interaction) {
|
|
1200
|
+
await this.handleCustomIdInteraction(interaction, () => this.roleSelectMap, "Role select menu");
|
|
1201
|
+
}
|
|
1202
|
+
async handleChannelSelectMenu(interaction) {
|
|
1203
|
+
await this.handleCustomIdInteraction(interaction, () => this.channelSelectMap, "Channel select menu");
|
|
1204
|
+
}
|
|
1205
|
+
async handleMentionableSelectMenu(interaction) {
|
|
1206
|
+
await this.handleCustomIdInteraction(interaction, () => this.mentionableSelectMap, "Mentionable select menu");
|
|
1207
|
+
}
|
|
1208
|
+
async handleMessageContextMenu(interaction) {
|
|
1209
|
+
await this.processInteraction(interaction, () => interaction.commandName, (key) => this.messageContextMenuMap.get(key));
|
|
1210
|
+
}
|
|
1211
|
+
async handleUserContextMenu(interaction) {
|
|
1212
|
+
await this.processInteraction(interaction, () => interaction.commandName, (key) => this.userContextMenuMap.get(key));
|
|
1213
|
+
}
|
|
1214
|
+
async handleAutocomplete(interaction) {
|
|
1215
|
+
const route = this.buildSlashRoute(interaction);
|
|
1216
|
+
const focused = interaction.options.getFocused(true);
|
|
1217
|
+
const autocompleteKey = `${route}:${focused.name}`;
|
|
1218
|
+
await this.processInteraction(interaction, () => autocompleteKey, (key) => this.autocompleteMap.get(key));
|
|
1219
|
+
}
|
|
1220
|
+
// Build the route from commandName, subcommandGroup, subcommand
|
|
1221
|
+
buildSlashRoute(interaction) {
|
|
1222
|
+
const command = interaction.commandName;
|
|
1223
|
+
const group = interaction.options.getSubcommandGroup(false);
|
|
1224
|
+
const sub = interaction.options.getSubcommand(false);
|
|
1225
|
+
let route = command;
|
|
1226
|
+
if (group && sub) {
|
|
1227
|
+
route = `${route}/${group}/${sub}`;
|
|
1228
|
+
} else if (group) {
|
|
1229
|
+
route = `${route}/${group}`;
|
|
1230
|
+
} else if (sub) {
|
|
1231
|
+
route = `${route}/${sub}`;
|
|
1232
|
+
}
|
|
1233
|
+
return route;
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
var EmojiInjector = class {
|
|
1237
|
+
static {
|
|
1238
|
+
__name(this, "EmojiInjector");
|
|
1239
|
+
}
|
|
1240
|
+
core;
|
|
1241
|
+
logger = new Logger("Emojis");
|
|
1242
|
+
constructor(core) {
|
|
1243
|
+
this.core = core;
|
|
1244
|
+
}
|
|
1245
|
+
async init() {
|
|
1246
|
+
if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
|
|
1247
|
+
this.logger.info(`${chalk6.bold.green("Loaded")}: ${chalk6.magenta.bold("0")} emojis`);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
const configEmojis = this.core.config.bot.emojis;
|
|
1251
|
+
await this.core.bot.client.application?.emojis.fetch();
|
|
1252
|
+
let foundCount = 0;
|
|
1253
|
+
Object.entries(configEmojis).forEach(([key, emojiName]) => {
|
|
1254
|
+
const emoji = this.core.bot.client.application?.emojis.cache.find((e) => e.name === emojiName);
|
|
1255
|
+
if (emoji) {
|
|
1256
|
+
configEmojis[key] = `<${emoji.identifier}>`;
|
|
1257
|
+
foundCount++;
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
this.logger.info(`${chalk6.bold.green("Loaded")}: ${chalk6.magenta.bold(foundCount)} emojis`);
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
var Plugin = class {
|
|
1264
|
+
static {
|
|
1265
|
+
__name(this, "Plugin");
|
|
1266
|
+
}
|
|
1267
|
+
pluggable;
|
|
1268
|
+
constructor(pluggable) {
|
|
1269
|
+
this.pluggable = pluggable;
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
var Pluggable = class _Pluggable {
|
|
1273
|
+
static {
|
|
1274
|
+
__name(this, "Pluggable");
|
|
1275
|
+
}
|
|
1276
|
+
isInitialized = false;
|
|
1277
|
+
shutdown;
|
|
1278
|
+
startup;
|
|
1279
|
+
static PLUGIN_INIT_TIMEOUT_MS = 15e3;
|
|
1280
|
+
constructor(shutdown, startup) {
|
|
1281
|
+
this.shutdown = shutdown;
|
|
1282
|
+
this.startup = startup;
|
|
1283
|
+
}
|
|
1284
|
+
async init() {
|
|
1285
|
+
if (this.isInitialized) return this;
|
|
1286
|
+
await this.startup.run();
|
|
1287
|
+
this.isInitialized = true;
|
|
1288
|
+
return this;
|
|
1289
|
+
}
|
|
1290
|
+
attach(key, Plugin2, startupPhase, ...args) {
|
|
1291
|
+
if (this.isInitialized) throw new Error("Cannot attach a plugin after initialization.");
|
|
1292
|
+
if (this[key]) throw new Error(`Plugin with key "${key}" already exists.`);
|
|
1293
|
+
const instance = new Plugin2(this, ...args);
|
|
1294
|
+
const entry = {
|
|
1295
|
+
[key]: instance
|
|
1296
|
+
};
|
|
1297
|
+
this.startup.addTask(startupPhase, `Plugin:${key}`, async () => {
|
|
1298
|
+
instance.logger.info(chalk6.bold("Initializing"));
|
|
1299
|
+
await instance.init();
|
|
1300
|
+
instance.logger.info(chalk6.bold("Initialized"));
|
|
1301
|
+
}, _Pluggable.PLUGIN_INIT_TIMEOUT_MS);
|
|
1302
|
+
return Object.assign(this, entry);
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
var CoordinatedLifecycle = class {
|
|
1306
|
+
static {
|
|
1307
|
+
__name(this, "CoordinatedLifecycle");
|
|
1308
|
+
}
|
|
1309
|
+
phaseOrder;
|
|
1310
|
+
phaseEnum;
|
|
1311
|
+
logger;
|
|
1312
|
+
events = new EventEmitter();
|
|
1313
|
+
tasksMap = /* @__PURE__ */ new Map();
|
|
1314
|
+
constructor(loggerName, phaseOrder, phaseEnum) {
|
|
1315
|
+
this.phaseOrder = phaseOrder;
|
|
1316
|
+
this.phaseEnum = phaseEnum;
|
|
1317
|
+
this.logger = new Logger(loggerName);
|
|
1318
|
+
this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Add a task to a specific phase
|
|
1322
|
+
*/
|
|
1323
|
+
addTask(phase, taskName, task, timeoutMs) {
|
|
1324
|
+
if (!this.canAddTask()) return;
|
|
1325
|
+
const tasks = this.tasksMap.get(phase);
|
|
1326
|
+
if (!tasks) throw new Error(`Unknown phase: ${phase}`);
|
|
1327
|
+
tasks.push({
|
|
1328
|
+
name: taskName,
|
|
1329
|
+
task,
|
|
1330
|
+
timeout: timeoutMs
|
|
1331
|
+
});
|
|
1332
|
+
this.logger.debug(`${chalk6.italic("Added")} ${this.getTaskType()} task ${chalk6.bold.cyan(taskName)} to phase ${chalk6.bold.magenta(this.phaseEnum[phase])}`);
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Remove a task by name from a specific phase
|
|
1336
|
+
*/
|
|
1337
|
+
removeTask(phase, taskName) {
|
|
1338
|
+
if (!this.canRemoveTask()) return false;
|
|
1339
|
+
const tasks = this.tasksMap.get(phase);
|
|
1340
|
+
if (!tasks) return false;
|
|
1341
|
+
const initialLength = tasks.length;
|
|
1342
|
+
const filteredTasks = tasks.filter((task) => task.name !== taskName);
|
|
1343
|
+
this.tasksMap.set(phase, filteredTasks);
|
|
1344
|
+
const removed = initialLength !== filteredTasks.length;
|
|
1345
|
+
if (removed) {
|
|
1346
|
+
this.logger.debug(`${chalk6.italic("Removed")} ${this.getTaskType()} task ${chalk6.bold.cyan(taskName)} from phase ${chalk6.bold.magenta(this.phaseEnum[phase])}`);
|
|
1347
|
+
}
|
|
1348
|
+
return removed;
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Run all tasks in a specific phase
|
|
1352
|
+
*/
|
|
1353
|
+
async runPhase(phase) {
|
|
1354
|
+
const tasks = this.tasksMap.get(phase) ?? [];
|
|
1355
|
+
if (tasks.length === 0) {
|
|
1356
|
+
this.logger.warn(`No tasks to run in phase ${chalk6.bold.magenta(this.phaseEnum[phase])}`);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
this.logger.info(`${chalk6.bold.yellow("Running")} ${this.getTaskType()} phase ${chalk6.bold.magenta(this.phaseEnum[phase])} with ${chalk6.bold.cyan(tasks.length)} tasks`);
|
|
1360
|
+
this.emit(`phase:${phase}:start`);
|
|
1361
|
+
const results = await this.executeTasksInPhase(phase, tasks);
|
|
1362
|
+
const failures = results.filter((r) => r.status === "rejected").length;
|
|
1363
|
+
if (failures > 0) {
|
|
1364
|
+
const errorMessage = `Phase ${chalk6.bold.magenta(this.phaseEnum[phase])} completed with ${chalk6.bold.red(failures)} failed tasks`;
|
|
1365
|
+
throw new Error(errorMessage);
|
|
1366
|
+
} else {
|
|
1367
|
+
this.logger.info(`Phase ${chalk6.bold.magenta(this.phaseEnum[phase])} ${chalk6.bold.green("completed successfully")}`);
|
|
1368
|
+
}
|
|
1369
|
+
this.emit(`phase:${phase}:complete`);
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Run a single task with timeout
|
|
1373
|
+
*/
|
|
1374
|
+
async runTaskWithTimeout(phase, task) {
|
|
1375
|
+
this.logger.info(`${chalk6.italic("Starting")} task ${chalk6.bold.cyan(task.name)} in phase ${chalk6.bold.magenta(this.phaseEnum[phase])}`);
|
|
1376
|
+
try {
|
|
1377
|
+
await Promise.race([
|
|
1378
|
+
task.task(),
|
|
1379
|
+
new Promise((_, reject) => {
|
|
1380
|
+
setTimeout(() => {
|
|
1381
|
+
reject(new Error(`Task '${task.name}' timed out after ${task.timeout}ms`));
|
|
1382
|
+
}, task.timeout);
|
|
1383
|
+
})
|
|
1384
|
+
]);
|
|
1385
|
+
this.logger.info(`${chalk6.italic("Completed")} task ${chalk6.bold.cyan(task.name)} in phase ${chalk6.bold.magenta(this.phaseEnum[phase])}`);
|
|
1386
|
+
} catch (error) {
|
|
1387
|
+
this.logger.error(`${chalk6.italic("Failed")} task ${chalk6.bold.cyan(task.name)} in phase ${chalk6.bold.magenta(this.phaseEnum[phase])}:`, error);
|
|
1388
|
+
throw error;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Subscribe to lifecycle events
|
|
1393
|
+
*/
|
|
1394
|
+
on(event, listener) {
|
|
1395
|
+
this.events.on(event, listener);
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Unsubscribe from lifecycle events
|
|
1399
|
+
*/
|
|
1400
|
+
off(event, listener) {
|
|
1401
|
+
this.events.off(event, listener);
|
|
1402
|
+
}
|
|
1403
|
+
emit(event, ...args) {
|
|
1404
|
+
return this.events.emit(event, ...args);
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1408
|
+
// src/services/Lifecycle/CoordinatedShutdown.ts
|
|
1409
|
+
var ShutdownPhase = /* @__PURE__ */ (function(ShutdownPhase2) {
|
|
1410
|
+
ShutdownPhase2[ShutdownPhase2["StopAcceptingRequests"] = 1] = "StopAcceptingRequests";
|
|
1411
|
+
ShutdownPhase2[ShutdownPhase2["StopServices"] = 2] = "StopServices";
|
|
1412
|
+
ShutdownPhase2[ShutdownPhase2["ExternalResources"] = 3] = "ExternalResources";
|
|
1413
|
+
ShutdownPhase2[ShutdownPhase2["DiscordCleanup"] = 4] = "DiscordCleanup";
|
|
1414
|
+
ShutdownPhase2[ShutdownPhase2["FinalCleanup"] = 5] = "FinalCleanup";
|
|
1415
|
+
return ShutdownPhase2;
|
|
1416
|
+
})({});
|
|
1417
|
+
var PHASE_ORDER = [
|
|
1418
|
+
1,
|
|
1419
|
+
2,
|
|
1420
|
+
3,
|
|
1421
|
+
4,
|
|
1422
|
+
5
|
|
1423
|
+
];
|
|
1424
|
+
var LOG_FLUSH_DELAY_MS = 500;
|
|
1425
|
+
var CoordinatedShutdown = class extends CoordinatedLifecycle {
|
|
1426
|
+
static {
|
|
1427
|
+
__name(this, "CoordinatedShutdown");
|
|
1428
|
+
}
|
|
1429
|
+
isShuttingDown = false;
|
|
1430
|
+
exitCode = 0;
|
|
1431
|
+
constructor() {
|
|
1432
|
+
super("CoordinatedShutdown", PHASE_ORDER, ShutdownPhase);
|
|
1433
|
+
this.registerSignalHandlers();
|
|
1434
|
+
}
|
|
1435
|
+
canAddTask() {
|
|
1436
|
+
return Globals.shutdownIsEnabled;
|
|
1437
|
+
}
|
|
1438
|
+
canRemoveTask() {
|
|
1439
|
+
return true;
|
|
1440
|
+
}
|
|
1441
|
+
getTaskType() {
|
|
1442
|
+
return "shutdown";
|
|
1443
|
+
}
|
|
1444
|
+
async executeTasksInPhase(phase, tasks) {
|
|
1445
|
+
const results = [];
|
|
1446
|
+
for (const task of tasks) {
|
|
1447
|
+
results.push(await Promise.resolve().then(() => this.runTaskWithTimeout(phase, task)).then(() => ({
|
|
1448
|
+
status: "fulfilled",
|
|
1449
|
+
value: void 0
|
|
1450
|
+
}), (reason) => ({
|
|
1451
|
+
status: "rejected",
|
|
1452
|
+
reason
|
|
1453
|
+
})));
|
|
1454
|
+
}
|
|
1455
|
+
return results;
|
|
1456
|
+
}
|
|
1457
|
+
registerSignalHandlers() {
|
|
1458
|
+
if (!Globals.shutdownIsEnabled) return;
|
|
1459
|
+
process.on("SIGTERM", () => {
|
|
1460
|
+
this.logger.info(`Received ${chalk6.yellow.bold("SIGTERM")} signal`);
|
|
1461
|
+
void this.run(0);
|
|
1462
|
+
});
|
|
1463
|
+
process.on("SIGINT", () => {
|
|
1464
|
+
this.logger.info(`Received ${chalk6.yellow.bold("SIGINT")} signal`);
|
|
1465
|
+
void this.run(0);
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Add a task to a specific shutdown phase
|
|
1470
|
+
*/
|
|
1471
|
+
addTask(phase, taskName, task, timeoutMs = 5e3) {
|
|
1472
|
+
super.addTask(phase, taskName, task, timeoutMs);
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Remove a task by name from a specific phase
|
|
1476
|
+
*/
|
|
1477
|
+
removeTask(phase, taskName) {
|
|
1478
|
+
return super.removeTask(phase, taskName);
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Start the coordinated shutdown sequence
|
|
1482
|
+
*/
|
|
1483
|
+
async run(exitCode = 0) {
|
|
1484
|
+
if (this.isShuttingDown) {
|
|
1485
|
+
this.logger.warn("Shutdown sequence already in progress");
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
this.isShuttingDown = true;
|
|
1489
|
+
this.exitCode = exitCode;
|
|
1490
|
+
this.logger.info(`${chalk6.bold.yellow("Starting")} coordinated shutdown with exit code ${chalk6.bold.cyan(exitCode)}`);
|
|
1491
|
+
this.emit("shutdown:start");
|
|
1492
|
+
try {
|
|
1493
|
+
for (const phase of PHASE_ORDER) {
|
|
1494
|
+
await this.runPhase(phase);
|
|
1495
|
+
}
|
|
1496
|
+
this.logger.info(`${chalk6.bold.green("Coordinated shutdown completed")} successfully`);
|
|
1497
|
+
this.emit("shutdown:complete");
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
this.logger.error(`${chalk6.bold.red("Coordinated shutdown failed")}`);
|
|
1500
|
+
this.emit("shutdown:error", error);
|
|
1501
|
+
} finally {
|
|
1502
|
+
this.logger.info(`${chalk6.bold.red("Exiting")} process with code ${chalk6.bold.cyan(this.exitCode)}`);
|
|
1503
|
+
setTimeout(() => {
|
|
1504
|
+
process.exit(this.exitCode);
|
|
1505
|
+
}, LOG_FLUSH_DELAY_MS);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Subscribe to shutdown events
|
|
1510
|
+
*/
|
|
1511
|
+
on(event, listener) {
|
|
1512
|
+
super.on(event, listener);
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Unsubscribe from shutdown events
|
|
1516
|
+
*/
|
|
1517
|
+
off(event, listener) {
|
|
1518
|
+
super.off(event, listener);
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
|
|
1522
|
+
// src/bot/Bot.ts
|
|
1523
|
+
var Bot = class extends Plugin {
|
|
1524
|
+
static {
|
|
1525
|
+
__name(this, "Bot");
|
|
1526
|
+
}
|
|
1527
|
+
core;
|
|
1528
|
+
logger = new Logger("Bot");
|
|
1529
|
+
isInitialized = false;
|
|
1530
|
+
_client;
|
|
1531
|
+
interactions;
|
|
1532
|
+
events;
|
|
1533
|
+
commands;
|
|
1534
|
+
emojiInjector;
|
|
1535
|
+
constructor(core) {
|
|
1536
|
+
super(core), this.core = core;
|
|
1537
|
+
this._client = new Client(core.config.bot.clientOptions);
|
|
1538
|
+
this.interactions = new InteractionController(core);
|
|
1539
|
+
this.events = new EventController(core);
|
|
1540
|
+
this.commands = new CommandRegistry(this.core);
|
|
1541
|
+
this.emojiInjector = new EmojiInjector(this.core);
|
|
1542
|
+
this.core.shutdown.addTask(ShutdownPhase.DiscordCleanup, "stop-bot", async () => await this.stop());
|
|
1543
|
+
}
|
|
1544
|
+
async init() {
|
|
1545
|
+
if (this.isInitialized) {
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
this.isInitialized = true;
|
|
1549
|
+
await this.login();
|
|
1550
|
+
await this.interactions.init();
|
|
1551
|
+
await this.events.init();
|
|
1552
|
+
await this.commands.init();
|
|
1553
|
+
await this.commands.setCommands();
|
|
1554
|
+
await this.emojiInjector.init();
|
|
1555
|
+
}
|
|
1556
|
+
async stop() {
|
|
1557
|
+
this._client.removeAllListeners();
|
|
1558
|
+
await this.logout();
|
|
1559
|
+
}
|
|
1560
|
+
async login() {
|
|
1561
|
+
await this._client.login(Globals.botToken);
|
|
1562
|
+
this.logger.info(`Logged in as ${chalk6.bold.magenta(this._client.user?.username)}!`);
|
|
1563
|
+
return this;
|
|
1564
|
+
}
|
|
1565
|
+
async logout() {
|
|
1566
|
+
await this._client.destroy();
|
|
1567
|
+
this.logger.info(chalk6.bold.red("Logged out of Discord!"));
|
|
1568
|
+
}
|
|
1569
|
+
get client() {
|
|
1570
|
+
return this._client;
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
// src/hooks/decorators/RegisterHook.ts
|
|
1575
|
+
var HookMetadataKey = Symbol("hook:metadata");
|
|
1576
|
+
function RegisterHook(hook) {
|
|
1577
|
+
return function(constructor) {
|
|
1578
|
+
Reflect.defineMetadata(HookMetadataKey, hook, constructor);
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
__name(RegisterHook, "RegisterHook");
|
|
1582
|
+
|
|
1583
|
+
// src/hooks/interfaces/HookHandler.ts
|
|
1584
|
+
var HookHandler = class {
|
|
1585
|
+
static {
|
|
1586
|
+
__name(this, "HookHandler");
|
|
1587
|
+
}
|
|
1588
|
+
data;
|
|
1589
|
+
core;
|
|
1590
|
+
constructor(data, core) {
|
|
1591
|
+
this.data = data;
|
|
1592
|
+
this.core = core;
|
|
1593
|
+
this.data = data;
|
|
1594
|
+
this.core = core;
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
// src/hooks/interfaces/abstracts/WebhookLog.ts
|
|
1599
|
+
var WebhookLog = class extends HookHandler {
|
|
1600
|
+
static {
|
|
1601
|
+
__name(this, "WebhookLog");
|
|
1602
|
+
}
|
|
1603
|
+
constructor(data, core) {
|
|
1604
|
+
super(data, core);
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
// src/hooks/default/UnknownException.ts
|
|
1609
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
1610
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1611
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1612
|
+
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;
|
|
1613
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1614
|
+
}
|
|
1615
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
1616
|
+
var UnknownException = class extends WebhookLog {
|
|
1617
|
+
static {
|
|
1618
|
+
__name(this, "UnknownException");
|
|
1619
|
+
}
|
|
1620
|
+
webhook = new WebhookClient({
|
|
1621
|
+
url: Globals.unknownExceptionWebhookUrl
|
|
1622
|
+
});
|
|
1623
|
+
async execute() {
|
|
1624
|
+
await this.webhook.send({
|
|
1625
|
+
username: "Unknown Exception",
|
|
1626
|
+
avatarURL: "https://cdn.discordapp.com/attachments/1351446034827579466/1351446912947191830/warning-2.png",
|
|
1627
|
+
embeds: [
|
|
1628
|
+
new UnhandledErrorEmbed(this.data).component
|
|
1629
|
+
]
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
UnknownException = _ts_decorate3([
|
|
1634
|
+
RegisterHook("unknownException")
|
|
1635
|
+
], UnknownException);
|
|
1636
|
+
var UnhandledErrorEmbed = class UnhandledErrorEmbed2 extends BuilderComponent {
|
|
1637
|
+
static {
|
|
1638
|
+
__name(this, "UnhandledErrorEmbed");
|
|
1639
|
+
}
|
|
1640
|
+
constructor(data) {
|
|
1641
|
+
super("embed");
|
|
1642
|
+
const { uuid, error, guild, user } = data;
|
|
1643
|
+
this.instance.setTitle(`An unknown exception was thrown`).setColor("#ef4860").setDescription(`**Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`
|
|
1644
|
+
**Guild Name:** ${guild?.name ?? "Not used in a guild"}
|
|
1645
|
+
**User ID:** \`${user?.id ?? "Missing user info"}\`
|
|
1646
|
+
**Username:** ${user?.username ?? "Missing user info"}
|
|
1647
|
+
### UUID: \`${uuid}\`
|
|
1648
|
+
\`\`\`${error.stack}\`\`\``);
|
|
1649
|
+
this.setTimestampsIfAvailable(error);
|
|
1650
|
+
}
|
|
1651
|
+
setTimestampsIfAvailable(error) {
|
|
1652
|
+
if (!(error instanceof DiscordAPIError)) return;
|
|
1653
|
+
const now = Date.now();
|
|
1654
|
+
const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
|
|
1655
|
+
if (!snowflake) return void 0;
|
|
1656
|
+
const interactionTs = Number(SnowflakeUtil.deconstruct(snowflake).timestamp);
|
|
1657
|
+
const diff = now - interactionTs;
|
|
1658
|
+
const seconds = Math.floor(diff / 1e3);
|
|
1659
|
+
const millis = diff % 1e3;
|
|
1660
|
+
this.instance.addFields([
|
|
1661
|
+
{
|
|
1662
|
+
name: "Timestamps",
|
|
1663
|
+
value: `- **\`Interaction sent\` :** ${new Date(interactionTs).toISOString()} (${interactionTs})
|
|
1664
|
+
- **\`Error logged \` :** ${new Date(now).toISOString()} (${now})
|
|
1665
|
+
- **\`Offset \` :** ${seconds}s ${millis}ms`,
|
|
1666
|
+
inline: true
|
|
1667
|
+
}
|
|
1668
|
+
]);
|
|
1669
|
+
}
|
|
1670
|
+
};
|
|
1671
|
+
var HookEmitter = class {
|
|
1672
|
+
static {
|
|
1673
|
+
__name(this, "HookEmitter");
|
|
1674
|
+
}
|
|
1675
|
+
emitter = new EventEmitter();
|
|
1676
|
+
on(event, listener) {
|
|
1677
|
+
this.emitter.on(event, listener);
|
|
1678
|
+
return this;
|
|
1679
|
+
}
|
|
1680
|
+
once(event, listener) {
|
|
1681
|
+
this.emitter.once(event, listener);
|
|
1682
|
+
return this;
|
|
1683
|
+
}
|
|
1684
|
+
emit(event, data) {
|
|
1685
|
+
return this.emitter.emit(event, data);
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
|
|
1689
|
+
// src/hooks/HookController.ts
|
|
1690
|
+
var HookController = class extends Plugin {
|
|
1691
|
+
static {
|
|
1692
|
+
__name(this, "HookController");
|
|
1693
|
+
}
|
|
1694
|
+
core;
|
|
1695
|
+
logger = new Logger("Hooks");
|
|
1696
|
+
isInitialized = false;
|
|
1697
|
+
hookMap = /* @__PURE__ */ new Map();
|
|
1698
|
+
emitter = new HookEmitter();
|
|
1699
|
+
constructor(core) {
|
|
1700
|
+
super(core), this.core = core;
|
|
1701
|
+
}
|
|
1702
|
+
async init() {
|
|
1703
|
+
if (this.isInitialized) return;
|
|
1704
|
+
this.isInitialized = true;
|
|
1705
|
+
const hooksDir = this.core.config.hooks.path;
|
|
1706
|
+
this.logger.info(chalk6.bold(hooksDir));
|
|
1707
|
+
this.registerHook("unknownException", UnknownException);
|
|
1708
|
+
await this.loadHooks(hooksDir);
|
|
1709
|
+
this.attachHooks();
|
|
1710
|
+
const totalHooks = Array.from(this.hookMap.values()).reduce((acc, handlers) => acc + handlers.length, 0);
|
|
1711
|
+
this.logger.info(`${chalk6.bold.green("Loaded")}: ${chalk6.bold.magenta(totalHooks)} hooks`);
|
|
1712
|
+
}
|
|
1713
|
+
async loadHooks(dir) {
|
|
1714
|
+
await traverseDirectory(dir, (_fullPath, relativePath, imported) => {
|
|
1715
|
+
for (const exportName of Object.keys(imported)) {
|
|
1716
|
+
const val = imported[exportName];
|
|
1717
|
+
if (this.isHookHandler(val)) {
|
|
1718
|
+
const hookName = Reflect.getMetadata(HookMetadataKey, val);
|
|
1719
|
+
if (hookName) {
|
|
1720
|
+
this.registerHook(hookName, val);
|
|
1721
|
+
this.logger.info(`${chalk6.italic("Registered")} ${chalk6.bold.yellow(val.name)} from ${chalk6.gray(relativePath)}`);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
registerHook(hookName, handler) {
|
|
1728
|
+
let handlers = this.hookMap.get(hookName);
|
|
1729
|
+
if (!handlers) {
|
|
1730
|
+
handlers = [];
|
|
1731
|
+
this.hookMap.set(hookName, handlers);
|
|
1732
|
+
}
|
|
1733
|
+
handlers.push(handler);
|
|
1734
|
+
}
|
|
1735
|
+
isHookHandler(obj) {
|
|
1736
|
+
if (typeof obj !== "function") return false;
|
|
1737
|
+
return obj.prototype instanceof HookHandler;
|
|
1738
|
+
}
|
|
1739
|
+
attachHooks() {
|
|
1740
|
+
for (const [hookName, handlerCtors] of this.hookMap) {
|
|
1741
|
+
this.emitter.on(hookName, (data) => {
|
|
1742
|
+
for (const HandlerCtor of handlerCtors) {
|
|
1743
|
+
try {
|
|
1744
|
+
const instance = new HandlerCtor(data, this.core);
|
|
1745
|
+
void instance.execute();
|
|
1746
|
+
} catch (err) {
|
|
1747
|
+
this.logger.error(`Error in hook ${String(hookName)} handler ${HandlerCtor.name}:`, err);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
emit(event, data) {
|
|
1754
|
+
return this.emitter.emit(event, data);
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1757
|
+
var HTTP_OK = 200;
|
|
1758
|
+
var HTTP_NOT_FOUND = 404;
|
|
1759
|
+
var HealthCheck = class extends Plugin {
|
|
1760
|
+
static {
|
|
1761
|
+
__name(this, "HealthCheck");
|
|
1762
|
+
}
|
|
1763
|
+
core;
|
|
1764
|
+
logger = new Logger("HealthCheck");
|
|
1765
|
+
port = Globals.healthCheckPort;
|
|
1766
|
+
path = Globals.healthCheckPath;
|
|
1767
|
+
server;
|
|
1768
|
+
constructor(core) {
|
|
1769
|
+
super(core), this.core = core;
|
|
1770
|
+
this.core.shutdown.addTask(ShutdownPhase.StopServices, "stop-healthcheck-server", async () => await this.stop());
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Starts the health check server.
|
|
1774
|
+
* @returns Promise that resolves when the server is listening
|
|
1775
|
+
*/
|
|
1776
|
+
async init() {
|
|
1777
|
+
return new Promise((resolve, reject) => {
|
|
1778
|
+
this.server = createServer((req, res) => {
|
|
1779
|
+
if (req.method === "GET" && req.url === this.path) {
|
|
1780
|
+
res.writeHead(HTTP_OK, {
|
|
1781
|
+
"Content-Type": "application/json"
|
|
1782
|
+
});
|
|
1783
|
+
res.end(JSON.stringify({
|
|
1784
|
+
status: "ok",
|
|
1785
|
+
timestamp: Date.now()
|
|
1786
|
+
}));
|
|
1787
|
+
} else {
|
|
1788
|
+
res.writeHead(HTTP_NOT_FOUND, {
|
|
1789
|
+
"Content-Type": "application/json"
|
|
1790
|
+
});
|
|
1791
|
+
res.end(JSON.stringify({
|
|
1792
|
+
status: "not found"
|
|
1793
|
+
}));
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
this.server.on("error", reject);
|
|
1797
|
+
this.server.listen(this.port, () => {
|
|
1798
|
+
this.logger.info(`${chalk6.green.bold("\u2713")} Health check server listening on ${chalk6.cyan(`http://localhost:${this.port}${this.path}`)}`);
|
|
1799
|
+
resolve();
|
|
1800
|
+
});
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
stop() {
|
|
1804
|
+
return new Promise((shutdownResolve) => {
|
|
1805
|
+
this.server?.close(() => {
|
|
1806
|
+
this.logger.info(chalk6.bold.red("Health check server stopped"));
|
|
1807
|
+
shutdownResolve();
|
|
1808
|
+
});
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
var StartupPhase = /* @__PURE__ */ (function(StartupPhase2) {
|
|
1813
|
+
StartupPhase2[StartupPhase2["Validation"] = 1] = "Validation";
|
|
1814
|
+
StartupPhase2[StartupPhase2["Discovery"] = 2] = "Discovery";
|
|
1815
|
+
StartupPhase2[StartupPhase2["Registration"] = 3] = "Registration";
|
|
1816
|
+
StartupPhase2[StartupPhase2["Configuration"] = 4] = "Configuration";
|
|
1817
|
+
StartupPhase2[StartupPhase2["Instantiation"] = 5] = "Instantiation";
|
|
1818
|
+
StartupPhase2[StartupPhase2["Activation"] = 6] = "Activation";
|
|
1819
|
+
StartupPhase2[StartupPhase2["Ready"] = 7] = "Ready";
|
|
1820
|
+
return StartupPhase2;
|
|
1821
|
+
})({});
|
|
1822
|
+
var PHASE_ORDER2 = [
|
|
1823
|
+
1,
|
|
1824
|
+
2,
|
|
1825
|
+
3,
|
|
1826
|
+
4,
|
|
1827
|
+
5,
|
|
1828
|
+
6,
|
|
1829
|
+
7
|
|
1830
|
+
];
|
|
1831
|
+
var CoordinatedStartup = class extends CoordinatedLifecycle {
|
|
1832
|
+
static {
|
|
1833
|
+
__name(this, "CoordinatedStartup");
|
|
1834
|
+
}
|
|
1835
|
+
isStartingUp = false;
|
|
1836
|
+
hasStarted = false;
|
|
1837
|
+
constructor() {
|
|
1838
|
+
super("CoordinatedStartup", PHASE_ORDER2, StartupPhase);
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Add a task to a specific startup phase
|
|
1842
|
+
*/
|
|
1843
|
+
addTask(phase, taskName, task, timeoutMs = 1e4) {
|
|
1844
|
+
super.addTask(phase, taskName, task, timeoutMs);
|
|
1845
|
+
}
|
|
1846
|
+
canAddTask() {
|
|
1847
|
+
if (this.hasStarted) {
|
|
1848
|
+
throw new Error("Cannot add tasks after startup sequence has already completed");
|
|
1849
|
+
}
|
|
1850
|
+
if (this.isStartingUp) {
|
|
1851
|
+
throw new Error("Cannot add tasks while startup sequence is in progress");
|
|
1852
|
+
}
|
|
1853
|
+
return true;
|
|
1854
|
+
}
|
|
1855
|
+
canRemoveTask() {
|
|
1856
|
+
if (this.isStartingUp) {
|
|
1857
|
+
throw new Error("Cannot remove tasks while startup sequence is in progress");
|
|
1858
|
+
}
|
|
1859
|
+
return true;
|
|
1860
|
+
}
|
|
1861
|
+
getTaskType() {
|
|
1862
|
+
return "startup";
|
|
1863
|
+
}
|
|
1864
|
+
async executeTasksInPhase(phase, tasks) {
|
|
1865
|
+
const results = [];
|
|
1866
|
+
for (const task of tasks) {
|
|
1867
|
+
results.push(await Promise.resolve().then(() => this.runTaskWithTimeout(phase, task)).then(() => ({
|
|
1868
|
+
status: "fulfilled",
|
|
1869
|
+
value: void 0
|
|
1870
|
+
}), (reason) => ({
|
|
1871
|
+
status: "rejected",
|
|
1872
|
+
reason
|
|
1873
|
+
})));
|
|
1874
|
+
}
|
|
1875
|
+
return results;
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Start the coordinated startup sequence
|
|
1879
|
+
*/
|
|
1880
|
+
async run() {
|
|
1881
|
+
if (this.hasStarted) {
|
|
1882
|
+
this.logger.warn("Startup sequence has already completed");
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
if (this.isStartingUp) {
|
|
1886
|
+
this.logger.warn("Startup sequence already in progress");
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
this.isStartingUp = true;
|
|
1890
|
+
this.logger.info(`${chalk6.bold.green("Starting")} coordinated startup sequence`);
|
|
1891
|
+
this.emit("startup:start");
|
|
1892
|
+
try {
|
|
1893
|
+
for (const phase of PHASE_ORDER2) await this.runPhase(phase);
|
|
1894
|
+
this.hasStarted = true;
|
|
1895
|
+
this.logger.info(`${chalk6.bold.green("Coordinated startup completed")} successfully`);
|
|
1896
|
+
this.emit("startup:complete");
|
|
1897
|
+
} catch (error) {
|
|
1898
|
+
this.logger.error(`${chalk6.bold.red("Coordinated startup failed")}`);
|
|
1899
|
+
this.emit("startup:error", error);
|
|
1900
|
+
throw error;
|
|
1901
|
+
} finally {
|
|
1902
|
+
this.isStartingUp = false;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Subscribe to startup events
|
|
1907
|
+
*/
|
|
1908
|
+
on(event, listener) {
|
|
1909
|
+
super.on(event, listener);
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Unsubscribe from startup events
|
|
1913
|
+
*/
|
|
1914
|
+
off(event, listener) {
|
|
1915
|
+
super.off(event, listener);
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Check if startup has completed
|
|
1919
|
+
*/
|
|
1920
|
+
get isReady() {
|
|
1921
|
+
return this.hasStarted;
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Check if startup is currently running
|
|
1925
|
+
*/
|
|
1926
|
+
get isRunning() {
|
|
1927
|
+
return this.isStartingUp;
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
|
|
1931
|
+
// src/Seedcord.ts
|
|
1932
|
+
var Seedcord = class _Seedcord extends Pluggable {
|
|
1933
|
+
static {
|
|
1934
|
+
__name(this, "Seedcord");
|
|
1935
|
+
}
|
|
1936
|
+
config;
|
|
1937
|
+
static isInstantiated = false;
|
|
1938
|
+
shutdown;
|
|
1939
|
+
startup;
|
|
1940
|
+
hooks;
|
|
1941
|
+
bot;
|
|
1942
|
+
healthCheck;
|
|
1943
|
+
constructor(config) {
|
|
1944
|
+
const shutdown = new CoordinatedShutdown();
|
|
1945
|
+
const startup = new CoordinatedStartup();
|
|
1946
|
+
super(shutdown, startup), this.config = config;
|
|
1947
|
+
this.shutdown = shutdown;
|
|
1948
|
+
this.startup = startup;
|
|
1949
|
+
if (_Seedcord.isInstantiated) {
|
|
1950
|
+
throw new Error("Seedcord can only be instantiated once. Use the existing instance instead.");
|
|
1951
|
+
}
|
|
1952
|
+
_Seedcord.isInstantiated = true;
|
|
1953
|
+
this.hooks = new HookController(this);
|
|
1954
|
+
this.bot = new Bot(this);
|
|
1955
|
+
this.healthCheck = new HealthCheck(this);
|
|
1956
|
+
this.registerStartupTasks();
|
|
1957
|
+
}
|
|
1958
|
+
registerStartupTasks() {
|
|
1959
|
+
this.startup.addTask(StartupPhase.Configuration, "Hook Initialization", async () => {
|
|
1960
|
+
this.hooks.logger.info(chalk6.bold("Initializing"));
|
|
1961
|
+
await this.hooks.init();
|
|
1962
|
+
this.hooks.logger.info(chalk6.bold("Initialized"));
|
|
1963
|
+
});
|
|
1964
|
+
this.startup.addTask(StartupPhase.Instantiation, "Bot Initialization", async () => {
|
|
1965
|
+
this.bot.logger.info(chalk6.bold("Initializing"));
|
|
1966
|
+
await this.bot.init();
|
|
1967
|
+
this.bot.logger.info(chalk6.bold("Initialized"));
|
|
1968
|
+
});
|
|
1969
|
+
this.startup.addTask(StartupPhase.Ready, "Health Check", async () => {
|
|
1970
|
+
this.healthCheck.logger.info(chalk6.bold("Initializing"));
|
|
1971
|
+
await this.healthCheck.init();
|
|
1972
|
+
this.healthCheck.logger.info(chalk6.bold("Initialized"));
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
async start() {
|
|
1976
|
+
await super.init();
|
|
1977
|
+
return this;
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
|
|
1981
|
+
// src/bot/decorators/Checkable.ts
|
|
1982
|
+
function Checkable(ctor) {
|
|
1983
|
+
return class extends ctor {
|
|
1984
|
+
static name = ctor.name;
|
|
1985
|
+
checkable = true;
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
__name(Checkable, "Checkable");
|
|
1989
|
+
function EventCatchable(log) {
|
|
1990
|
+
return function(_target, _prop, descriptor) {
|
|
1991
|
+
const original = descriptor.value;
|
|
1992
|
+
descriptor.value = async function(...args) {
|
|
1993
|
+
if (!original) throw new Error("Method not found");
|
|
1994
|
+
try {
|
|
1995
|
+
await original.apply(this, args);
|
|
1996
|
+
} catch (err) {
|
|
1997
|
+
if (!(err instanceof Error)) throw err;
|
|
1998
|
+
this.setErrored();
|
|
1999
|
+
if (log) console.error(err);
|
|
2000
|
+
const eventArgs = Array.isArray(this.getEvent()) ? this.getEvent() : [
|
|
2001
|
+
this.getEvent()
|
|
2002
|
+
];
|
|
2003
|
+
const msg = eventArgs.find((x) => x instanceof Message);
|
|
2004
|
+
const result = ErrorHandlingUtils.handleError(err, this.core, msg?.guild ?? null, msg?.author ?? null);
|
|
2005
|
+
if (!msg) return;
|
|
2006
|
+
await msg.reply({
|
|
2007
|
+
embeds: [
|
|
2008
|
+
result.response
|
|
2009
|
+
],
|
|
2010
|
+
components: []
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
__name(EventCatchable, "EventCatchable");
|
|
2017
|
+
|
|
2018
|
+
// src/services/CooldownManager.ts
|
|
2019
|
+
var CooldownManager = class {
|
|
2020
|
+
static {
|
|
2021
|
+
__name(this, "CooldownManager");
|
|
2022
|
+
}
|
|
2023
|
+
window;
|
|
2024
|
+
Err;
|
|
2025
|
+
msg;
|
|
2026
|
+
map = /* @__PURE__ */ new Map();
|
|
2027
|
+
constructor(opts = {}) {
|
|
2028
|
+
this.window = opts.cooldown ?? 1e3;
|
|
2029
|
+
this.Err = opts.err ?? Error;
|
|
2030
|
+
this.msg = opts.message ?? "Cooldown active";
|
|
2031
|
+
}
|
|
2032
|
+
/** Record usage without any checks. */
|
|
2033
|
+
set(key) {
|
|
2034
|
+
this.map.set(key, Date.now());
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Verify cooldown for `key`.\
|
|
2038
|
+
* If active → throws the custom error.\
|
|
2039
|
+
* If not active → updates timestamp and returns void.
|
|
2040
|
+
*/
|
|
2041
|
+
check(key) {
|
|
2042
|
+
const now = Date.now();
|
|
2043
|
+
const last = this.map.get(key);
|
|
2044
|
+
const remaining = this.window - (now - (last ?? 0));
|
|
2045
|
+
if (Globals.isDevelopment && remaining > 0) {
|
|
2046
|
+
Logger.Debug("CooldownManager", `${key} - ${remaining}ms remaining`);
|
|
2047
|
+
}
|
|
2048
|
+
if (last !== void 0 && remaining > 0) {
|
|
2049
|
+
throw new this.Err(this.msg, remaining);
|
|
2050
|
+
}
|
|
2051
|
+
this.map.set(key, now);
|
|
2052
|
+
}
|
|
2053
|
+
/** Returns true if the key is still cooling down (does not update timestamp). */
|
|
2054
|
+
isActive(key) {
|
|
2055
|
+
const last = this.map.get(key);
|
|
2056
|
+
return last !== void 0 && Date.now() - last < this.window;
|
|
2057
|
+
}
|
|
2058
|
+
/** Remove a key from the map (useful for manual resets). */
|
|
2059
|
+
clear(key) {
|
|
2060
|
+
this.map.delete(key);
|
|
2061
|
+
}
|
|
2062
|
+
};
|
|
2063
|
+
var ModelMetadataKey = Symbol("db:model");
|
|
2064
|
+
function DatabaseModel(collection) {
|
|
2065
|
+
return (target, propertyKey) => {
|
|
2066
|
+
const schema = target[propertyKey];
|
|
2067
|
+
const name = String(collection);
|
|
2068
|
+
const model = mongoose2.model(name, schema);
|
|
2069
|
+
Reflect.defineMetadata(ModelMetadataKey, model, target);
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
__name(DatabaseModel, "DatabaseModel");
|
|
2073
|
+
|
|
2074
|
+
// src/mongo/decorators/DatabaseService.ts
|
|
2075
|
+
var ServiceMetadataKey = Symbol("db:serviceKey");
|
|
2076
|
+
function DatabaseService(key) {
|
|
2077
|
+
return (ctor) => {
|
|
2078
|
+
Reflect.defineMetadata(ServiceMetadataKey, key, ctor);
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
__name(DatabaseService, "DatabaseService");
|
|
2082
|
+
|
|
2083
|
+
// src/mongo/BaseService.ts
|
|
2084
|
+
var BaseService = class {
|
|
2085
|
+
static {
|
|
2086
|
+
__name(this, "BaseService");
|
|
2087
|
+
}
|
|
2088
|
+
db;
|
|
2089
|
+
core;
|
|
2090
|
+
model;
|
|
2091
|
+
constructor(db, core) {
|
|
2092
|
+
this.db = db;
|
|
2093
|
+
this.core = core;
|
|
2094
|
+
const ctor = this.constructor;
|
|
2095
|
+
const key = Reflect.getMetadata(ServiceMetadataKey, ctor);
|
|
2096
|
+
if (!key) throw new Error(`Missing @DatabaseService on ${ctor.name}`);
|
|
2097
|
+
const model = Reflect.getMetadata(ModelMetadataKey, ctor);
|
|
2098
|
+
if (!model) throw new Error(`Missing @DatabaseModel on ${ctor.name}`);
|
|
2099
|
+
this.model = model;
|
|
2100
|
+
db._register(key, this);
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
|
|
2104
|
+
// src/mongo/Mongo.ts
|
|
2105
|
+
var Mongo = class extends Plugin {
|
|
2106
|
+
static {
|
|
2107
|
+
__name(this, "Mongo");
|
|
2108
|
+
}
|
|
2109
|
+
core;
|
|
2110
|
+
options;
|
|
2111
|
+
logger = new Logger("MongoDB");
|
|
2112
|
+
isInitialised = false;
|
|
2113
|
+
uri;
|
|
2114
|
+
/**
|
|
2115
|
+
* Map of all loaded services.
|
|
2116
|
+
* Keys come from `@DatabaseService('key')`
|
|
2117
|
+
*/
|
|
2118
|
+
services = {};
|
|
2119
|
+
constructor(core, options) {
|
|
2120
|
+
super(core), this.core = core, this.options = options;
|
|
2121
|
+
this.uri = options.uri;
|
|
2122
|
+
this.core.shutdown.addTask(ShutdownPhase.ExternalResources, "stop-database", async () => await this.stop());
|
|
2123
|
+
}
|
|
2124
|
+
async init() {
|
|
2125
|
+
if (this.isInitialised) return;
|
|
2126
|
+
this.isInitialised = true;
|
|
2127
|
+
await this.connect();
|
|
2128
|
+
await this.loadServices();
|
|
2129
|
+
}
|
|
2130
|
+
async stop() {
|
|
2131
|
+
await this.disconnect();
|
|
2132
|
+
}
|
|
2133
|
+
async connect() {
|
|
2134
|
+
await mongoose2.connect(this.uri, {
|
|
2135
|
+
dbName: this.options.dbName,
|
|
2136
|
+
...Globals.isProduction && {
|
|
2137
|
+
tls: true,
|
|
2138
|
+
ssl: true
|
|
2139
|
+
}
|
|
2140
|
+
}).then((i) => this.logger.info(`Connected to MongoDB: ${chalk6.bold.magenta(i.connection.name)}`)).catch((err) => throwCustomError(err, "Could not connect to MongoDB", DatabaseConnectionFailure));
|
|
2141
|
+
}
|
|
2142
|
+
async disconnect() {
|
|
2143
|
+
await mongoose2.disconnect().then(() => this.logger.info(chalk6.red.bold("Disconnected from MongoDB"))).catch((err) => this.logger.error(`Could not disconnect from MongoDB: ${err.message}`));
|
|
2144
|
+
}
|
|
2145
|
+
async loadServices() {
|
|
2146
|
+
const servicesDir = this.options.servicesDir;
|
|
2147
|
+
this.logger.info(chalk6.bold(servicesDir));
|
|
2148
|
+
await traverseDirectory(servicesDir, (_full, rel, mod) => {
|
|
2149
|
+
for (const Service of Object.values(mod)) {
|
|
2150
|
+
if (this.isServiceClass(Service)) {
|
|
2151
|
+
const instance = new Service(this, this.core);
|
|
2152
|
+
this.logger.info(`${chalk6.italic("Registered")} ${chalk6.bold.yellow(instance.constructor.name)} from ${chalk6.gray(rel)}`);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
this.logger.info(`${chalk6.bold.green("Loaded")}: ${chalk6.magenta(Object.keys(this.services).length)} services`);
|
|
2157
|
+
}
|
|
2158
|
+
isServiceClass(obj) {
|
|
2159
|
+
return typeof obj === "function" && obj.prototype instanceof BaseService && Reflect.hasMetadata(ServiceMetadataKey, obj);
|
|
2160
|
+
}
|
|
2161
|
+
_register(key, instance) {
|
|
2162
|
+
this.services[key] = instance;
|
|
2163
|
+
}
|
|
2164
|
+
};
|
|
2165
|
+
|
|
2166
|
+
// src/mongo/decorators/DBCatchable.ts
|
|
2167
|
+
function DBCatchable(errorMessage) {
|
|
2168
|
+
return function(_target, _propertyKey, descriptor) {
|
|
2169
|
+
const originalMethod = descriptor.value;
|
|
2170
|
+
descriptor.value = async function(...args) {
|
|
2171
|
+
if (!originalMethod) {
|
|
2172
|
+
throw new Error("Method not found");
|
|
2173
|
+
}
|
|
2174
|
+
try {
|
|
2175
|
+
return await originalMethod.apply(this, args);
|
|
2176
|
+
} catch (error) {
|
|
2177
|
+
if (!(error instanceof CustomError)) {
|
|
2178
|
+
throwCustomError(error, errorMessage, DatabaseError);
|
|
2179
|
+
} else {
|
|
2180
|
+
throw error;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
__name(DBCatchable, "DBCatchable");
|
|
2187
|
+
|
|
2188
|
+
export { AutocompleteHandler, AutocompleteRoute, BaseErrorEmbed, BaseService, Bot, BuilderComponent, ButtonRoute, Catchable, Checkable, CommandMetadataKey, ContextMenuRoute, CooldownManager, CoordinatedShutdown, CoordinatedStartup, CustomError, DBCatchable, DatabaseModel, DatabaseService, EventCatchable, EventHandler, EventMetadataKey, Globals, HealthCheck, HookController, HookEmitter, HookHandler, HookMetadataKey, InteractionHandler, InteractionMetadataKey, InteractionMiddleware, InteractionRoutes, Logger, ModalComponent, ModalRoute, ModelMetadataKey, Mongo, Pluggable, Plugin, RegisterCommand, RegisterEvent, RegisterHook, RowComponent, Seedcord, SelectMenuRoute, SelectMenuType, ServiceMetadataKey, ShutdownPhase, SlashRoute, StartupPhase, WebhookLog, currentTime, formatWord, fyShuffle, generateAsciiTable, isTsOrJsFile, longestStringLength, numberFixer, ordinal, percentage, prettify, prettyDifference, throwCustomError, traverseDirectory };
|
|
2189
|
+
//# sourceMappingURL=index.mjs.map
|
|
2190
|
+
//# sourceMappingURL=index.mjs.map
|