seedcord 0.7.0 → 0.8.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/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import 'reflect-metadata';
2
- import { Logger, ShutdownPhase, CoordinatedShutdown, CoordinatedStartup, HealthCheck, StartupPhase } from '@seedcord/services';
2
+ import { Logger, StrictEventEmitter, SeedcordError, SeedcordErrorCode, ShutdownPhase, CoordinatedShutdown, CoordinatedStartup, HealthCheck, StartupPhase, SeedcordTypeError } from '@seedcord/services';
3
3
  export * from '@seedcord/services';
4
4
  import chalk3 from 'chalk';
5
- import { SeparatorBuilder, SectionBuilder, MediaGalleryBuilder, FileBuilder, TextDisplayBuilder, ContainerBuilder, RoleSelectMenuBuilder, MentionableSelectMenuBuilder, ChannelSelectMenuBuilder, UserSelectMenuBuilder, StringSelectMenuOptionBuilder, StringSelectMenuBuilder, ButtonBuilder, FileUploadBuilder, TextInputBuilder, LabelBuilder, ModalBuilder, EmbedBuilder, SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandBuilder, ContextMenuCommandBuilder, SlashCommandBuilder, InteractionContextType, ActionRowBuilder, Collection, MessageFlags, Events, Client, WebhookClient, AttachmentBuilder, SeparatorSpacingSize, DiscordAPIError, SnowflakeUtil, Role, PermissionFlagsBits, ChatInputCommandInteraction, AutocompleteInteraction, Message, TextChannel, Guild, RESTJSONErrorCodes, Colors } from 'discord.js';
5
+ import { SeparatorBuilder, SectionBuilder, MediaGalleryBuilder, FileBuilder, TextDisplayBuilder, ContainerBuilder, RoleSelectMenuBuilder, MentionableSelectMenuBuilder, ChannelSelectMenuBuilder, UserSelectMenuBuilder, StringSelectMenuOptionBuilder, StringSelectMenuBuilder, ButtonBuilder, FileUploadBuilder, TextInputBuilder, LabelBuilder, ModalBuilder, EmbedBuilder, SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandBuilder, ContextMenuCommandBuilder, SlashCommandBuilder, InteractionContextType, ActionRowBuilder, Collection, MessageFlags, Events, Client, WebhookClient, AttachmentBuilder, SeparatorSpacingSize, DiscordAPIError, SnowflakeUtil, Role, PermissionFlagsBits, ChatInputCommandInteraction, AutocompleteInteraction, Message, TextChannel, Guild, RESTJSONErrorCodes, Colors, ComponentType } from 'discord.js';
6
6
  import { Envapt } from 'envapt';
7
7
  import { hexToNumber, traverseDirectory, filterCirculars, prettify } from '@seedcord/utils';
8
8
  export * from '@seedcord/utils';
@@ -11,8 +11,6 @@ import { EventEmitter } from 'events';
11
11
 
12
12
  var __defProp = Object.defineProperty;
13
13
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
14
-
15
- // src/bot/decorators/Command.ts
16
14
  var CommandMetadataKey = Symbol("command:metadata");
17
15
  function RegisterCommand(scope, guilds = []) {
18
16
  return (ctor) => {
@@ -22,6 +20,20 @@ function RegisterCommand(scope, guilds = []) {
22
20
  scope,
23
21
  guilds
24
22
  };
23
+ const existingMeta = Reflect.getOwnMetadata(CommandMetadataKey, ctor);
24
+ if (existingMeta) {
25
+ throw new SeedcordError(SeedcordErrorCode.DecoratorCommandAlreadyRegistered, [
26
+ ctor.name,
27
+ existingMeta.scope,
28
+ scope
29
+ ]);
30
+ }
31
+ if (scope === "global" && guilds.length > 0) {
32
+ throw new SeedcordError(SeedcordErrorCode.DecoratorCommandGlobalWithGuilds);
33
+ }
34
+ if (scope === "guild" && (!Array.isArray(guilds) || guilds.length === 0)) {
35
+ throw new SeedcordError(SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds);
36
+ }
25
37
  Reflect.defineMetadata(CommandMetadataKey, meta, ctor);
26
38
  };
27
39
  }
@@ -268,24 +280,20 @@ var CommandRegistry = class {
268
280
  await guild.commands.set(commands);
269
281
  const tag = commands.length === 1 ? "command" : "commands";
270
282
  this.logger.info(`${chalk3.bold.green("Configured")} ${chalk3.magenta.bold(commands.length)} ${tag} for guild ${chalk3.bold.yellow(guild.name)}`);
271
- this.logger.info(` \u2192 ${commands.map((command) => chalk3.bold.cyan(command.name)).join(", ")}`);
283
+ this.logger.info(`\u2192 ${commands.map((command) => chalk3.bold.cyan(command.name)).join(", ")}`);
272
284
  }
273
285
  }
274
286
  };
275
287
 
276
288
  // src/bot/decorators/Events.ts
277
289
  var EventMetadataKey = Symbol("event:metadata");
278
- function RegisterEvent(events, options) {
290
+ function RegisterEvent(...defs) {
279
291
  return function(constructor) {
280
292
  const saved = Reflect.getMetadata(EventMetadataKey, constructor);
281
293
  const existing = saved ?? [];
282
- const toStore = Array.isArray(events) ? events : [
283
- events
284
- ];
285
- const frequency = options?.frequency ?? "on";
286
- const entries = toStore.map((event) => ({
294
+ const entries = defs.map(([event, options]) => ({
287
295
  event,
288
- frequency
296
+ frequency: options?.frequency ?? "on"
289
297
  }));
290
298
  Reflect.defineMetadata(EventMetadataKey, [
291
299
  ...existing,
@@ -294,8 +302,6 @@ function RegisterEvent(events, options) {
294
302
  };
295
303
  }
296
304
  __name(RegisterEvent, "RegisterEvent");
297
-
298
- // src/bot/decorators/Middlewares.ts
299
305
  var MiddlewareType = /* @__PURE__ */ (function(MiddlewareType2) {
300
306
  MiddlewareType2["Interaction"] = "middleware:interaction";
301
307
  MiddlewareType2["Event"] = "middleware:event";
@@ -306,10 +312,10 @@ function Middleware(type, priority = 0, options = {}) {
306
312
  return (ctor) => {
307
313
  const normalizedPriority = Number(priority);
308
314
  if (!Number.isFinite(normalizedPriority)) {
309
- throw new TypeError("Middleware priority must be a finite number");
315
+ throw new SeedcordTypeError(SeedcordErrorCode.DecoratorInvalidMiddlewarePriority);
310
316
  }
311
- if (type === "middleware:interaction" && options.events?.length) {
312
- throw new Error("Interaction middleware cannot specify event filters");
317
+ if (type === "middleware:interaction" && Array.isArray(options.events) && options.events.length > 0) {
318
+ throw new SeedcordError(SeedcordErrorCode.DecoratorInteractionEventFilter);
313
319
  }
314
320
  const metadata = {
315
321
  priority: normalizedPriority,
@@ -322,8 +328,6 @@ function Middleware(type, priority = 0, options = {}) {
322
328
  };
323
329
  }
324
330
  __name(Middleware, "Middleware");
325
-
326
- // src/interfaces/Handler.ts
327
331
  var BaseHandler = class {
328
332
  static {
329
333
  __name(this, "BaseHandler");
@@ -334,10 +338,18 @@ var BaseHandler = class {
334
338
  errored = false;
335
339
  event;
336
340
  args = [];
341
+ logger;
337
342
  constructor(event, core, args) {
338
343
  this.core = core;
339
344
  this.event = event;
340
345
  this.args = args ?? [];
346
+ this.logger = new Logger(this.constructor.name);
347
+ this.populate();
348
+ }
349
+ /**
350
+ * Populates the handler with necessary data before execution. Override this method in your handler classes to customize population logic. This method is called at the end of the constructor before all async operations.
351
+ */
352
+ populate() {
341
353
  }
342
354
  hasChecks() {
343
355
  return this.checkable;
@@ -548,12 +560,16 @@ var EventController = class {
548
560
  }
549
561
  attachToClient() {
550
562
  for (const [eventName, handlerEntries] of this.eventMap) {
551
- this.logger.debug(`Attaching ${chalk3.bold.green(eventName)} to ${chalk3.bold.yellow(this.core.bot.client.user?.username)}`);
563
+ this.logger.debug(`Attaching ${chalk3.bold.green(eventName)} to the client with ${chalk3.gray(handlerEntries.length)} handler(s)`);
552
564
  for (const entry of handlerEntries) {
553
565
  const register = entry.frequency === "once" ? this.core.bot.client.once.bind(this.core.bot.client) : this.core.bot.client.on.bind(this.core.bot.client);
554
566
  register(eventName, (...args) => {
567
+ this.core.bot.emit("any:event", eventName, ...args);
555
568
  void (async () => {
556
- await this.processEvent(eventName, args, entry.ctor);
569
+ await this.processEvent(eventName, args, entry.ctor).catch((err) => {
570
+ this.logger.error(`[${chalk3.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
571
+ this.core.bot.emit("error:unhandled:event", err);
572
+ });
557
573
  })();
558
574
  });
559
575
  }
@@ -690,7 +706,7 @@ function buildSlashRoute(arg1, arg2, arg3) {
690
706
  group = arg1.options.getSubcommandGroup(false) ?? void 0;
691
707
  sub = arg1.options.getSubcommand(false) ?? void 0;
692
708
  } else {
693
- throw new TypeError("Invalid argument passed to buildSlashRoute");
709
+ throw new SeedcordTypeError(SeedcordErrorCode.UtilInvalidSlashRouteArgument);
694
710
  }
695
711
  if (sub && group) return `${command}/${group}/${sub}`;
696
712
  if (sub) return `${command}/${sub}`;
@@ -793,7 +809,7 @@ function Catchable(options) {
793
809
  const originalMethod = descriptor.value;
794
810
  descriptor.value = async function(...args) {
795
811
  const interaction = this.getEvent();
796
- if (!originalMethod) throw new Error("Method not found");
812
+ if (!originalMethod) throw new SeedcordError(SeedcordErrorCode.DecoratorMethodNotFound);
797
813
  try {
798
814
  await originalMethod.apply(this, args);
799
815
  } catch (error) {
@@ -1010,8 +1026,10 @@ var InteractionController = class {
1010
1026
  }
1011
1027
  attachToClient() {
1012
1028
  this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
1029
+ this.core.bot.emit("any:interaction", interaction);
1013
1030
  this.handleInteraction(interaction).catch((err) => {
1014
1031
  this.logger.error(`[${chalk3.bold.red("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
1032
+ this.core.bot.emit("error:unhandled:interaction", err);
1015
1033
  });
1016
1034
  });
1017
1035
  }
@@ -1032,7 +1050,11 @@ var InteractionController = class {
1032
1050
  }
1033
1051
  async processInteraction(interaction, extractKey, getHandler, args) {
1034
1052
  const key = extractKey(interaction);
1035
- if (this.keysToIgnore.has(key)) return;
1053
+ if ([
1054
+ ...this.keysToIgnore
1055
+ ].some((pattern) => typeof pattern === "string" ? pattern === key : pattern.test(key))) {
1056
+ return;
1057
+ }
1036
1058
  for (const { ctor } of this.middlewares) {
1037
1059
  const middleware = new ctor(interaction, this.core, args);
1038
1060
  if (middleware.hasChecks()) await middleware.runChecks();
@@ -1129,16 +1151,16 @@ var InteractionController = class {
1129
1151
  await this.processInteraction(interaction, () => autocompleteKey, (key) => this.autocompleteMap.get(key));
1130
1152
  }
1131
1153
  };
1132
- var Plugin = class {
1154
+ var Plugin = class extends StrictEventEmitter {
1133
1155
  static {
1134
1156
  __name(this, "Plugin");
1135
1157
  }
1136
1158
  pluggable;
1137
1159
  constructor(pluggable) {
1138
- this.pluggable = pluggable;
1160
+ super(), this.pluggable = pluggable;
1139
1161
  }
1140
1162
  };
1141
- var Pluggable = class _Pluggable {
1163
+ var Pluggable = class _Pluggable extends StrictEventEmitter {
1142
1164
  static {
1143
1165
  __name(this, "Pluggable");
1144
1166
  }
@@ -1147,6 +1169,7 @@ var Pluggable = class _Pluggable {
1147
1169
  startup;
1148
1170
  static PLUGIN_INIT_TIMEOUT_MS = 15e3;
1149
1171
  constructor(shutdown, startup) {
1172
+ super();
1150
1173
  this.shutdown = shutdown;
1151
1174
  this.startup = startup;
1152
1175
  }
@@ -1171,15 +1194,21 @@ var Pluggable = class _Pluggable {
1171
1194
  * @param startupPhase - When during startup to initialize this plugin ({@link StartupPhase})
1172
1195
  * @param args - Additional arguments to pass to the plugin constructor
1173
1196
  * @returns This instance with the plugin attached as a typed property
1174
- * @throws An {@link Error} When called after initialization or if key already exists
1197
+ * @throws A {@link SeedcordError} When called after initialization or if key already exists
1175
1198
  * @example
1176
1199
  * ```typescript
1177
1200
  * seedcord.attach('db', Mongo, StartupPhase.Configuration, { uri: 'mongodb://...', name: 'seedcord', dir: ... })
1178
1201
  * ```
1179
1202
  */
1180
1203
  attach(key, Plugin2, startupPhase, ...args) {
1181
- if (this.isInitialized) throw new Error("Cannot attach a plugin after initialization.");
1182
- if (this[key]) throw new Error(`Plugin with key "${key}" already exists.`);
1204
+ if (this.isInitialized) {
1205
+ throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);
1206
+ }
1207
+ if (this[key]) {
1208
+ throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [
1209
+ key
1210
+ ]);
1211
+ }
1183
1212
  const instance = new Plugin2(this, ...args);
1184
1213
  const entry = {
1185
1214
  [key]: instance
@@ -1192,6 +1221,8 @@ var Pluggable = class _Pluggable {
1192
1221
  return Object.assign(this, entry);
1193
1222
  }
1194
1223
  };
1224
+ var emojiStorage = {};
1225
+ var Emojis = emojiStorage;
1195
1226
  var EmojiInjector = class {
1196
1227
  static {
1197
1228
  __name(this, "EmojiInjector");
@@ -1202,6 +1233,7 @@ var EmojiInjector = class {
1202
1233
  this.core = core;
1203
1234
  }
1204
1235
  async init() {
1236
+ this.clearEmojis();
1205
1237
  if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
1206
1238
  this.logger.info(`${chalk3.bold.green("Loaded")}: ${chalk3.magenta.bold("0")} emojis`);
1207
1239
  return;
@@ -1210,15 +1242,24 @@ var EmojiInjector = class {
1210
1242
  await this.core.bot.client.application?.emojis.fetch();
1211
1243
  let foundCount = 0;
1212
1244
  Object.entries(configEmojis).forEach(([key, emojiName]) => {
1245
+ if (typeof emojiName !== "string") {
1246
+ this.logger.warn(`${chalk3.bold.yellow("Invalid")}: ${chalk3.magenta.bold(String(key))} (expected string, received ${typeof emojiName})`);
1247
+ return;
1248
+ }
1213
1249
  const emoji = this.core.bot.client.application?.emojis.cache.find((e) => e.name === emojiName);
1214
1250
  if (emoji) {
1215
- configEmojis[key] = `<${emoji.identifier}>`;
1251
+ emojiStorage[key] = `<${emoji.identifier}>`;
1216
1252
  foundCount++;
1217
- this.logger.debug(`${chalk3.bold.green("Found")}: ${chalk3.magenta.bold(emojiName)} (${emoji.id})`);
1253
+ return;
1218
1254
  }
1255
+ emojiStorage[key] = emojiName;
1256
+ this.logger.warn(`${chalk3.bold.yellow("Missing")}: ${chalk3.magenta.bold(emojiName)} (using configured value)`);
1219
1257
  });
1220
1258
  this.logger.info(`${chalk3.bold.green("Loaded")}: ${chalk3.magenta.bold(foundCount)} emojis`);
1221
1259
  }
1260
+ clearEmojis() {
1261
+ for (const key of Object.keys(emojiStorage)) Reflect.deleteProperty(emojiStorage, key);
1262
+ }
1222
1263
  };
1223
1264
 
1224
1265
  // src/bot/Bot.ts
@@ -1245,6 +1286,7 @@ var Bot = class extends Plugin {
1245
1286
  events;
1246
1287
  commands;
1247
1288
  emojiInjector;
1289
+ emojis = Emojis;
1248
1290
  constructor(core) {
1249
1291
  super(core), this.core = core;
1250
1292
  this._client = new Client(core.config.bot.clientOptions);
@@ -1296,11 +1338,16 @@ var Bot = class extends Plugin {
1296
1338
  get client() {
1297
1339
  return this._client;
1298
1340
  }
1341
+ emit(event, ...args) {
1342
+ return super.emit(event, ...args);
1343
+ }
1299
1344
  };
1300
1345
  _ts_decorate4([
1301
1346
  Envapt("DISCORD_BOT_TOKEN", {
1302
1347
  converter(raw, _fallback) {
1303
- if (typeof raw !== "string") throw new Error("Missing DISCORD_BOT_TOKEN");
1348
+ if (typeof raw !== "string") {
1349
+ throw new SeedcordError(SeedcordErrorCode.ConfigMissingDiscordToken);
1350
+ }
1304
1351
  return raw;
1305
1352
  }
1306
1353
  }),
@@ -1363,6 +1410,25 @@ function _ts_metadata5(k, v) {
1363
1410
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1364
1411
  }
1365
1412
  __name(_ts_metadata5, "_ts_metadata");
1413
+ function webhookUrlValidator(raw, _fallback) {
1414
+ if (raw === null) {
1415
+ throw new SeedcordError(SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing);
1416
+ }
1417
+ if (typeof raw !== "string") {
1418
+ throw new SeedcordError(SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
1419
+ }
1420
+ const value = raw.trim();
1421
+ if (value === "") {
1422
+ throw new SeedcordError(SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing);
1423
+ }
1424
+ const pattern = String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`;
1425
+ const discordWebhookRegex = new RegExp(pattern);
1426
+ if (!URL.canParse(value) || !discordWebhookRegex.test(value)) {
1427
+ throw new SeedcordError(SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
1428
+ }
1429
+ return value;
1430
+ }
1431
+ __name(webhookUrlValidator, "webhookUrlValidator");
1366
1432
  var UnknownException = class _UnknownException extends WebhookLog {
1367
1433
  static {
1368
1434
  __name(this, "UnknownException");
@@ -1402,11 +1468,7 @@ var UnknownException = class _UnknownException extends WebhookLog {
1402
1468
  };
1403
1469
  _ts_decorate5([
1404
1470
  Envapt("UNKNOWN_EXCEPTION_WEBHOOK_URL", {
1405
- converter(raw, _fallback) {
1406
- if (!raw) throw new Error("Missing UNKNOWN_EXCEPTION_WEBHOOK_URL");
1407
- if (!URL.canParse(String(raw))) throw new Error("Invalid UNKNOWN_EXCEPTION_WEBHOOK_URL");
1408
- return raw;
1409
- }
1471
+ converter: /* @__PURE__ */ __name((raw, fallback) => webhookUrlValidator(raw), "converter")
1410
1472
  }),
1411
1473
  _ts_metadata5("design:type", String)
1412
1474
  ], UnknownException, "unknownExceptionWebhookUrl", void 0);
@@ -1595,7 +1657,7 @@ var Seedcord = class _Seedcord extends Pluggable {
1595
1657
  * Creates a new Seedcord instance
1596
1658
  *
1597
1659
  * @param config - Bot configuration including paths and Discord client options
1598
- * @throws An {@link Error} When attempting to create multiple instances (singleton)
1660
+ * @throws An {@link SeedcordError} When attempting to create multiple instances (singleton)
1599
1661
  */
1600
1662
  constructor(config) {
1601
1663
  const shutdown = new CoordinatedShutdown();
@@ -1604,7 +1666,7 @@ var Seedcord = class _Seedcord extends Pluggable {
1604
1666
  this.shutdown = shutdown;
1605
1667
  this.startup = startup;
1606
1668
  if (_Seedcord.isInstantiated) {
1607
- throw new Error("Seedcord can only be instantiated once. Use the existing instance instead.");
1669
+ throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);
1608
1670
  }
1609
1671
  _Seedcord.isInstantiated = true;
1610
1672
  this.effects = new EffectsRegistry(this);
@@ -1656,7 +1718,7 @@ function EventCatchable(log) {
1656
1718
  return function(_target, _prop, descriptor) {
1657
1719
  const original = descriptor.value;
1658
1720
  descriptor.value = async function(...args) {
1659
- if (!original) throw new Error("Method not found");
1721
+ if (!original) throw new SeedcordError(SeedcordErrorCode.DecoratorMethodNotFound);
1660
1722
  try {
1661
1723
  await original.apply(this, args);
1662
1724
  } catch (err) {
@@ -1680,6 +1742,206 @@ function EventCatchable(log) {
1680
1742
  };
1681
1743
  }
1682
1744
  __name(EventCatchable, "EventCatchable");
1745
+ var resolveFactory = /* @__PURE__ */ __name(async (input, ctx) => typeof input === "function" ? await input(ctx) : input, "resolveFactory");
1746
+ var isV2 = /* @__PURE__ */ __name((opts) => "container" in opts, "isV2");
1747
+ var usesResolver = /* @__PURE__ */ __name((opts) => "resolveDecision" in opts, "usesResolver");
1748
+ var usesCustomIds = /* @__PURE__ */ __name((opts) => "confirmCustomIds" in opts, "usesCustomIds");
1749
+ var isMessageComponentIx = /* @__PURE__ */ __name((ix) => "deferUpdate" in ix && "customId" in ix, "isMessageComponentIx");
1750
+ var buildPrompt = /* @__PURE__ */ __name(async (opts, ctx) => {
1751
+ if (isV2(opts)) {
1752
+ const container = await resolveFactory(opts.container, ctx);
1753
+ return {
1754
+ flags: "IsComponentsV2",
1755
+ components: [
1756
+ container.component
1757
+ ]
1758
+ };
1759
+ }
1760
+ const prompt = await resolveFactory(opts.prompt, ctx);
1761
+ const rows = await resolveFactory(opts.rows, ctx);
1762
+ const components = [
1763
+ ...rows
1764
+ ];
1765
+ const payload = {
1766
+ components
1767
+ };
1768
+ if (typeof prompt === "string") payload.content = prompt;
1769
+ else payload.embeds = [
1770
+ prompt.component
1771
+ ];
1772
+ return payload;
1773
+ }, "buildPrompt");
1774
+ var clearedPayload = /* @__PURE__ */ __name((opts) => isV2(opts) ? {
1775
+ flags: "IsComponentsV2",
1776
+ components: []
1777
+ } : {
1778
+ components: []
1779
+ }, "clearedPayload");
1780
+ var decide = /* @__PURE__ */ __name(async (opts, i) => {
1781
+ if (usesResolver(opts)) return opts.resolveDecision(i);
1782
+ if (usesCustomIds(opts)) {
1783
+ const { confirmCustomIds, cancelCustomIds = [] } = opts;
1784
+ if (confirmCustomIds.includes(i.customId)) return true;
1785
+ if (cancelCustomIds.includes(i.customId)) return false;
1786
+ }
1787
+ return false;
1788
+ }, "decide");
1789
+ var shouldDefer = /* @__PURE__ */ __name((ix, opts, isSlash, isContext) => {
1790
+ if (ix.deferred) return false;
1791
+ if (opts.defer === false) return false;
1792
+ return isSlash || isContext;
1793
+ }, "shouldDefer");
1794
+ var maybeDefer = /* @__PURE__ */ __name(async (ix, opts, isSlash, isContext) => {
1795
+ if (!shouldDefer(ix, opts, isSlash, isContext)) return;
1796
+ if (isSlash || isContext) {
1797
+ const { ephemeral = true } = opts;
1798
+ const flags = ephemeral ? {
1799
+ flags: "Ephemeral"
1800
+ } : void 0;
1801
+ if (flags) await ix.deferReply(flags);
1802
+ else await ix.deferReply();
1803
+ } else if (isMessageComponentIx(ix)) {
1804
+ await ix.deferUpdate();
1805
+ } else {
1806
+ await ix.deferReply().catch(() => void 0);
1807
+ }
1808
+ }, "maybeDefer");
1809
+ var sendPrompt = /* @__PURE__ */ __name(async (ix, payload, ephemeral, isSlash, isContext) => {
1810
+ if (isSlash || isContext) {
1811
+ if (ix.deferred) return ix.editReply(payload);
1812
+ const reply = {
1813
+ ...payload,
1814
+ ...ephemeral ? {
1815
+ flags: "Ephemeral"
1816
+ } : {}
1817
+ };
1818
+ await ix.reply(reply);
1819
+ return ix.fetchReply();
1820
+ }
1821
+ const follow = {
1822
+ ...payload,
1823
+ ...ephemeral ? {
1824
+ flags: "Ephemeral"
1825
+ } : {}
1826
+ };
1827
+ return ix.followUp(follow);
1828
+ }, "sendPrompt");
1829
+ var awaitComponent = /* @__PURE__ */ __name(async (msg, original, opts) => {
1830
+ const componentType = opts.componentType ?? ComponentType.Button;
1831
+ const timeoutMs = opts.timeoutMs ?? 1e4;
1832
+ try {
1833
+ const button = await msg.awaitMessageComponent({
1834
+ componentType,
1835
+ time: timeoutMs,
1836
+ filter: /* @__PURE__ */ __name((c) => {
1837
+ if (c.user.id !== original.user.id) return false;
1838
+ if (usesCustomIds(opts)) {
1839
+ const { confirmCustomIds, cancelCustomIds = [] } = opts;
1840
+ return confirmCustomIds.includes(c.customId) || cancelCustomIds.includes(c.customId);
1841
+ }
1842
+ return true;
1843
+ }, "filter")
1844
+ });
1845
+ return {
1846
+ button,
1847
+ timedOut: false
1848
+ };
1849
+ } catch {
1850
+ return {
1851
+ button: null,
1852
+ timedOut: true
1853
+ };
1854
+ }
1855
+ }, "awaitComponent");
1856
+ var clearUi = /* @__PURE__ */ __name(async (ix, msg, opts, isSlash) => {
1857
+ if (!isSlash) {
1858
+ try {
1859
+ await msg.edit(clearedPayload(opts));
1860
+ } catch {
1861
+ await ix.deleteReply(msg).catch(() => void 0);
1862
+ }
1863
+ return;
1864
+ }
1865
+ await ix.editReply(clearedPayload(opts)).catch(() => void 0);
1866
+ }, "clearUi");
1867
+ var normalizeClassicOutcome = /* @__PURE__ */ __name((payload) => ({
1868
+ ...payload,
1869
+ components: payload.components ?? []
1870
+ }), "normalizeClassicOutcome");
1871
+ var outcomeReplacement = /* @__PURE__ */ __name(async (opts, ctx, confirmed, timedOut) => {
1872
+ const outcomes = opts.outcomeUi;
1873
+ if (isV2(opts)) {
1874
+ const v2Outcomes = outcomes;
1875
+ if (timedOut) return await resolveFactory(v2Outcomes.onTimeout, ctx);
1876
+ if (!confirmed) return await resolveFactory(v2Outcomes.onCancel, ctx);
1877
+ if (v2Outcomes.onConfirm) return await resolveFactory(v2Outcomes.onConfirm, ctx);
1878
+ return null;
1879
+ }
1880
+ const classicOutcomes = outcomes;
1881
+ if (timedOut) return normalizeClassicOutcome(await resolveFactory(classicOutcomes.onTimeout, ctx));
1882
+ if (!confirmed) return normalizeClassicOutcome(await resolveFactory(classicOutcomes.onCancel, ctx));
1883
+ if (classicOutcomes.onConfirm) {
1884
+ return normalizeClassicOutcome(await resolveFactory(classicOutcomes.onConfirm, ctx));
1885
+ }
1886
+ return null;
1887
+ }, "outcomeReplacement");
1888
+ function Confirmable(question, options) {
1889
+ return function(_target, _propertyKey, descriptor) {
1890
+ const original = descriptor.value;
1891
+ descriptor.value = async function(...args) {
1892
+ if (!original) throw new SeedcordError(SeedcordErrorCode.DecoratorMethodNotFound);
1893
+ const ix = this.getEvent();
1894
+ const isSlash = ix.isChatInputCommand();
1895
+ const isContext = ix.isContextMenuCommand();
1896
+ const { ephemeral = true } = options;
1897
+ await maybeDefer(ix, options, isSlash, isContext);
1898
+ const q = typeof question === "function" ? await question.apply(this) : question;
1899
+ const ctx = {
1900
+ handler: this,
1901
+ interaction: ix,
1902
+ question: q
1903
+ };
1904
+ const prompt = await buildPrompt(options, ctx);
1905
+ const promptMsg = await sendPrompt(ix, prompt, ephemeral, isSlash, isContext);
1906
+ const { button, timedOut } = await awaitComponent(promptMsg, ix, options);
1907
+ let confirmed = false;
1908
+ if (button) {
1909
+ await button.deferUpdate().catch(() => void 0);
1910
+ confirmed = await decide(options, button);
1911
+ }
1912
+ const replacement = await outcomeReplacement(options, ctx, confirmed, timedOut);
1913
+ if (replacement) {
1914
+ if (isSlash || isContext) {
1915
+ await ix.editReply(replacement).catch(() => void 0);
1916
+ } else {
1917
+ await promptMsg.edit(replacement).catch(async () => {
1918
+ await ix.deleteReply(promptMsg).catch(() => void 0);
1919
+ await ix.followUp(replacement).catch(() => void 0);
1920
+ });
1921
+ }
1922
+ } else {
1923
+ await clearUi(ix, promptMsg, options, isSlash);
1924
+ }
1925
+ if (options.onResolved) {
1926
+ await options.onResolved({
1927
+ confirmed,
1928
+ timedOut,
1929
+ handler: this,
1930
+ interaction: ix,
1931
+ question: q,
1932
+ ...button ? {
1933
+ button
1934
+ } : {}
1935
+ });
1936
+ }
1937
+ if (confirmed) {
1938
+ await original.apply(this, args);
1939
+ }
1940
+ };
1941
+ return descriptor;
1942
+ };
1943
+ }
1944
+ __name(Confirmable, "Confirmable");
1683
1945
 
1684
1946
  // src/bot/errors/Channels.ts
1685
1947
  var ChannelNotFoundError = class extends CustomError {
@@ -2045,7 +2307,9 @@ function checkPermissions(client, roleOrChannel, scope = "all", inverse = false)
2045
2307
  if (roleOrChannel instanceof Role) {
2046
2308
  permissions = roleOrChannel.permissions;
2047
2309
  } else {
2048
- if (!client.user) throw new Error("Client user is not available");
2310
+ if (!client.user) {
2311
+ throw new SeedcordError(SeedcordErrorCode.CoreClientUserUnavailable);
2312
+ }
2049
2313
  permissions = roleOrChannel.permissionsFor(client.user, true);
2050
2314
  }
2051
2315
  if (!permissions) {
@@ -2064,12 +2328,16 @@ function checkPermissions(client, roleOrChannel, scope = "all", inverse = false)
2064
2328
  }
2065
2329
  }
2066
2330
  __name(checkPermissions, "checkPermissions");
2067
-
2068
- // src/bot/utilities/roles/getBotRole.ts
2069
2331
  function getBotRole(client, guild) {
2070
- if (!client.user) throw new Error("Client user is not available");
2332
+ if (!client.user) {
2333
+ throw new SeedcordError(SeedcordErrorCode.CoreClientUserUnavailable);
2334
+ }
2071
2335
  const botRole = guild.roles.botRoleFor(client.user);
2072
- if (!botRole) throw new Error("Bot role not found in guild");
2336
+ if (!botRole) {
2337
+ throw new SeedcordError(SeedcordErrorCode.CoreBotRoleMissing, [
2338
+ guild.id
2339
+ ]);
2340
+ }
2073
2341
  return botRole;
2074
2342
  }
2075
2343
  __name(getBotRole, "getBotRole");
@@ -2181,6 +2449,6 @@ async function updateMemberRoles(rolesToAdd, rolesToRemove, member) {
2181
2449
  }
2182
2450
  __name(updateMemberRoles, "updateMemberRoles");
2183
2451
 
2184
- export { AutocompleteHandler, AutocompleteRoute, BaseComponent, BaseErrorEmbed, BaseHandler, Bot, BuilderComponent, BuilderTypes, ButtonRoute, CannotAssignBotRole, CannotSendEmbedsError, Catchable, ChannelNotFoundError, ChannelNotTextChannel, Checkable, CommandMetadataKey, CommandRegistry, ContextMenuRoute, CouldNotFindChannel, CustomError, DatabaseError, EffectMetadataKey, EffectsEmitter, EffectsHandler, EffectsRegistry, EmojiInjector, EventCatchable, EventController, EventHandler, EventMetadataKey, EventMiddleware, GenericError, HasDangerousPermissions, InteractionController, InteractionHandler, InteractionMetadataKey, InteractionMiddleware, InteractionRoutes, Middleware, MiddlewareMetadataKey, MiddlewareType, MissingPermissions, ModalRoute, PERM_GROUPS, PermissionNames, Pluggable, Plugin, RegisterCommand, RegisterEffect, RegisterEvent, RoleDoesNotExist, RoleHigherThanMe, RowComponent, RowTypes, Seedcord, SelectMenuRoute, SelectMenuType, SlashRoute, UnhandledEvent, UnknownException, UserNotFound, UserNotInGuild, WebhookLog, attemptSendDM, buildSlashRoute, checkBotPermissions, checkPermissions, extractErrorResponse, fetchGuildMember, fetchManyGuildMembers, fetchManyUsers, fetchRole, fetchText, fetchUser, getBotRole, hasPermsToAssign, sendInText, throwCustomError, updateMemberRoles };
2452
+ export { AutocompleteHandler, AutocompleteRoute, BaseComponent, BaseErrorEmbed, BaseHandler, Bot, BuilderComponent, BuilderTypes, ButtonRoute, CannotAssignBotRole, CannotSendEmbedsError, Catchable, ChannelNotFoundError, ChannelNotTextChannel, Checkable, CommandMetadataKey, CommandRegistry, Confirmable, ContextMenuRoute, CouldNotFindChannel, CustomError, DatabaseError, EffectMetadataKey, EffectsEmitter, EffectsHandler, EffectsRegistry, EmojiInjector, Emojis, EventCatchable, EventController, EventHandler, EventMetadataKey, EventMiddleware, GenericError, HasDangerousPermissions, InteractionController, InteractionHandler, InteractionMetadataKey, InteractionMiddleware, InteractionRoutes, Middleware, MiddlewareMetadataKey, MiddlewareType, MissingPermissions, ModalRoute, PERM_GROUPS, PermissionNames, Pluggable, Plugin, RegisterCommand, RegisterEffect, RegisterEvent, RoleDoesNotExist, RoleHigherThanMe, RowComponent, RowTypes, Seedcord, SelectMenuRoute, SelectMenuType, SlashRoute, UnhandledEvent, UnknownException, UserNotFound, UserNotInGuild, WebhookLog, attemptSendDM, buildSlashRoute, checkBotPermissions, checkPermissions, extractErrorResponse, fetchGuildMember, fetchManyGuildMembers, fetchManyUsers, fetchRole, fetchText, fetchUser, getBotRole, hasPermsToAssign, sendInText, throwCustomError, updateMemberRoles };
2185
2453
  //# sourceMappingURL=index.mjs.map
2186
2454
  //# sourceMappingURL=index.mjs.map