seedcord 0.9.0 → 0.10.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.cjs CHANGED
@@ -737,7 +737,7 @@ function buildSlashRoute(arg1, arg2, arg3) {
737
737
  }
738
738
  __name(buildSlashRoute, "buildSlashRoute");
739
739
 
740
- // src/bot/errors/Database.ts
740
+ // src/bot/defaults/errors/Database.ts
741
741
  var DatabaseError = class extends CustomError {
742
742
  static {
743
743
  __name(this, "DatabaseError");
@@ -1245,6 +1245,10 @@ var Pluggable = class _Pluggable extends services.StrictEventEmitter {
1245
1245
  }
1246
1246
  };
1247
1247
  var emojiStorage = {};
1248
+ function isEmojiTuple(v) {
1249
+ return Array.isArray(v) && v.length === 2 && typeof v[0] === "string" && typeof v[1] === "string";
1250
+ }
1251
+ __name(isEmojiTuple, "isEmojiTuple");
1248
1252
  var Emojis = emojiStorage;
1249
1253
  var EmojiInjector = class {
1250
1254
  static {
@@ -1264,23 +1268,57 @@ var EmojiInjector = class {
1264
1268
  const configEmojis = this.core.config.bot.emojis;
1265
1269
  await this.core.bot.client.application?.emojis.fetch();
1266
1270
  let foundCount = 0;
1267
- Object.entries(configEmojis).forEach(([key, emojiName]) => {
1268
- if (typeof emojiName !== "string") {
1269
- this.logger.warn(`${chalk3__default.default.bold.yellow("Invalid")}: ${chalk3__default.default.magenta.bold(String(key))} (expected string, received ${typeof emojiName})`);
1270
- return;
1271
+ const entries = Object.entries(configEmojis);
1272
+ for (const [key, value] of entries) {
1273
+ if (isEmojiTuple(value)) {
1274
+ foundCount += this.handleTuple(key, value);
1275
+ continue;
1271
1276
  }
1272
- const emoji = this.core.bot.client.application?.emojis.cache.find((e) => e.name === emojiName);
1273
- if (emoji) {
1274
- emojiStorage[key] = `<${emoji.identifier}>`;
1275
- foundCount++;
1276
- this.logger.debug(`${chalk3__default.default.bold.green("Found")}: ${chalk3__default.default.magenta.bold(emojiName)} (${emoji.id})`);
1277
- return;
1277
+ if (typeof value === "string") {
1278
+ foundCount += this.handleString(key, value);
1279
+ continue;
1278
1280
  }
1279
- emojiStorage[key] = emojiName;
1280
- this.logger.warn(`${chalk3__default.default.bold.yellow("Missing")}: ${chalk3__default.default.magenta.bold(emojiName)} (using configured value)`);
1281
- });
1281
+ this.logger.warn(`${chalk3__default.default.bold.yellow("Invalid")}: ${chalk3__default.default.magenta.bold(String(key))} (expected string or [string, string])`);
1282
+ }
1282
1283
  this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${chalk3__default.default.magenta.bold(foundCount)} emoji(s)`);
1283
1284
  }
1285
+ /**
1286
+ * Handle emoji config values in tuple form: [emojiName, guildId]
1287
+ * Returns 1 when the emoji was found and stored as an emoji object, otherwise 0.
1288
+ */
1289
+ handleTuple(key, value) {
1290
+ const [emojiName, guildId] = value;
1291
+ const guild = this.core.bot.client.guilds.cache.get(guildId);
1292
+ if (!guild) {
1293
+ emojiStorage[key] = emojiName;
1294
+ this.logger.warn(`${chalk3__default.default.bold.yellow("Missing")}: ${chalk3__default.default.magenta.bold(emojiName)} in guild ${chalk3__default.default.gray(guildId)} (guild not in cache or not found, using provided string)`);
1295
+ return 0;
1296
+ }
1297
+ const guildEmoji = guild.emojis.cache.find((e) => e.name === emojiName);
1298
+ if (guildEmoji) {
1299
+ emojiStorage[key] = guildEmoji;
1300
+ this.logger.debug(`${chalk3__default.default.bold.green("Found")}: ${chalk3__default.default.magenta.bold(emojiName)} (${guildEmoji.id}) in guild ${chalk3__default.default.gray(guildId)}`);
1301
+ return 1;
1302
+ }
1303
+ emojiStorage[key] = emojiName;
1304
+ this.logger.warn(`${chalk3__default.default.bold.yellow("Missing")}: ${chalk3__default.default.magenta.bold(emojiName)} in guild ${chalk3__default.default.magenta.bold(guildId)} (using provided string)`);
1305
+ return 0;
1306
+ }
1307
+ /**
1308
+ * Handle emoji config values provided as a simple string (application emoji lookup).
1309
+ * Returns 1 when the emoji was found and stored as an emoji object, otherwise 0.
1310
+ */
1311
+ handleString(key, emojiName) {
1312
+ const appEmoji = this.core.bot.client.application?.emojis.cache.find((e) => e.name === emojiName);
1313
+ if (appEmoji) {
1314
+ emojiStorage[key] = appEmoji;
1315
+ this.logger.debug(`${chalk3__default.default.bold.green("Found")}: ${chalk3__default.default.magenta.bold(emojiName)} (${appEmoji.id})`);
1316
+ return 1;
1317
+ }
1318
+ emojiStorage[key] = emojiName;
1319
+ this.logger.warn(`${chalk3__default.default.bold.yellow("Missing")}: ${chalk3__default.default.magenta.bold(emojiName)} (using provided string)`);
1320
+ return 0;
1321
+ }
1284
1322
  clearEmojis() {
1285
1323
  for (const key of Object.keys(emojiStorage)) Reflect.deleteProperty(emojiStorage, key);
1286
1324
  }
@@ -1378,1106 +1416,1041 @@ _ts_decorate4([
1378
1416
  _ts_metadata4("design:type", String)
1379
1417
  ], Bot.prototype, "botToken", void 0);
1380
1418
 
1381
- // src/effects/decorators/RegisterEffect.ts
1382
- var EffectMetadataKey = Symbol("effect:metadata");
1383
- function RegisterEffect(effect, options) {
1384
- return function(constructor) {
1385
- const meta = {
1386
- effect,
1387
- frequency: options?.frequency
1388
- };
1389
- Reflect.defineMetadata(EffectMetadataKey, meta, constructor);
1419
+ // src/bot/decorators/Checkable.ts
1420
+ function Checkable(ctor) {
1421
+ return class extends ctor {
1422
+ static name = ctor.name;
1423
+ checkable = true;
1390
1424
  };
1391
1425
  }
1392
- __name(RegisterEffect, "RegisterEffect");
1393
-
1394
- // src/effects/EffectsHandler.ts
1395
- var EffectsHandler = class {
1396
- static {
1397
- __name(this, "EffectsHandler");
1398
- }
1399
- data;
1400
- core;
1401
- /**
1402
- * Creates a new effects handler instance.
1403
- *
1404
- * @param data - The effect event data
1405
- * @param core - The core framework instance
1406
- */
1407
- constructor(data, core) {
1408
- this.data = data;
1409
- this.core = core;
1410
- this.data = data;
1411
- this.core = core;
1412
- }
1413
- };
1414
-
1415
- // src/effects/bases/WebhookLog.ts
1416
- var WebhookLog = class extends EffectsHandler {
1417
- static {
1418
- __name(this, "WebhookLog");
1419
- }
1420
- constructor(data, core) {
1421
- super(data, core);
1422
- }
1423
- };
1424
-
1425
- // src/effects/default/UnknownException.ts
1426
- function _ts_decorate5(decorators, target, key, desc) {
1427
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1428
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1429
- 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;
1430
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1431
- }
1432
- __name(_ts_decorate5, "_ts_decorate");
1433
- function _ts_metadata5(k, v) {
1434
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1435
- }
1436
- __name(_ts_metadata5, "_ts_metadata");
1437
- function webhookUrlValidator(raw, _fallback) {
1438
- if (raw === null) {
1439
- throw new services.SeedcordError(services.SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing);
1426
+ __name(Checkable, "Checkable");
1427
+ var resolveFactory = /* @__PURE__ */ __name(async (input, ctx) => typeof input === "function" ? await input(ctx) : input, "resolveFactory");
1428
+ var isV2 = /* @__PURE__ */ __name((opts) => "container" in opts, "isV2");
1429
+ var usesResolver = /* @__PURE__ */ __name((opts) => "resolveDecision" in opts, "usesResolver");
1430
+ var usesCustomIds = /* @__PURE__ */ __name((opts) => "confirmCustomIds" in opts, "usesCustomIds");
1431
+ var isMessageComponentIx = /* @__PURE__ */ __name((ix) => "deferUpdate" in ix && "customId" in ix, "isMessageComponentIx");
1432
+ var buildPrompt = /* @__PURE__ */ __name(async (opts, ctx) => {
1433
+ if (isV2(opts)) {
1434
+ const container = await resolveFactory(opts.container, ctx);
1435
+ return {
1436
+ flags: "IsComponentsV2",
1437
+ components: [
1438
+ container.component
1439
+ ]
1440
+ };
1440
1441
  }
1441
- if (typeof raw !== "string") {
1442
- throw new services.SeedcordError(services.SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
1442
+ const prompt = await resolveFactory(opts.prompt, ctx);
1443
+ const rows = await resolveFactory(opts.rows, ctx);
1444
+ const components = [
1445
+ ...rows
1446
+ ];
1447
+ const payload = {
1448
+ components
1449
+ };
1450
+ if (typeof prompt === "string") payload.content = prompt;
1451
+ else payload.embeds = [
1452
+ prompt.component
1453
+ ];
1454
+ return payload;
1455
+ }, "buildPrompt");
1456
+ var clearedPayload = /* @__PURE__ */ __name((opts) => isV2(opts) ? {
1457
+ flags: "IsComponentsV2",
1458
+ components: []
1459
+ } : {
1460
+ components: []
1461
+ }, "clearedPayload");
1462
+ var decide = /* @__PURE__ */ __name(async (opts, i) => {
1463
+ if (usesResolver(opts)) return opts.resolveDecision(i);
1464
+ if (usesCustomIds(opts)) {
1465
+ const { confirmCustomIds, cancelCustomIds = [] } = opts;
1466
+ if (confirmCustomIds.includes(i.customId)) return true;
1467
+ if (cancelCustomIds.includes(i.customId)) return false;
1443
1468
  }
1444
- const value = raw.trim();
1445
- if (value === "") {
1446
- throw new services.SeedcordError(services.SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing);
1469
+ return false;
1470
+ }, "decide");
1471
+ var shouldDefer = /* @__PURE__ */ __name((ix, opts, isSlash, isContext) => {
1472
+ if (ix.deferred) return false;
1473
+ if (opts.defer === false) return false;
1474
+ return isSlash || isContext;
1475
+ }, "shouldDefer");
1476
+ var maybeDefer = /* @__PURE__ */ __name(async (ix, opts, isSlash, isContext) => {
1477
+ if (!shouldDefer(ix, opts, isSlash, isContext)) return;
1478
+ if (isSlash || isContext) {
1479
+ const { ephemeral = true } = opts;
1480
+ const flags = ephemeral ? {
1481
+ flags: "Ephemeral"
1482
+ } : void 0;
1483
+ if (flags) await ix.deferReply(flags);
1484
+ else await ix.deferReply();
1485
+ } else if (isMessageComponentIx(ix)) {
1486
+ await ix.deferUpdate();
1487
+ } else {
1488
+ await ix.deferReply().catch(() => void 0);
1447
1489
  }
1448
- const pattern = String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`;
1449
- const discordWebhookRegex = new RegExp(pattern);
1450
- if (!URL.canParse(value) || !discordWebhookRegex.test(value)) {
1451
- throw new services.SeedcordError(services.SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
1490
+ }, "maybeDefer");
1491
+ var sendPrompt = /* @__PURE__ */ __name(async (ix, payload, ephemeral, isSlash, isContext) => {
1492
+ if (isSlash || isContext) {
1493
+ if (ix.deferred) return ix.editReply(payload);
1494
+ const reply = {
1495
+ ...payload,
1496
+ ...ephemeral ? {
1497
+ flags: "Ephemeral"
1498
+ } : {}
1499
+ };
1500
+ await ix.reply(reply);
1501
+ return ix.fetchReply();
1452
1502
  }
1453
- return value;
1454
- }
1455
- __name(webhookUrlValidator, "webhookUrlValidator");
1456
- exports.UnknownException = class _UnknownException extends WebhookLog {
1457
- static {
1458
- __name(this, "UnknownException");
1503
+ const follow = {
1504
+ ...payload,
1505
+ ...ephemeral ? {
1506
+ flags: "Ephemeral"
1507
+ } : {}
1508
+ };
1509
+ return ix.followUp(follow);
1510
+ }, "sendPrompt");
1511
+ var awaitComponent = /* @__PURE__ */ __name(async (msg, original, opts) => {
1512
+ const componentType = opts.componentType ?? discord_js.ComponentType.Button;
1513
+ const timeoutMs = opts.timeoutMs ?? 1e4;
1514
+ try {
1515
+ const button = await msg.awaitMessageComponent({
1516
+ componentType,
1517
+ time: timeoutMs,
1518
+ filter: /* @__PURE__ */ __name((c) => {
1519
+ if (c.user.id !== original.user.id) return false;
1520
+ if (usesCustomIds(opts)) {
1521
+ const { confirmCustomIds, cancelCustomIds = [] } = opts;
1522
+ return confirmCustomIds.includes(c.customId) || cancelCustomIds.includes(c.customId);
1523
+ }
1524
+ return true;
1525
+ }, "filter")
1526
+ });
1527
+ return {
1528
+ button,
1529
+ timedOut: false
1530
+ };
1531
+ } catch {
1532
+ return {
1533
+ button: null,
1534
+ timedOut: true
1535
+ };
1459
1536
  }
1460
- static logger = new services.Logger("Effect: UnknownException");
1461
- webhook = new discord_js.WebhookClient({
1462
- url: _UnknownException.unknownExceptionWebhookUrl
1463
- });
1464
- async execute() {
1465
- const metadataFile = this.prepareMetadataFile();
1537
+ }, "awaitComponent");
1538
+ var clearUi = /* @__PURE__ */ __name(async (ix, msg, opts, isSlash) => {
1539
+ if (!isSlash) {
1466
1540
  try {
1467
- await this.webhook.send({
1468
- flags: "IsComponentsV2",
1469
- withComponents: true,
1470
- username: "Unknown Exception",
1471
- avatarURL: "https://cdn.discordapp.com/attachments/1351446034827579466/1351446912947191830/warning-2.png",
1472
- components: [
1473
- new UnhandledErrorContainer(this.data).component
1474
- ],
1475
- files: metadataFile ? [
1476
- metadataFile
1477
- ] : []
1478
- });
1479
- } catch (error) {
1480
- _UnknownException.logger.error("Failed to send unknown exception webhook", error);
1541
+ await msg.edit(clearedPayload(opts));
1542
+ } catch {
1543
+ await ix.deleteReply(msg).catch(() => void 0);
1481
1544
  }
1545
+ return;
1482
1546
  }
1483
- prepareMetadataFile() {
1484
- const { metadata } = this.data;
1485
- if (!metadata) return null;
1486
- const content = utils.filterCirculars(metadata);
1487
- return new discord_js.AttachmentBuilder(Buffer.from(JSON.stringify(content, void 0, 2), "utf-8"), {
1488
- name: "metadata.json",
1489
- description: "Metadata associated with the error"
1490
- });
1547
+ await ix.editReply(clearedPayload(opts)).catch(() => void 0);
1548
+ }, "clearUi");
1549
+ var normalizeClassicOutcome = /* @__PURE__ */ __name((payload) => ({
1550
+ ...payload,
1551
+ components: payload.components ?? []
1552
+ }), "normalizeClassicOutcome");
1553
+ var outcomeReplacement = /* @__PURE__ */ __name(async (opts, ctx, confirmed, timedOut) => {
1554
+ const outcomes = opts.outcomeUi;
1555
+ if (isV2(opts)) {
1556
+ const v2Outcomes = outcomes;
1557
+ if (timedOut) return await resolveFactory(v2Outcomes.onTimeout, ctx);
1558
+ if (!confirmed) return await resolveFactory(v2Outcomes.onCancel, ctx);
1559
+ if (v2Outcomes.onConfirm) return await resolveFactory(v2Outcomes.onConfirm, ctx);
1560
+ return null;
1491
1561
  }
1492
- };
1493
- _ts_decorate5([
1494
- envapt.Envapt("UNKNOWN_EXCEPTION_WEBHOOK_URL", {
1495
- converter: /* @__PURE__ */ __name((raw, fallback) => webhookUrlValidator(raw), "converter")
1496
- }),
1497
- _ts_metadata5("design:type", String)
1498
- ], exports.UnknownException, "unknownExceptionWebhookUrl", void 0);
1499
- exports.UnknownException = _ts_decorate5([
1500
- RegisterEffect("unknownException")
1501
- ], exports.UnknownException);
1502
- var DefaultSeparator = class DefaultSeparator2 extends BuilderComponent {
1503
- static {
1504
- __name(this, "DefaultSeparator");
1505
- }
1506
- constructor() {
1507
- super("separator");
1508
- this.instance.setSpacing(discord_js.SeparatorSpacingSize.Small).setDivider(true);
1562
+ const classicOutcomes = outcomes;
1563
+ if (timedOut) return normalizeClassicOutcome(await resolveFactory(classicOutcomes.onTimeout, ctx));
1564
+ if (!confirmed) return normalizeClassicOutcome(await resolveFactory(classicOutcomes.onCancel, ctx));
1565
+ if (classicOutcomes.onConfirm) {
1566
+ return normalizeClassicOutcome(await resolveFactory(classicOutcomes.onConfirm, ctx));
1509
1567
  }
1510
- };
1511
- var UnhandledErrorContainer = class UnhandledErrorContainer2 extends BuilderComponent {
1568
+ return null;
1569
+ }, "outcomeReplacement");
1570
+ function Confirmable(question, options) {
1571
+ return function(_target, _propertyKey, descriptor) {
1572
+ const original = descriptor.value;
1573
+ descriptor.value = async function(...args) {
1574
+ if (!original) throw new services.SeedcordError(services.SeedcordErrorCode.DecoratorMethodNotFound);
1575
+ const ix = this.getEvent();
1576
+ const isSlash = ix.isChatInputCommand();
1577
+ const isContext = ix.isContextMenuCommand();
1578
+ const { ephemeral = true } = options;
1579
+ await maybeDefer(ix, options, isSlash, isContext);
1580
+ const q = typeof question === "function" ? await question.apply(this) : question;
1581
+ const ctx = {
1582
+ handler: this,
1583
+ interaction: ix,
1584
+ question: q
1585
+ };
1586
+ const prompt = await buildPrompt(options, ctx);
1587
+ const promptMsg = await sendPrompt(ix, prompt, ephemeral, isSlash, isContext);
1588
+ const { button, timedOut } = await awaitComponent(promptMsg, ix, options);
1589
+ let confirmed = false;
1590
+ if (button) {
1591
+ await button.deferUpdate().catch(() => void 0);
1592
+ confirmed = await decide(options, button);
1593
+ }
1594
+ const replacement = await outcomeReplacement(options, ctx, confirmed, timedOut);
1595
+ if (replacement) {
1596
+ if (isSlash || isContext) {
1597
+ await ix.editReply(replacement).catch(() => void 0);
1598
+ } else {
1599
+ await promptMsg.edit(replacement).catch(async () => {
1600
+ await ix.deleteReply(promptMsg).catch(() => void 0);
1601
+ await ix.followUp(replacement).catch(() => void 0);
1602
+ });
1603
+ }
1604
+ } else {
1605
+ await clearUi(ix, promptMsg, options, isSlash);
1606
+ }
1607
+ if (options.onResolved) {
1608
+ await options.onResolved({
1609
+ confirmed,
1610
+ timedOut,
1611
+ handler: this,
1612
+ interaction: ix,
1613
+ question: q,
1614
+ ...button ? {
1615
+ button
1616
+ } : {}
1617
+ });
1618
+ }
1619
+ if (confirmed) {
1620
+ await original.apply(this, args);
1621
+ }
1622
+ };
1623
+ return descriptor;
1624
+ };
1625
+ }
1626
+ __name(Confirmable, "Confirmable");
1627
+ function EventCatchable(options) {
1628
+ return function(_target, _prop, descriptor) {
1629
+ const log = options?.log ?? false;
1630
+ const silent = options?.silent ?? false;
1631
+ const original = descriptor.value;
1632
+ descriptor.value = async function(...args) {
1633
+ if (!original) throw new services.SeedcordError(services.SeedcordErrorCode.DecoratorMethodNotFound);
1634
+ try {
1635
+ await original.apply(this, args);
1636
+ } catch (err) {
1637
+ if (!(err instanceof Error)) throw err;
1638
+ this.setErrored();
1639
+ if (log) console.error(err);
1640
+ const eventArgs = Array.isArray(this.getEvent()) ? this.getEvent() : [
1641
+ this.getEvent()
1642
+ ];
1643
+ const msg = eventArgs.find((x) => x instanceof discord_js.Message);
1644
+ const { response } = extractErrorResponse(err, this.core, msg?.guild ?? null, msg?.author ?? null, eventArgs);
1645
+ if (typeof silent === "boolean" && silent) return;
1646
+ if (typeof silent !== "boolean" && silent.some((errorType) => err instanceof errorType)) {
1647
+ return;
1648
+ }
1649
+ if (!msg) return;
1650
+ await msg.reply({
1651
+ embeds: [
1652
+ response
1653
+ ],
1654
+ components: []
1655
+ });
1656
+ }
1657
+ };
1658
+ };
1659
+ }
1660
+ __name(EventCatchable, "EventCatchable");
1661
+
1662
+ // src/bot/defaults/errors/Channels.ts
1663
+ var ChannelNotFoundError = class extends CustomError {
1512
1664
  static {
1513
- __name(this, "UnhandledErrorContainer");
1514
- }
1515
- constructor(data) {
1516
- super("container");
1517
- const { uuid, error, guild, user, metadata } = data;
1518
- this.instance.addTextDisplayComponents((text) => text.setContent(`### An unknown exception was thrown
1519
- **Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`
1520
- **Guild Name:** ${guild?.name ?? "Not used in a guild"}
1521
- **User ID:** \`${user?.id ?? "Missing user info"}\`
1522
- **Username:** ${user?.username ?? "Missing user info"}
1523
- `)).addSeparatorComponents(new DefaultSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`
1524
- \`\`\`${error.stack}\`\`\``));
1525
- this.addTimestampsIfAvailable(error);
1526
- this.addMetadataIfAvailable(metadata);
1527
- }
1528
- addTimestampsIfAvailable(error) {
1529
- if (!(error instanceof discord_js.DiscordAPIError)) return;
1530
- const now = Date.now();
1531
- const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
1532
- if (!snowflake) return void 0;
1533
- const interactionTs = Number(discord_js.SnowflakeUtil.deconstruct(snowflake).timestamp);
1534
- const diff = now - interactionTs;
1535
- const seconds = Math.floor(diff / 1e3);
1536
- const millis = diff % 1e3;
1537
- this.instance.addSeparatorComponents(new DefaultSeparator().component).addTextDisplayComponents((text) => text.setContent(`### Timestamps
1538
- - **\`Interaction sent\` :** ${new Date(interactionTs).toISOString()} (${interactionTs})
1539
- - **\`Error logged \` :** ${new Date(now).toISOString()} (${now})
1540
- - **\`Offset \` :** ${seconds}s ${millis}ms`));
1665
+ __name(this, "ChannelNotFoundError");
1541
1666
  }
1542
- addMetadataIfAvailable(metadata) {
1543
- if (!metadata) return;
1544
- this.instance.addSeparatorComponents(new DefaultSeparator().component).addTextDisplayComponents((text) => text.setContent("### Metadata")).addFileComponents((file) => file.setURL("attachment://metadata.json"));
1667
+ channelId;
1668
+ /**
1669
+ * Creates a new ChannelNotFoundError.
1670
+ *
1671
+ * @param message - The error message
1672
+ * @param channelId - The ID of the channel that could not be found
1673
+ */
1674
+ constructor(message, channelId) {
1675
+ super(message), this.channelId = channelId;
1676
+ this.response.setDescription(`Channel with ID \`${this.channelId}\` not found.`);
1545
1677
  }
1546
1678
  };
1547
- var EffectsEmitter = class {
1679
+ var CannotSendEmbedsError = class extends CustomError {
1548
1680
  static {
1549
- __name(this, "EffectsEmitter");
1681
+ __name(this, "CannotSendEmbedsError");
1550
1682
  }
1551
- emitter = new events.EventEmitter();
1683
+ channelId;
1552
1684
  /**
1553
- * Registers a listener for the specified side effect.
1685
+ * Creates a new CannotSendEmbedsError.
1554
1686
  *
1555
- * @typeParam KeyOfEffects - The side effect name type
1556
- * @param event - The side effect name to listen for
1557
- * @param listener - Function to call when the event is emitted
1558
- * @returns This EffectsEmitter instance for chaining
1687
+ * @param message - The error message
1688
+ * @param channelId - The ID of the channel where embeds cannot be sent
1559
1689
  */
1560
- on(event, listener) {
1561
- this.emitter.on(event, listener);
1562
- return this;
1690
+ constructor(message, channelId) {
1691
+ super(message), this.channelId = channelId;
1692
+ this.response.setDescription(`Cannot send embeds in <#${this.channelId}>.
1693
+
1694
+ Please ensure I have the following permissions:
1695
+ \u2022 View Channel
1696
+ \u2022 Send Messages
1697
+ \u2022 Embed Links
1698
+ \u2022 Attach Files
1699
+ \u2022 Read Message History
1700
+ \u2022 Use External Emojis
1701
+ `);
1702
+ }
1703
+ };
1704
+ var CouldNotFindChannel = class extends CustomError {
1705
+ static {
1706
+ __name(this, "CouldNotFindChannel");
1563
1707
  }
1708
+ channelId;
1564
1709
  /**
1565
- * Registers a one-time listener for the specified side effect.
1710
+ * Creates a new CouldNotFindChannel error.
1566
1711
  *
1567
- * @typeParam KeyOfEffects - The side effect name type
1568
- * @param event - The side effect name to listen for once
1569
- * @param listener - Function to call when the event is emitted
1570
- * @returns This EffectsEmitter instance for chaining
1712
+ * @param message - The error message
1713
+ * @param channelId - The ID of the channel that could not be found
1571
1714
  */
1572
- once(event, listener) {
1573
- this.emitter.once(event, listener);
1574
- return this;
1715
+ constructor(message, channelId) {
1716
+ super(message), this.channelId = channelId;
1717
+ this.response.setDescription(`Could not find channel with ID \`${this.channelId}\`. It could also be that the channel is not a text channel.`);
1718
+ }
1719
+ };
1720
+ var ChannelNotTextChannel = class extends CustomError {
1721
+ static {
1722
+ __name(this, "ChannelNotTextChannel");
1575
1723
  }
1724
+ channelId;
1576
1725
  /**
1577
- * Emits a side effect with the provided data.
1726
+ * Creates a new ChannelNotTextChannel error.
1578
1727
  *
1579
- * @typeParam KeyOfEffects - The side effect name type
1580
- * @param event - The side effect name to emit
1581
- * @param data - The data to pass to registered listeners
1582
- * @returns True if the event had listeners, false otherwise
1728
+ * @param message - The error message
1729
+ * @param channelId - The ID of the channel that is not a text channel
1583
1730
  */
1584
- emit(event, data) {
1585
- return this.emitter.emit(event, data);
1731
+ constructor(message, channelId) {
1732
+ super(message), this.channelId = channelId;
1733
+ this.response.setDescription(`Channel with ID \`${this.channelId}\` is not a text channel.`);
1586
1734
  }
1587
1735
  };
1588
1736
 
1589
- // src/effects/EffectsRegistry.ts
1590
- var EffectsRegistry = class extends Plugin {
1591
- static {
1592
- __name(this, "EffectsRegistry");
1593
- }
1594
- core;
1595
- logger = new services.Logger("Effects");
1596
- isInitialized = false;
1597
- effectsMap = new discord_js.Collection();
1598
- emitter = new EffectsEmitter();
1599
- constructor(core) {
1600
- super(core), this.core = core;
1737
+ // src/bot/utilities/channels/fetchText.ts
1738
+ async function fetchText(client, channelId) {
1739
+ if (channelId instanceof discord_js.TextChannel) {
1740
+ return channelId;
1601
1741
  }
1602
- async init() {
1603
- if (this.isInitialized) return;
1604
- this.isInitialized = true;
1605
- const effectsDir = this.core.config.effects.path;
1606
- this.logger.info(chalk3__default.default.bold(effectsDir));
1607
- this.registerEffect(exports.UnknownException, {
1608
- effect: "unknownException",
1609
- frequency: "on"
1610
- });
1611
- await this.loadEffects(effectsDir);
1612
- this.attachEffects();
1613
- const totalEffects = Array.from(this.effectsMap.values()).reduce((acc, handlers) => acc + handlers.length, 0);
1614
- this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${chalk3__default.default.bold.magenta(totalEffects)} side effects`);
1742
+ let channel = client.channels.cache.get(channelId);
1743
+ if (!channel) {
1744
+ try {
1745
+ channel = await client.channels.fetch(channelId);
1746
+ } catch {
1747
+ throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
1748
+ }
1615
1749
  }
1616
- async loadEffects(dir) {
1617
- await utils.traverseDirectory(dir, (_fullPath, relativePath, imported) => {
1618
- for (const exportName of Object.keys(imported)) {
1619
- const val = imported[exportName];
1620
- if (this.isEffectHandler(val)) {
1621
- const meta = Reflect.getMetadata(EffectMetadataKey, val);
1622
- this.registerEffect(val, meta);
1623
- this.logger.info(`${chalk3__default.default.italic("Registered")} ${chalk3__default.default.bold.yellow(val.name)} from ${chalk3__default.default.gray(relativePath)}`);
1624
- }
1625
- }
1626
- }, this.logger);
1750
+ if (channel?.isTextBased()) {
1751
+ return channel;
1627
1752
  }
1628
- registerEffect(handler, options) {
1629
- let handlers = this.effectsMap.get(options.effect);
1630
- if (!handlers) {
1631
- handlers = [];
1632
- this.effectsMap.set(options.effect, handlers);
1753
+ throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
1754
+ }
1755
+ __name(fetchText, "fetchText");
1756
+
1757
+ // src/bot/utilities/channels/sendInText.ts
1758
+ async function sendInText(client, channelId, message) {
1759
+ const channel = await fetchText(client, channelId);
1760
+ return await channel.send(message);
1761
+ }
1762
+ __name(sendInText, "sendInText");
1763
+ function throwCustomError(error, message, CustomError2) {
1764
+ const uuid = crypto.randomUUID();
1765
+ services.Logger.Error("Throwing Custom Error", error.name);
1766
+ if (typeof CustomError2 === typeof DatabaseError) {
1767
+ const errorMessage = error instanceof Error ? error.message : message;
1768
+ throw new CustomError2(errorMessage, uuid);
1769
+ } else {
1770
+ if (error instanceof Error) {
1771
+ throw new CustomError2(`${message}: ${error.message ? error.message : error.toString()}`);
1772
+ } else {
1773
+ throw new CustomError2(message);
1633
1774
  }
1634
- handlers.push({
1635
- ctor: handler,
1636
- frequency: options.frequency ?? "on"
1637
- });
1638
- }
1639
- isEffectHandler(obj) {
1640
- if (typeof obj !== "function") return false;
1641
- return obj.prototype instanceof EffectsHandler && Reflect.hasMetadata(EffectMetadataKey, obj);
1642
1775
  }
1643
- attachEffects() {
1644
- for (const [effectName, handlerEntries] of this.effectsMap) {
1645
- for (const entry of handlerEntries) {
1646
- const register = entry.frequency === "once" ? this.emitter.once.bind(this.emitter) : this.emitter.on.bind(this.emitter);
1647
- register(effectName, (data) => {
1648
- try {
1649
- const instance = new entry.ctor(data, this.core);
1650
- void instance.execute();
1651
- } catch (err) {
1652
- this.logger.error(`Error in side effect ${String(effectName)} handler ${entry.ctor.name}:`, err);
1653
- }
1654
- });
1655
- }
1776
+ }
1777
+ __name(throwCustomError, "throwCustomError");
1778
+
1779
+ // src/bot/utilities/messages/attemptSendDM.ts
1780
+ async function attemptSendDM(user, content) {
1781
+ const payload = {
1782
+ ...content.content !== void 0 && {
1783
+ content: content.content
1784
+ },
1785
+ ...content.embeds !== void 0 && {
1786
+ embeds: [
1787
+ ...content.embeds
1788
+ ]
1789
+ },
1790
+ ...content.components !== void 0 && {
1791
+ components: [
1792
+ ...content.components
1793
+ ]
1656
1794
  }
1795
+ };
1796
+ try {
1797
+ return await user.send(payload);
1798
+ } catch {
1799
+ return null;
1657
1800
  }
1658
- emit(event, data) {
1659
- return this.emitter.emit(event, data);
1801
+ }
1802
+ __name(attemptSendDM, "attemptSendDM");
1803
+ var RoleHigherThanMe = class extends CustomError {
1804
+ static {
1805
+ __name(this, "RoleHigherThanMe");
1660
1806
  }
1661
- };
1807
+ role;
1808
+ botRole;
1809
+ /**
1810
+ * Creates a new RoleHigherThanMe error.
1811
+ *
1812
+ * @param message - The error message
1813
+ */
1814
+ constructor(message, role, botRole) {
1815
+ super(message), this.role = role, this.botRole = botRole;
1816
+ this.response.setDescription(`I cannot assign a role that is higher than me.
1662
1817
 
1663
- // src/Seedcord.ts
1664
- var Seedcord = class _Seedcord extends Pluggable {
1818
+ The role <@&${this.role.id}> is higher than my role <@&${this.botRole.id}> in the hierarchy.`);
1819
+ }
1820
+ };
1821
+ var CannotAssignBotRole = class extends CustomError {
1665
1822
  static {
1666
- __name(this, "Seedcord");
1823
+ __name(this, "CannotAssignBotRole");
1667
1824
  }
1668
- config;
1669
- static isInstantiated = false;
1670
- /** @see {@link CoordinatedShutdown} */
1671
- shutdown;
1672
- /** @see {@link CoordinatedStartup} */
1673
- startup;
1674
- /** @see {@link EffectsRegistry} */
1675
- effects;
1676
- /** @see {@link Bot} */
1677
- bot;
1678
- /** @see {@link HealthCheck} */
1679
- healthCheck;
1680
1825
  /**
1681
- * Creates a new Seedcord instance
1826
+ * Creates a new CannotAssignBotRole error.
1682
1827
  *
1683
- * @param config - Bot configuration including paths and Discord client options
1684
- * @throws An {@link SeedcordError} When attempting to create multiple instances (singleton)
1828
+ * @param message - The error message
1685
1829
  */
1686
- constructor(config) {
1687
- const shutdown = new services.CoordinatedShutdown();
1688
- const startup = new services.CoordinatedStartup();
1689
- super(shutdown, startup), this.config = config;
1690
- this.shutdown = shutdown;
1691
- this.startup = startup;
1692
- if (_Seedcord.isInstantiated) {
1693
- throw new services.SeedcordError(services.SeedcordErrorCode.CoreSingletonViolation);
1694
- }
1695
- _Seedcord.isInstantiated = true;
1696
- this.effects = new EffectsRegistry(this);
1697
- this.bot = new Bot(this);
1698
- this.healthCheck = new services.HealthCheck(this.shutdown);
1699
- this.registerStartupTasks();
1830
+ constructor(message = "I cannot assign a managed role.") {
1831
+ super(message);
1832
+ this.response.setDescription("I cannot assign a managed role.");
1833
+ }
1834
+ };
1835
+ var RoleDoesNotExist = class extends CustomError {
1836
+ static {
1837
+ __name(this, "RoleDoesNotExist");
1700
1838
  }
1839
+ roleId;
1701
1840
  /**
1702
- * Registers default startup tasks
1703
- * @internal
1841
+ * Creates a new RoleDoesNotExist error.
1842
+ *
1843
+ * @param message - The error message
1844
+ * @param roleId - The ID of the role that doesn't exist
1704
1845
  */
1705
- registerStartupTasks() {
1706
- this.startup.addTask(services.StartupPhase.Configuration, "Effect Initialization", async () => {
1707
- this.effects.logger.info(chalk3__default.default.bold("Initializing"));
1708
- await this.effects.init();
1709
- this.effects.logger.info(chalk3__default.default.bold("Initialized"));
1710
- });
1711
- this.startup.addTask(services.StartupPhase.Instantiation, "Bot Initialization", async () => {
1712
- this.bot.logger.info(chalk3__default.default.bold("Initializing"));
1713
- await this.bot.init();
1714
- this.bot.logger.info(chalk3__default.default.bold("Initialized"));
1715
- });
1716
- this.startup.addTask(services.StartupPhase.Ready, "Health Check", async () => {
1717
- this.healthCheck.logger.info(chalk3__default.default.bold("Initializing"));
1718
- await this.healthCheck.init();
1719
- this.healthCheck.logger.info(chalk3__default.default.bold("Initialized"));
1720
- });
1846
+ constructor(message, roleId) {
1847
+ super(message), this.roleId = roleId;
1848
+ this.response.setDescription(`The role with ID \`${this.roleId}\` does not exist.`);
1849
+ }
1850
+ };
1851
+ var MissingPermissions = class extends CustomError {
1852
+ static {
1853
+ __name(this, "MissingPermissions");
1721
1854
  }
1855
+ where;
1856
+ missingPerms;
1722
1857
  /**
1723
- * Starts the bot and runs all initialization tasks
1858
+ * Creates a new MissingPermissions error.
1724
1859
  *
1725
- * @returns This Seedcord instance when fully initialized
1860
+ * @param message - The error message
1861
+ * @param missingPerms - Array of missing permission names
1862
+ * @param where - Location or subject where permissions are missing
1726
1863
  */
1727
- async start() {
1728
- await super.init();
1729
- return this;
1864
+ constructor(message, where, missingPerms) {
1865
+ super(message), this.where = where, this.missingPerms = missingPerms;
1866
+ const bullets = this.missingPerms.map((perm) => `\u2022 ${perm}`).join("\n");
1867
+ const mention = this.where instanceof discord_js.Role ? `<@&${this.where.id}>` : this.where instanceof discord_js.TextChannel ? `<#${this.where.id}>` : this.where instanceof discord_js.GuildMember ? `<@${this.where.id}>` : `\`${this.where.name}\``;
1868
+ const label = this.where instanceof discord_js.Role ? "role" : this.where instanceof discord_js.TextChannel ? "channel" : this.where instanceof discord_js.GuildMember ? "member" : "guild";
1869
+ this.response.setDescription(`The ${label} ${mention} is missing the following permission entries:
1870
+
1871
+ ${bullets}`);
1730
1872
  }
1731
1873
  };
1874
+ var HasDangerousPermissions = class extends CustomError {
1875
+ static {
1876
+ __name(this, "HasDangerousPermissions");
1877
+ }
1878
+ target;
1879
+ dangerousPerms;
1880
+ /**
1881
+ * Creates a new HasDangerousPermissions error.
1882
+ *
1883
+ * @param message - The error message
1884
+ * @param target - The subject that has the unwanted permissions
1885
+ * @param dangerousPerms - Array of dangerous permission names
1886
+ */
1887
+ constructor(message, target, dangerousPerms) {
1888
+ super(message), this.target = target, this.dangerousPerms = dangerousPerms;
1889
+ const bullets = this.dangerousPerms.map((perm) => `\u2022 ${perm}`).join("\n");
1890
+ const mention = this.target instanceof discord_js.Role ? `<@&${this.target.id}>` : this.target instanceof discord_js.TextChannel ? `<#${this.target.id}>` : this.target instanceof discord_js.GuildMember ? `<@${this.target.id}>` : `\`${this.target.name}\``;
1891
+ const label = this.target instanceof discord_js.Role ? "role" : this.target instanceof discord_js.TextChannel ? "channel" : this.target instanceof discord_js.GuildMember ? "member" : "guild";
1892
+ this.response.setDescription(`The ${label} ${mention} has the following permission entries that must not be enabled:
1732
1893
 
1733
- // src/bot/decorators/Checkable.ts
1734
- function Checkable(ctor) {
1735
- return class extends ctor {
1736
- static name = ctor.name;
1737
- checkable = true;
1738
- };
1894
+ ${bullets}`);
1895
+ }
1896
+ };
1897
+ var PermissionNames = new Map(Object.entries(discord_js.PermissionFlagsBits).map(([key, bit]) => [
1898
+ bit,
1899
+ utils.prettify(key)
1900
+ ]));
1901
+ function checkPermissions(a, b, c, d, e) {
1902
+ const opts = a instanceof discord_js.Role || a instanceof discord_js.GuildMember ? {
1903
+ for: a,
1904
+ in: b,
1905
+ scope: c,
1906
+ inverse: d ?? false,
1907
+ ...e ?? {}
1908
+ } : a;
1909
+ const { for: pFor, in: pIn, scope, inverse = false, missing: missingCtor, dangerous: dangerousCtor } = opts;
1910
+ const Missing = missingCtor ?? MissingPermissions;
1911
+ const Dangerous = dangerousCtor ?? HasDangerousPermissions;
1912
+ const perms = pIn instanceof discord_js.Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
1913
+ const names = /* @__PURE__ */ __name((bits) => bits.map((bit) => PermissionNames.get(bit) ?? String(bit)), "names");
1914
+ const present = scope.filter((bit) => perms.has(bit, true));
1915
+ const presentNames = names(present);
1916
+ if (inverse) {
1917
+ if (present.length > 0) {
1918
+ const base = `${pFor instanceof discord_js.Role ? "Role" : "Member"} has dangerous permissions in this ${pIn instanceof discord_js.Guild ? "guild" : "channel"}`;
1919
+ throw new Dangerous(base, pFor, presentNames);
1920
+ }
1921
+ return;
1922
+ }
1923
+ const missingBits = scope.filter((bit) => !perms.has(bit, true));
1924
+ if (missingBits.length > 0) {
1925
+ throw new Missing("Missing Any/All/No Permissions", pIn, names(missingBits));
1926
+ }
1739
1927
  }
1740
- __name(Checkable, "Checkable");
1741
- function EventCatchable(options) {
1742
- return function(_target, _prop, descriptor) {
1743
- const log = options?.log ?? false;
1744
- const silent = options?.silent ?? false;
1745
- const original = descriptor.value;
1746
- descriptor.value = async function(...args) {
1747
- if (!original) throw new services.SeedcordError(services.SeedcordErrorCode.DecoratorMethodNotFound);
1748
- try {
1749
- await original.apply(this, args);
1750
- } catch (err) {
1751
- if (!(err instanceof Error)) throw err;
1752
- this.setErrored();
1753
- if (log) console.error(err);
1754
- if (typeof silent === "boolean" && silent) return;
1755
- if (typeof silent !== "boolean" && silent.some((errorType) => err instanceof errorType)) {
1756
- return;
1757
- }
1758
- const eventArgs = Array.isArray(this.getEvent()) ? this.getEvent() : [
1759
- this.getEvent()
1760
- ];
1761
- const msg = eventArgs.find((x) => x instanceof discord_js.Message);
1762
- const { response } = extractErrorResponse(err, this.core, msg?.guild ?? null, msg?.author ?? null, eventArgs);
1763
- if (!msg) return;
1764
- await msg.reply({
1765
- embeds: [
1766
- response
1767
- ],
1768
- components: []
1769
- });
1770
- }
1771
- };
1772
- };
1928
+ __name(checkPermissions, "checkPermissions");
1929
+ var ensurePermissions = checkPermissions;
1930
+
1931
+ // src/bot/utilities/permissions/checkBotPermissions.ts
1932
+ function checkBotPermissions(target, scope, inverse = false, errors) {
1933
+ if (target instanceof discord_js.Guild) {
1934
+ const me2 = target.members.me;
1935
+ if (!me2) {
1936
+ const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
1937
+ const Missing = errors?.missing ?? MissingPermissions;
1938
+ throw new Missing("Missing Permissions", target, names);
1939
+ }
1940
+ checkPermissions(me2, target, scope, inverse, errors);
1941
+ return;
1942
+ }
1943
+ const me = target.guild.members.me;
1944
+ if (!me) {
1945
+ const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
1946
+ const Missing = errors?.missing ?? MissingPermissions;
1947
+ services.Logger.Warn("checkBotPermissions", `Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
1948
+ throw new Missing("Missing Permissions", target, names);
1949
+ }
1950
+ checkPermissions(me, target, scope, inverse, errors);
1773
1951
  }
1774
- __name(EventCatchable, "EventCatchable");
1775
- var resolveFactory = /* @__PURE__ */ __name(async (input, ctx) => typeof input === "function" ? await input(ctx) : input, "resolveFactory");
1776
- var isV2 = /* @__PURE__ */ __name((opts) => "container" in opts, "isV2");
1777
- var usesResolver = /* @__PURE__ */ __name((opts) => "resolveDecision" in opts, "usesResolver");
1778
- var usesCustomIds = /* @__PURE__ */ __name((opts) => "confirmCustomIds" in opts, "usesCustomIds");
1779
- var isMessageComponentIx = /* @__PURE__ */ __name((ix) => "deferUpdate" in ix && "customId" in ix, "isMessageComponentIx");
1780
- var buildPrompt = /* @__PURE__ */ __name(async (opts, ctx) => {
1781
- if (isV2(opts)) {
1782
- const container = await resolveFactory(opts.container, ctx);
1783
- return {
1784
- flags: "IsComponentsV2",
1785
- components: [
1786
- container.component
1787
- ]
1788
- };
1952
+ __name(checkBotPermissions, "checkBotPermissions");
1953
+ var ensureBotPermissions = checkBotPermissions;
1954
+ function getBotRole(guild) {
1955
+ const botRole = guild.roles.botRoleFor(guild.client.user);
1956
+ if (!botRole) {
1957
+ throw new services.SeedcordError(services.SeedcordErrorCode.CoreBotRoleMissing, [
1958
+ guild.id
1959
+ ]);
1789
1960
  }
1790
- const prompt = await resolveFactory(opts.prompt, ctx);
1791
- const rows = await resolveFactory(opts.rows, ctx);
1792
- const components = [
1793
- ...rows
1794
- ];
1795
- const payload = {
1796
- components
1797
- };
1798
- if (typeof prompt === "string") payload.content = prompt;
1799
- else payload.embeds = [
1800
- prompt.component
1801
- ];
1802
- return payload;
1803
- }, "buildPrompt");
1804
- var clearedPayload = /* @__PURE__ */ __name((opts) => isV2(opts) ? {
1805
- flags: "IsComponentsV2",
1806
- components: []
1807
- } : {
1808
- components: []
1809
- }, "clearedPayload");
1810
- var decide = /* @__PURE__ */ __name(async (opts, i) => {
1811
- if (usesResolver(opts)) return opts.resolveDecision(i);
1812
- if (usesCustomIds(opts)) {
1813
- const { confirmCustomIds, cancelCustomIds = [] } = opts;
1814
- if (confirmCustomIds.includes(i.customId)) return true;
1815
- if (cancelCustomIds.includes(i.customId)) return false;
1961
+ return botRole;
1962
+ }
1963
+ __name(getBotRole, "getBotRole");
1964
+
1965
+ // src/bot/utilities/permissions/hasPermsToAssign.ts
1966
+ function hasPermsToAssign(roleOrOptions) {
1967
+ const { targetRole, errors } = roleOrOptions instanceof discord_js.Role ? {
1968
+ targetRole: roleOrOptions,
1969
+ errors: void 0
1970
+ } : roleOrOptions;
1971
+ const HigherErr = errors?.higher ?? RoleHigherThanMe;
1972
+ const ManagedErr = errors?.managed ?? CannotAssignBotRole;
1973
+ const Missing = errors?.missing ?? MissingPermissions;
1974
+ const botRole = getBotRole(targetRole.guild);
1975
+ if (targetRole.comparePositionTo(botRole) >= 0) {
1976
+ throw new HigherErr("Role is higher than me", targetRole, botRole);
1816
1977
  }
1817
- return false;
1818
- }, "decide");
1819
- var shouldDefer = /* @__PURE__ */ __name((ix, opts, isSlash, isContext) => {
1820
- if (ix.deferred) return false;
1821
- if (opts.defer === false) return false;
1822
- return isSlash || isContext;
1823
- }, "shouldDefer");
1824
- var maybeDefer = /* @__PURE__ */ __name(async (ix, opts, isSlash, isContext) => {
1825
- if (!shouldDefer(ix, opts, isSlash, isContext)) return;
1826
- if (isSlash || isContext) {
1827
- const { ephemeral = true } = opts;
1828
- const flags = ephemeral ? {
1829
- flags: "Ephemeral"
1830
- } : void 0;
1831
- if (flags) await ix.deferReply(flags);
1832
- else await ix.deferReply();
1833
- } else if (isMessageComponentIx(ix)) {
1834
- await ix.deferUpdate();
1835
- } else {
1836
- await ix.deferReply().catch(() => void 0);
1978
+ if (targetRole.managed) {
1979
+ throw new ManagedErr(`Cannot assign managed role ${targetRole.name}`);
1837
1980
  }
1838
- }, "maybeDefer");
1839
- var sendPrompt = /* @__PURE__ */ __name(async (ix, payload, ephemeral, isSlash, isContext) => {
1840
- if (isSlash || isContext) {
1841
- if (ix.deferred) return ix.editReply(payload);
1842
- const reply = {
1843
- ...payload,
1844
- ...ephemeral ? {
1845
- flags: "Ephemeral"
1846
- } : {}
1847
- };
1848
- await ix.reply(reply);
1849
- return ix.fetchReply();
1981
+ checkBotPermissions(targetRole.guild, [
1982
+ discord_js.PermissionFlagsBits.ManageRoles
1983
+ ], false, {
1984
+ missing: Missing
1985
+ });
1986
+ }
1987
+ __name(hasPermsToAssign, "hasPermsToAssign");
1988
+ async function fetchRole(clientOrGuild, roleId) {
1989
+ let role;
1990
+ if (!roleId) {
1991
+ throw new RoleDoesNotExist("Role ID is null or undefined", roleId);
1850
1992
  }
1851
- const follow = {
1852
- ...payload,
1853
- ...ephemeral ? {
1854
- flags: "Ephemeral"
1855
- } : {}
1856
- };
1857
- return ix.followUp(follow);
1858
- }, "sendPrompt");
1859
- var awaitComponent = /* @__PURE__ */ __name(async (msg, original, opts) => {
1860
- const componentType = opts.componentType ?? discord_js.ComponentType.Button;
1861
- const timeoutMs = opts.timeoutMs ?? 1e4;
1862
- try {
1863
- const button = await msg.awaitMessageComponent({
1864
- componentType,
1865
- time: timeoutMs,
1866
- filter: /* @__PURE__ */ __name((c) => {
1867
- if (c.user.id !== original.user.id) return false;
1868
- if (usesCustomIds(opts)) {
1869
- const { confirmCustomIds, cancelCustomIds = [] } = opts;
1870
- return confirmCustomIds.includes(c.customId) || cancelCustomIds.includes(c.customId);
1993
+ if (clientOrGuild instanceof discord_js.Guild) {
1994
+ const guild = clientOrGuild;
1995
+ role = guild.roles.cache.get(roleId);
1996
+ if (!role) {
1997
+ try {
1998
+ role = await guild.roles.fetch(roleId);
1999
+ } catch {
2000
+ throw new RoleDoesNotExist("Role not found in specified guild", roleId);
2001
+ }
2002
+ }
2003
+ } else {
2004
+ const client = clientOrGuild;
2005
+ role = client.guilds.cache.map((guild) => guild.roles.cache.get(roleId)).find((role2) => role2);
2006
+ if (!role) {
2007
+ const guilds = client.guilds.cache;
2008
+ for (const guild of guilds.values()) {
2009
+ try {
2010
+ role = await guild.roles.fetch(roleId);
2011
+ if (role) break;
2012
+ } catch {
2013
+ continue;
1871
2014
  }
1872
- return true;
1873
- }, "filter")
1874
- });
1875
- return {
1876
- button,
1877
- timedOut: false
1878
- };
1879
- } catch {
1880
- return {
1881
- button: null,
1882
- timedOut: true
1883
- };
1884
- }
1885
- }, "awaitComponent");
1886
- var clearUi = /* @__PURE__ */ __name(async (ix, msg, opts, isSlash) => {
1887
- if (!isSlash) {
1888
- try {
1889
- await msg.edit(clearedPayload(opts));
1890
- } catch {
1891
- await ix.deleteReply(msg).catch(() => void 0);
2015
+ }
1892
2016
  }
1893
- return;
1894
- }
1895
- await ix.editReply(clearedPayload(opts)).catch(() => void 0);
1896
- }, "clearUi");
1897
- var normalizeClassicOutcome = /* @__PURE__ */ __name((payload) => ({
1898
- ...payload,
1899
- components: payload.components ?? []
1900
- }), "normalizeClassicOutcome");
1901
- var outcomeReplacement = /* @__PURE__ */ __name(async (opts, ctx, confirmed, timedOut) => {
1902
- const outcomes = opts.outcomeUi;
1903
- if (isV2(opts)) {
1904
- const v2Outcomes = outcomes;
1905
- if (timedOut) return await resolveFactory(v2Outcomes.onTimeout, ctx);
1906
- if (!confirmed) return await resolveFactory(v2Outcomes.onCancel, ctx);
1907
- if (v2Outcomes.onConfirm) return await resolveFactory(v2Outcomes.onConfirm, ctx);
1908
- return null;
1909
2017
  }
1910
- const classicOutcomes = outcomes;
1911
- if (timedOut) return normalizeClassicOutcome(await resolveFactory(classicOutcomes.onTimeout, ctx));
1912
- if (!confirmed) return normalizeClassicOutcome(await resolveFactory(classicOutcomes.onCancel, ctx));
1913
- if (classicOutcomes.onConfirm) {
1914
- return normalizeClassicOutcome(await resolveFactory(classicOutcomes.onConfirm, ctx));
2018
+ if (!role) {
2019
+ throw new RoleDoesNotExist("Role not found", roleId);
1915
2020
  }
1916
- return null;
1917
- }, "outcomeReplacement");
1918
- function Confirmable(question, options) {
1919
- return function(_target, _propertyKey, descriptor) {
1920
- const original = descriptor.value;
1921
- descriptor.value = async function(...args) {
1922
- if (!original) throw new services.SeedcordError(services.SeedcordErrorCode.DecoratorMethodNotFound);
1923
- const ix = this.getEvent();
1924
- const isSlash = ix.isChatInputCommand();
1925
- const isContext = ix.isContextMenuCommand();
1926
- const { ephemeral = true } = options;
1927
- await maybeDefer(ix, options, isSlash, isContext);
1928
- const q = typeof question === "function" ? await question.apply(this) : question;
1929
- const ctx = {
1930
- handler: this,
1931
- interaction: ix,
1932
- question: q
1933
- };
1934
- const prompt = await buildPrompt(options, ctx);
1935
- const promptMsg = await sendPrompt(ix, prompt, ephemeral, isSlash, isContext);
1936
- const { button, timedOut } = await awaitComponent(promptMsg, ix, options);
1937
- let confirmed = false;
1938
- if (button) {
1939
- await button.deferUpdate().catch(() => void 0);
1940
- confirmed = await decide(options, button);
1941
- }
1942
- const replacement = await outcomeReplacement(options, ctx, confirmed, timedOut);
1943
- if (replacement) {
1944
- if (isSlash || isContext) {
1945
- await ix.editReply(replacement).catch(() => void 0);
1946
- } else {
1947
- await promptMsg.edit(replacement).catch(async () => {
1948
- await ix.deleteReply(promptMsg).catch(() => void 0);
1949
- await ix.followUp(replacement).catch(() => void 0);
1950
- });
1951
- }
1952
- } else {
1953
- await clearUi(ix, promptMsg, options, isSlash);
1954
- }
1955
- if (options.onResolved) {
1956
- await options.onResolved({
1957
- confirmed,
1958
- timedOut,
1959
- handler: this,
1960
- interaction: ix,
1961
- question: q,
1962
- ...button ? {
1963
- button
1964
- } : {}
1965
- });
1966
- }
1967
- if (confirmed) {
1968
- await original.apply(this, args);
1969
- }
1970
- };
1971
- return descriptor;
1972
- };
2021
+ return role;
1973
2022
  }
1974
- __name(Confirmable, "Confirmable");
2023
+ __name(fetchRole, "fetchRole");
1975
2024
 
1976
- // src/bot/errors/Channels.ts
1977
- var ChannelNotFoundError = class extends CustomError {
2025
+ // src/bot/defaults/errors/User.ts
2026
+ var UserNotInGuild = class extends CustomError {
1978
2027
  static {
1979
- __name(this, "ChannelNotFoundError");
2028
+ __name(this, "UserNotInGuild");
1980
2029
  }
1981
- channelId;
1982
2030
  /**
1983
- * Creates a new ChannelNotFoundError.
2031
+ * Creates a new UserNotInGuild error.
1984
2032
  *
1985
2033
  * @param message - The error message
1986
- * @param channelId - The ID of the channel that could not be found
1987
2034
  */
1988
- constructor(message, channelId) {
1989
- super(message), this.channelId = channelId;
1990
- this.response.setDescription(`Channel with ID \`${this.channelId}\` not found.`);
2035
+ constructor(message = "User is not in the guild.") {
2036
+ super(message);
2037
+ this.response.setDescription("User is not in the guild.");
1991
2038
  }
1992
2039
  };
1993
- var CannotSendEmbedsError = class extends CustomError {
2040
+ var UserNotFound = class extends CustomError {
1994
2041
  static {
1995
- __name(this, "CannotSendEmbedsError");
2042
+ __name(this, "UserNotFound");
1996
2043
  }
1997
- channelId;
2044
+ userArg;
1998
2045
  /**
1999
- * Creates a new CannotSendEmbedsError.
2046
+ * Creates a new UserNotFound error.
2000
2047
  *
2001
- * @param message - The error message
2002
- * @param channelId - The ID of the channel where embeds cannot be sent
2048
+ * @param userArg - The user argument that could not be resolved
2003
2049
  */
2004
- constructor(message, channelId) {
2005
- super(message), this.channelId = channelId;
2006
- this.response.setDescription(`Cannot send embeds in <#${this.channelId}>.
2007
-
2008
- Please ensure I have the following permissions:
2009
- \u2022 View Channel
2010
- \u2022 Send Messages
2011
- \u2022 Embed Links
2012
- \u2022 Attach Files
2013
- \u2022 Read Message History
2014
- \u2022 Use External Emojis
2015
- `);
2050
+ constructor(userArg) {
2051
+ super(`User not found: ${userArg}`), this.userArg = userArg;
2052
+ this.response.setTitle("User Not Found").setDescription(`User probably doesn't exist or was deleted.
2053
+ **User Argument:** \`${this.userArg}\`
2054
+ Please check the user ID and try again. Only pass valid user IDs as the argument.`);
2016
2055
  }
2017
2056
  };
2018
- var CouldNotFindChannel = class extends CustomError {
2057
+
2058
+ // src/bot/utilities/users/fetchGuildMember.ts
2059
+ async function fetchGuildMember(guild, userId) {
2060
+ let user = guild.members.cache.get(userId);
2061
+ user ??= await guild.members.fetch(userId).catch(() => {
2062
+ throw new UserNotInGuild(`User with ID ${userId} not found in guild`);
2063
+ });
2064
+ return user;
2065
+ }
2066
+ __name(fetchGuildMember, "fetchGuildMember");
2067
+
2068
+ // src/bot/utilities/users/fetchManyGuildMembers.ts
2069
+ async function fetchManyGuildMembers(guild, userIds) {
2070
+ const results = await Promise.allSettled(userIds.map((userId) => fetchGuildMember(guild, userId)));
2071
+ return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
2072
+ }
2073
+ __name(fetchManyGuildMembers, "fetchManyGuildMembers");
2074
+ async function fetchUser(client, userId) {
2075
+ let user = client.users.cache.get(userId);
2076
+ user ??= await client.users.fetch(userId).catch((err) => {
2077
+ if (err instanceof discord_js.DiscordAPIError && err.code === discord_js.RESTJSONErrorCodes.UnknownUser) {
2078
+ throw new UserNotFound(userId);
2079
+ }
2080
+ throw err;
2081
+ });
2082
+ return user;
2083
+ }
2084
+ __name(fetchUser, "fetchUser");
2085
+
2086
+ // src/bot/utilities/users/fetchManyUsers.ts
2087
+ async function fetchManyUsers(client, userIds) {
2088
+ const results = await Promise.allSettled(userIds.map((userId) => fetchUser(client, userId)));
2089
+ return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
2090
+ }
2091
+ __name(fetchManyUsers, "fetchManyUsers");
2092
+
2093
+ // src/bot/utilities/users/updateMemberRoles.ts
2094
+ async function updateMemberRoles(rolesToAdd, rolesToRemove, member) {
2095
+ const current = new Set(member.roles.cache.map((r) => r.id));
2096
+ const toAdd = new Set(rolesToAdd);
2097
+ const toRemove = new Set(rolesToRemove);
2098
+ const updated = current.union(toAdd).difference(toRemove);
2099
+ await member.roles.set([
2100
+ ...updated
2101
+ ]);
2102
+ }
2103
+ __name(updateMemberRoles, "updateMemberRoles");
2104
+ var EffectsEmitter = class {
2019
2105
  static {
2020
- __name(this, "CouldNotFindChannel");
2106
+ __name(this, "EffectsEmitter");
2021
2107
  }
2022
- channelId;
2108
+ emitter = new events.EventEmitter();
2023
2109
  /**
2024
- * Creates a new CouldNotFindChannel error.
2110
+ * Registers a listener for the specified side effect.
2025
2111
  *
2026
- * @param message - The error message
2027
- * @param channelId - The ID of the channel that could not be found
2112
+ * @typeParam KeyOfEffects - The side effect name type
2113
+ * @param event - The side effect name to listen for
2114
+ * @param listener - Function to call when the event is emitted
2115
+ * @returns This EffectsEmitter instance for chaining
2028
2116
  */
2029
- constructor(message, channelId) {
2030
- super(message), this.channelId = channelId;
2031
- this.response.setDescription(`Could not find channel with ID \`${this.channelId}\`. It could also be that the channel is not a text channel.`);
2032
- }
2033
- };
2034
- var ChannelNotTextChannel = class extends CustomError {
2035
- static {
2036
- __name(this, "ChannelNotTextChannel");
2117
+ on(event, listener) {
2118
+ this.emitter.on(event, listener);
2119
+ return this;
2037
2120
  }
2038
- channelId;
2039
2121
  /**
2040
- * Creates a new ChannelNotTextChannel error.
2122
+ * Registers a one-time listener for the specified side effect.
2041
2123
  *
2042
- * @param message - The error message
2043
- * @param channelId - The ID of the channel that is not a text channel
2124
+ * @typeParam KeyOfEffects - The side effect name type
2125
+ * @param event - The side effect name to listen for once
2126
+ * @param listener - Function to call when the event is emitted
2127
+ * @returns This EffectsEmitter instance for chaining
2044
2128
  */
2045
- constructor(message, channelId) {
2046
- super(message), this.channelId = channelId;
2047
- this.response.setDescription(`Channel with ID \`${this.channelId}\` is not a text channel.`);
2048
- }
2049
- };
2050
- var MissingPermissions = class extends CustomError {
2051
- static {
2052
- __name(this, "MissingPermissions");
2129
+ once(event, listener) {
2130
+ this.emitter.once(event, listener);
2131
+ return this;
2053
2132
  }
2054
- missingPerms;
2055
- roleOrChannel;
2056
2133
  /**
2057
- * Creates a new BotMissingPermissionsError.
2134
+ * Emits a side effect with the provided data.
2058
2135
  *
2059
- * @param message - The error message
2060
- * @param missingPerms - Array of missing permission names
2061
- * @param roleOrChannel - The role or channel where permissions are missing
2136
+ * @typeParam KeyOfEffects - The side effect name type
2137
+ * @param event - The side effect name to emit
2138
+ * @param data - The data to pass to registered listeners
2139
+ * @returns True if the event had listeners, false otherwise
2062
2140
  */
2063
- constructor(message, missingPerms, roleOrChannel) {
2064
- super(message), this.missingPerms = missingPerms, this.roleOrChannel = roleOrChannel;
2065
- const missing = this.missingPerms.map((perm) => `\u2022 ${perm}`).join("\n");
2066
- const errorSubtext = this.roleOrChannel instanceof discord_js.Role ? `My role, <@&${this.roleOrChannel.id}>, is missing the following permissions:` : `I am missing the following permissions in <#${this.roleOrChannel.id}>:`;
2067
- this.response.setDescription(`${errorSubtext}
2068
-
2069
- Please ensure I have the following missing permission(s):
2070
- ${missing}`);
2141
+ emit(event, data) {
2142
+ return this.emitter.emit(event, data);
2071
2143
  }
2072
2144
  };
2073
- var RoleHigherThanMe = class extends CustomError {
2074
- static {
2075
- __name(this, "RoleHigherThanMe");
2076
- }
2077
- role;
2078
- botRole;
2079
- /**
2080
- * Creates a new RoleHigherThanMe error.
2081
- *
2082
- * @param message - The error message
2083
- */
2084
- constructor(message, role, botRole) {
2085
- super(message), this.role = role, this.botRole = botRole;
2086
- this.response.setDescription(`I cannot assign a role that is higher than me.
2087
2145
 
2088
- The role <@&${this.role.id}> is higher than my role <@&${this.botRole.id}> in the hierarchy.`);
2089
- }
2090
- };
2091
- var CannotAssignBotRole = class extends CustomError {
2146
+ // src/effects/EffectsHandler.ts
2147
+ var EffectsHandler = class {
2092
2148
  static {
2093
- __name(this, "CannotAssignBotRole");
2149
+ __name(this, "EffectsHandler");
2094
2150
  }
2151
+ data;
2152
+ core;
2095
2153
  /**
2096
- * Creates a new CannotAssignBotRole error.
2154
+ * Creates a new effects handler instance.
2097
2155
  *
2098
- * @param message - The error message
2156
+ * @param data - The effect event data
2157
+ * @param core - The core framework instance
2099
2158
  */
2100
- constructor(message = "I cannot assign a managed role.") {
2101
- super(message);
2102
- this.response.setDescription("I cannot assign a managed role.");
2159
+ constructor(data, core) {
2160
+ this.data = data;
2161
+ this.core = core;
2162
+ this.data = data;
2163
+ this.core = core;
2103
2164
  }
2104
2165
  };
2105
- var RoleDoesNotExist = class extends CustomError {
2166
+
2167
+ // src/effects/decorators/RegisterEffect.ts
2168
+ var EffectMetadataKey = Symbol("effect:metadata");
2169
+ function RegisterEffect(effect, options) {
2170
+ return function(constructor) {
2171
+ const meta = {
2172
+ effect,
2173
+ frequency: options?.frequency
2174
+ };
2175
+ Reflect.defineMetadata(EffectMetadataKey, meta, constructor);
2176
+ };
2177
+ }
2178
+ __name(RegisterEffect, "RegisterEffect");
2179
+
2180
+ // src/effects/bases/WebhookLog.ts
2181
+ var WebhookLog = class extends EffectsHandler {
2106
2182
  static {
2107
- __name(this, "RoleDoesNotExist");
2183
+ __name(this, "WebhookLog");
2108
2184
  }
2109
- roleId;
2110
- /**
2111
- * Creates a new RoleDoesNotExist error.
2112
- *
2113
- * @param message - The error message
2114
- * @param roleId - The ID of the role that doesn't exist
2115
- */
2116
- constructor(message, roleId) {
2117
- super(message), this.roleId = roleId;
2118
- this.response.setDescription(`The role with ID \`${this.roleId}\` does not exist.`);
2185
+ constructor(data, core) {
2186
+ super(data, core);
2119
2187
  }
2120
2188
  };
2121
- var HasDangerousPermissions = class extends CustomError {
2122
- static {
2123
- __name(this, "HasDangerousPermissions");
2124
- }
2125
- role;
2126
- dangerousPerms;
2127
- /**
2128
- * Creates a new HasDangerousPermissions error.
2129
- *
2130
- * @param message - The error message
2131
- * @param role - The role with dangerous permissions
2132
- * @param dangerousPerms - Array of dangerous permission names
2133
- */
2134
- constructor(message, role, dangerousPerms) {
2135
- super(message), this.role = role, this.dangerousPerms = dangerousPerms;
2136
- const dangerous = this.dangerousPerms.map((perm) => `\u2022 ${perm}`).join("\n");
2137
- this.response.setDescription(`The role <@&${this.role.id}> has the following dangerous permissions:
2138
2189
 
2139
- Please ensure the following dangerous permission(s) are not enabled:
2140
- ${dangerous}`);
2190
+ // src/effects/default/UnknownException.ts
2191
+ function _ts_decorate5(decorators, target, key, desc) {
2192
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2193
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2194
+ 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;
2195
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2196
+ }
2197
+ __name(_ts_decorate5, "_ts_decorate");
2198
+ function _ts_metadata5(k, v) {
2199
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2200
+ }
2201
+ __name(_ts_metadata5, "_ts_metadata");
2202
+ function webhookUrlValidator(raw, _fallback) {
2203
+ if (raw === null) {
2204
+ throw new services.SeedcordError(services.SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing);
2141
2205
  }
2142
- };
2143
-
2144
- // src/bot/errors/User.ts
2145
- var UserNotInGuild = class extends CustomError {
2146
- static {
2147
- __name(this, "UserNotInGuild");
2206
+ if (typeof raw !== "string") {
2207
+ throw new services.SeedcordError(services.SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
2148
2208
  }
2149
- /**
2150
- * Creates a new UserNotInGuild error.
2151
- *
2152
- * @param message - The error message
2153
- */
2154
- constructor(message = "User is not in the guild.") {
2155
- super(message);
2156
- this.response.setDescription("User is not in the guild.");
2209
+ const value = raw.trim();
2210
+ if (value === "") {
2211
+ throw new services.SeedcordError(services.SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing);
2157
2212
  }
2158
- };
2159
- var UserNotFound = class extends CustomError {
2213
+ const pattern = String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`;
2214
+ const discordWebhookRegex = new RegExp(pattern);
2215
+ if (!URL.canParse(value) || !discordWebhookRegex.test(value)) {
2216
+ throw new services.SeedcordError(services.SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
2217
+ }
2218
+ return value;
2219
+ }
2220
+ __name(webhookUrlValidator, "webhookUrlValidator");
2221
+ exports.UnknownException = class _UnknownException extends WebhookLog {
2160
2222
  static {
2161
- __name(this, "UserNotFound");
2223
+ __name(this, "UnknownException");
2162
2224
  }
2163
- userArg;
2164
- /**
2165
- * Creates a new UserNotFound error.
2166
- *
2167
- * @param userArg - The user argument that could not be resolved
2168
- */
2169
- constructor(userArg) {
2170
- super(`User not found: ${userArg}`), this.userArg = userArg;
2171
- this.response.setTitle("User Not Found").setDescription(`User probably doesn't exist or was deleted.
2172
- **User Argument:** \`${this.userArg}\`
2173
- Please check the user ID and try again. Only pass valid user IDs as the argument.`);
2174
- }
2175
- };
2176
- async function fetchText(client, channelId) {
2177
- if (channelId instanceof discord_js.TextChannel) {
2178
- return channelId;
2179
- }
2180
- let channel = client.channels.cache.get(channelId);
2181
- if (!channel) {
2225
+ static logger = new services.Logger("Effect: UnknownException");
2226
+ webhook = new discord_js.WebhookClient({
2227
+ url: _UnknownException.unknownExceptionWebhookUrl
2228
+ });
2229
+ async execute() {
2230
+ const metadataFile = this.prepareMetadataFile();
2182
2231
  try {
2183
- channel = await client.channels.fetch(channelId);
2184
- } catch {
2185
- throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
2186
- }
2187
- }
2188
- if (channel?.isTextBased()) {
2189
- return channel;
2190
- }
2191
- throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
2192
- }
2193
- __name(fetchText, "fetchText");
2194
-
2195
- // src/bot/utilities/channels/sendInText.ts
2196
- async function sendInText(client, channelId, message) {
2197
- const channel = await fetchText(client, channelId);
2198
- return await channel.send(message);
2199
- }
2200
- __name(sendInText, "sendInText");
2201
- function throwCustomError(error, message, CustomError2) {
2202
- const uuid = crypto.randomUUID();
2203
- services.Logger.Error("Throwing Custom Error", error.name);
2204
- if (typeof CustomError2 === typeof DatabaseError) {
2205
- const errorMessage = error instanceof Error ? error.message : message;
2206
- throw new CustomError2(errorMessage, uuid);
2207
- } else {
2208
- if (error instanceof Error) {
2209
- throw new CustomError2(`${message}: ${error.message ? error.message : error.toString()}`);
2210
- } else {
2211
- throw new CustomError2(message);
2232
+ await this.webhook.send({
2233
+ flags: "IsComponentsV2",
2234
+ withComponents: true,
2235
+ username: "Unknown Exception",
2236
+ avatarURL: "https://cdn.discordapp.com/attachments/1351446034827579466/1351446912947191830/warning-2.png",
2237
+ components: [
2238
+ new UnhandledErrorContainer(this.data).component
2239
+ ],
2240
+ files: metadataFile ? [
2241
+ metadataFile
2242
+ ] : []
2243
+ });
2244
+ } catch (error) {
2245
+ _UnknownException.logger.error("Failed to send unknown exception webhook", error);
2212
2246
  }
2213
2247
  }
2214
- }
2215
- __name(throwCustomError, "throwCustomError");
2216
-
2217
- // src/bot/utilities/messages/attemptSendDM.ts
2218
- async function attemptSendDM(user, content) {
2219
- const payload = {
2220
- ...content.content !== void 0 && {
2221
- content: content.content
2222
- },
2223
- ...content.embeds !== void 0 && {
2224
- embeds: [
2225
- ...content.embeds
2226
- ]
2227
- },
2228
- ...content.components !== void 0 && {
2229
- components: [
2230
- ...content.components
2231
- ]
2232
- }
2233
- };
2234
- try {
2235
- return await user.send(payload);
2236
- } catch {
2237
- return null;
2248
+ prepareMetadataFile() {
2249
+ const { metadata } = this.data;
2250
+ if (!metadata) return null;
2251
+ const content = utils.filterCirculars(metadata);
2252
+ return new discord_js.AttachmentBuilder(Buffer.from(JSON.stringify(content, void 0, 2), "utf-8"), {
2253
+ name: "metadata.json",
2254
+ description: "Metadata associated with the error"
2255
+ });
2238
2256
  }
2239
- }
2240
- __name(attemptSendDM, "attemptSendDM");
2241
- var PermissionNames = new Map(Object.entries(discord_js.PermissionFlagsBits).map(([key, bit]) => [
2242
- bit,
2243
- utils.prettify(key)
2244
- ]));
2245
- var PERM_GROUPS = {
2246
- manage: /* @__PURE__ */ new Map([
2247
- [
2248
- discord_js.PermissionFlagsBits.ManageChannels,
2249
- "Manage Channels"
2250
- ],
2251
- [
2252
- discord_js.PermissionFlagsBits.ManageRoles,
2253
- "Manage Roles"
2254
- ],
2255
- [
2256
- discord_js.PermissionFlagsBits.ManageWebhooks,
2257
- "Manage Webhooks"
2258
- ],
2259
- [
2260
- discord_js.PermissionFlagsBits.ManageMessages,
2261
- "Manage Messages"
2262
- ],
2263
- [
2264
- discord_js.PermissionFlagsBits.ManageNicknames,
2265
- "Manage Nicknames"
2266
- ]
2267
- ]),
2268
- embed: /* @__PURE__ */ new Map([
2269
- [
2270
- discord_js.PermissionFlagsBits.ViewChannel,
2271
- "View Channel"
2272
- ],
2273
- [
2274
- discord_js.PermissionFlagsBits.SendMessages,
2275
- "Send Messages"
2276
- ],
2277
- [
2278
- discord_js.PermissionFlagsBits.EmbedLinks,
2279
- "Embed Links"
2280
- ],
2281
- [
2282
- discord_js.PermissionFlagsBits.AttachFiles,
2283
- "Attach Files"
2284
- ],
2285
- [
2286
- discord_js.PermissionFlagsBits.UseExternalEmojis,
2287
- "Use External Emojis"
2288
- ],
2289
- [
2290
- discord_js.PermissionFlagsBits.ReadMessageHistory,
2291
- "Read Message History"
2292
- ]
2293
- ]),
2294
- others: /* @__PURE__ */ new Map([
2295
- [
2296
- discord_js.PermissionFlagsBits.AddReactions,
2297
- "Add Reactions"
2298
- ],
2299
- [
2300
- discord_js.PermissionFlagsBits.UseApplicationCommands,
2301
- "Use Application Commands"
2302
- ]
2303
- ])
2304
2257
  };
2305
- function checkPermissions(client, roleOrChannel, scope = "all", inverse = false) {
2306
- let required;
2307
- if (Array.isArray(scope)) {
2308
- required = /* @__PURE__ */ new Map();
2309
- for (const bit of scope) {
2310
- const name = PermissionNames.get(bit);
2311
- if (name) required.set(bit, name);
2312
- }
2313
- } else {
2314
- switch (scope) {
2315
- case "manage":
2316
- required = PERM_GROUPS.manage;
2317
- break;
2318
- case "embed":
2319
- required = PERM_GROUPS.embed;
2320
- break;
2321
- case "others":
2322
- required = new Map([
2323
- ...PERM_GROUPS.others,
2324
- ...PERM_GROUPS.embed
2325
- ]);
2326
- break;
2327
- default:
2328
- required = new Map([
2329
- ...PERM_GROUPS.manage,
2330
- ...PERM_GROUPS.others,
2331
- ...PERM_GROUPS.embed
2332
- ]);
2333
- break;
2334
- }
2258
+ _ts_decorate5([
2259
+ envapt.Envapt("UNKNOWN_EXCEPTION_WEBHOOK_URL", {
2260
+ converter: /* @__PURE__ */ __name((raw, fallback) => webhookUrlValidator(raw), "converter")
2261
+ }),
2262
+ _ts_metadata5("design:type", String)
2263
+ ], exports.UnknownException, "unknownExceptionWebhookUrl", void 0);
2264
+ exports.UnknownException = _ts_decorate5([
2265
+ RegisterEffect("unknownException")
2266
+ ], exports.UnknownException);
2267
+ var DefaultSeparator = class DefaultSeparator2 extends BuilderComponent {
2268
+ static {
2269
+ __name(this, "DefaultSeparator");
2335
2270
  }
2336
- let permissions;
2337
- if (roleOrChannel instanceof discord_js.Role) {
2338
- permissions = roleOrChannel.permissions;
2339
- } else {
2340
- if (!client.user) {
2341
- throw new services.SeedcordError(services.SeedcordErrorCode.CoreClientUserUnavailable);
2342
- }
2343
- permissions = roleOrChannel.permissionsFor(client.user, true);
2271
+ constructor() {
2272
+ super("separator");
2273
+ this.instance.setSpacing(discord_js.SeparatorSpacingSize.Small).setDivider(true);
2344
2274
  }
2345
- if (!permissions) {
2346
- throw new MissingPermissions("Missing Permissions", Array.from(required.values()), roleOrChannel);
2275
+ };
2276
+ var UnhandledErrorContainer = class UnhandledErrorContainer2 extends BuilderComponent {
2277
+ static {
2278
+ __name(this, "UnhandledErrorContainer");
2347
2279
  }
2348
- if (inverse) {
2349
- const dangerous = Array.from(required.entries()).filter(([bit]) => permissions.has(bit, true)).map(([, name]) => name);
2350
- if (dangerous.length > 0) {
2351
- throw new HasDangerousPermissions("Role has dangerous permissions", roleOrChannel, dangerous);
2352
- }
2353
- } else {
2354
- const missing = Array.from(required.entries()).filter(([bit]) => !permissions.has(bit, true)).map(([, name]) => name);
2355
- if (missing.length > 0) {
2356
- throw new MissingPermissions("Missing Permissions", missing, roleOrChannel);
2357
- }
2280
+ constructor(data) {
2281
+ super("container");
2282
+ const { uuid, error, guild, user, metadata } = data;
2283
+ this.instance.addTextDisplayComponents((text) => text.setContent(`### An unknown exception was thrown
2284
+ **Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`
2285
+ **Guild Name:** ${guild?.name ?? "Not used in a guild"}
2286
+ **User ID:** \`${user?.id ?? "Missing user info"}\`
2287
+ **Username:** ${user?.username ?? "Missing user info"}
2288
+ `)).addSeparatorComponents(new DefaultSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`
2289
+ \`\`\`${error.stack}\`\`\``));
2290
+ this.addTimestampsIfAvailable(error);
2291
+ this.addMetadataIfAvailable(metadata);
2358
2292
  }
2359
- }
2360
- __name(checkPermissions, "checkPermissions");
2361
- function getBotRole(client, guild) {
2362
- if (!client.user) {
2363
- throw new services.SeedcordError(services.SeedcordErrorCode.CoreClientUserUnavailable);
2293
+ addTimestampsIfAvailable(error) {
2294
+ if (!(error instanceof discord_js.DiscordAPIError)) return;
2295
+ const now = Date.now();
2296
+ const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
2297
+ if (!snowflake) return void 0;
2298
+ const interactionTs = Number(discord_js.SnowflakeUtil.deconstruct(snowflake).timestamp);
2299
+ const diff = now - interactionTs;
2300
+ const seconds = Math.floor(diff / 1e3);
2301
+ const millis = diff % 1e3;
2302
+ this.instance.addSeparatorComponents(new DefaultSeparator().component).addTextDisplayComponents((text) => text.setContent(`### Timestamps
2303
+ - **\`Interaction sent\` :** ${new Date(interactionTs).toISOString()} (${interactionTs})
2304
+ - **\`Error logged \` :** ${new Date(now).toISOString()} (${now})
2305
+ - **\`Offset \` :** ${seconds}s ${millis}ms`));
2364
2306
  }
2365
- const botRole = guild.roles.botRoleFor(client.user);
2366
- if (!botRole) {
2367
- throw new services.SeedcordError(services.SeedcordErrorCode.CoreBotRoleMissing, [
2368
- guild.id
2369
- ]);
2307
+ addMetadataIfAvailable(metadata) {
2308
+ if (!metadata) return;
2309
+ this.instance.addSeparatorComponents(new DefaultSeparator().component).addTextDisplayComponents((text) => text.setContent("### Metadata")).addFileComponents((file) => file.setURL("attachment://metadata.json"));
2370
2310
  }
2371
- return botRole;
2372
- }
2373
- __name(getBotRole, "getBotRole");
2311
+ };
2374
2312
 
2375
- // src/bot/utilities/roles/checkBotPermissions.ts
2376
- function checkBotPermissions(client, guildOrChannel, scope = "all", inverse = false) {
2377
- if (guildOrChannel instanceof discord_js.Guild) {
2378
- const botRole = getBotRole(client, guildOrChannel);
2379
- checkPermissions(client, botRole, scope, inverse);
2380
- } else {
2381
- checkPermissions(client, guildOrChannel, scope);
2313
+ // src/effects/EffectsRegistry.ts
2314
+ var EffectsRegistry = class extends Plugin {
2315
+ static {
2316
+ __name(this, "EffectsRegistry");
2382
2317
  }
2383
- }
2384
- __name(checkBotPermissions, "checkBotPermissions");
2385
- async function fetchRole(clientOrGuild, roleId) {
2386
- let role;
2387
- if (!roleId) {
2388
- throw new RoleDoesNotExist("Role ID is null or undefined", roleId);
2318
+ core;
2319
+ logger = new services.Logger("Effects");
2320
+ isInitialized = false;
2321
+ effectsMap = new discord_js.Collection();
2322
+ emitter = new EffectsEmitter();
2323
+ constructor(core) {
2324
+ super(core), this.core = core;
2389
2325
  }
2390
- if (clientOrGuild instanceof discord_js.Guild) {
2391
- const guild = clientOrGuild;
2392
- role = guild.roles.cache.get(roleId);
2393
- if (!role) {
2394
- try {
2395
- role = await guild.roles.fetch(roleId);
2396
- } catch {
2397
- throw new RoleDoesNotExist("Role not found in specified guild", roleId);
2398
- }
2399
- }
2400
- } else {
2401
- const client = clientOrGuild;
2402
- role = client.guilds.cache.map((guild) => guild.roles.cache.get(roleId)).find((role2) => role2);
2403
- if (!role) {
2404
- const guilds = client.guilds.cache;
2405
- for (const guild of guilds.values()) {
2406
- try {
2407
- role = await guild.roles.fetch(roleId);
2408
- if (role) break;
2409
- } catch {
2410
- continue;
2326
+ async init() {
2327
+ if (this.isInitialized) return;
2328
+ this.isInitialized = true;
2329
+ const effectsDir = this.core.config.effects.path;
2330
+ this.logger.info(chalk3__default.default.bold(effectsDir));
2331
+ this.registerEffect(exports.UnknownException, {
2332
+ effect: "unknownException",
2333
+ frequency: "on"
2334
+ });
2335
+ await this.loadEffects(effectsDir);
2336
+ this.attachEffects();
2337
+ const totalEffects = Array.from(this.effectsMap.values()).reduce((acc, handlers) => acc + handlers.length, 0);
2338
+ this.logger.info(`${chalk3__default.default.bold.green("Loaded")}: ${chalk3__default.default.bold.magenta(totalEffects)} side effects`);
2339
+ }
2340
+ async loadEffects(dir) {
2341
+ await utils.traverseDirectory(dir, (_fullPath, relativePath, imported) => {
2342
+ for (const exportName of Object.keys(imported)) {
2343
+ const val = imported[exportName];
2344
+ if (this.isEffectHandler(val)) {
2345
+ const meta = Reflect.getMetadata(EffectMetadataKey, val);
2346
+ this.registerEffect(val, meta);
2347
+ this.logger.info(`${chalk3__default.default.italic("Registered")} ${chalk3__default.default.bold.yellow(val.name)} from ${chalk3__default.default.gray(relativePath)}`);
2411
2348
  }
2412
2349
  }
2350
+ }, this.logger);
2351
+ }
2352
+ registerEffect(handler, options) {
2353
+ let handlers = this.effectsMap.get(options.effect);
2354
+ if (!handlers) {
2355
+ handlers = [];
2356
+ this.effectsMap.set(options.effect, handlers);
2413
2357
  }
2358
+ handlers.push({
2359
+ ctor: handler,
2360
+ frequency: options.frequency ?? "on"
2361
+ });
2414
2362
  }
2415
- if (!role) {
2416
- throw new RoleDoesNotExist("Role not found", roleId);
2363
+ isEffectHandler(obj) {
2364
+ if (typeof obj !== "function") return false;
2365
+ return obj.prototype instanceof EffectsHandler && Reflect.hasMetadata(EffectMetadataKey, obj);
2417
2366
  }
2418
- return role;
2419
- }
2420
- __name(fetchRole, "fetchRole");
2421
- function hasPermsToAssign(targetRole) {
2422
- const botRole = getBotRole(targetRole.client, targetRole.guild);
2423
- if (targetRole.comparePositionTo(botRole) >= 0) {
2424
- throw new RoleHigherThanMe("Role is higher than me", targetRole, botRole);
2367
+ attachEffects() {
2368
+ for (const [effectName, handlerEntries] of this.effectsMap) {
2369
+ for (const entry of handlerEntries) {
2370
+ const register = entry.frequency === "once" ? this.emitter.once.bind(this.emitter) : this.emitter.on.bind(this.emitter);
2371
+ register(effectName, (data) => {
2372
+ try {
2373
+ const instance = new entry.ctor(data, this.core);
2374
+ void instance.execute();
2375
+ } catch (err) {
2376
+ this.logger.error(`Error in side effect ${String(effectName)} handler ${entry.ctor.name}:`, err);
2377
+ }
2378
+ });
2379
+ }
2380
+ }
2425
2381
  }
2426
- if (targetRole.managed) {
2427
- throw new CannotAssignBotRole(`Cannot assign bot role ${targetRole.name}`);
2382
+ emit(event, data) {
2383
+ return this.emitter.emit(event, data);
2428
2384
  }
2429
- checkBotPermissions(targetRole.client, targetRole.guild, [
2430
- discord_js.PermissionFlagsBits.ManageRoles
2431
- ]);
2432
- }
2433
- __name(hasPermsToAssign, "hasPermsToAssign");
2434
-
2435
- // src/bot/utilities/users/fetchGuildMember.ts
2436
- async function fetchGuildMember(guild, userId) {
2437
- let user = guild.members.cache.get(userId);
2438
- user ??= await guild.members.fetch(userId).catch(() => {
2439
- throw new UserNotInGuild(`User with ID ${userId} not found in guild`);
2440
- });
2441
- return user;
2442
- }
2443
- __name(fetchGuildMember, "fetchGuildMember");
2444
-
2445
- // src/bot/utilities/users/fetchManyGuildMembers.ts
2446
- async function fetchManyGuildMembers(guild, userIds) {
2447
- const results = await Promise.allSettled(userIds.map((userId) => fetchGuildMember(guild, userId)));
2448
- return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
2449
- }
2450
- __name(fetchManyGuildMembers, "fetchManyGuildMembers");
2451
- async function fetchUser(client, userId) {
2452
- let user = client.users.cache.get(userId);
2453
- user ??= await client.users.fetch(userId).catch((err) => {
2454
- if (err instanceof discord_js.DiscordAPIError && err.code === discord_js.RESTJSONErrorCodes.UnknownUser) {
2455
- throw new UserNotFound(userId);
2385
+ };
2386
+ var Seedcord = class _Seedcord extends Pluggable {
2387
+ static {
2388
+ __name(this, "Seedcord");
2389
+ }
2390
+ config;
2391
+ static isInstantiated = false;
2392
+ /** @see {@link CoordinatedShutdown} */
2393
+ shutdown;
2394
+ /** @see {@link CoordinatedStartup} */
2395
+ startup;
2396
+ /** @see {@link EffectsRegistry} */
2397
+ effects;
2398
+ /** @see {@link Bot} */
2399
+ bot;
2400
+ /** @see {@link HealthCheck} */
2401
+ healthCheck;
2402
+ /**
2403
+ * Creates a new Seedcord instance
2404
+ *
2405
+ * @param config - Bot configuration including paths and Discord client options
2406
+ * @throws An {@link SeedcordError} When attempting to create multiple instances (singleton)
2407
+ */
2408
+ constructor(config) {
2409
+ const shutdown = new services.CoordinatedShutdown();
2410
+ const startup = new services.CoordinatedStartup();
2411
+ super(shutdown, startup), this.config = config;
2412
+ this.shutdown = shutdown;
2413
+ this.startup = startup;
2414
+ if (_Seedcord.isInstantiated) {
2415
+ throw new services.SeedcordError(services.SeedcordErrorCode.CoreSingletonViolation);
2456
2416
  }
2457
- throw err;
2458
- });
2459
- return user;
2460
- }
2461
- __name(fetchUser, "fetchUser");
2462
-
2463
- // src/bot/utilities/users/fetchManyUsers.ts
2464
- async function fetchManyUsers(client, userIds) {
2465
- const results = await Promise.allSettled(userIds.map((userId) => fetchUser(client, userId)));
2466
- return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
2467
- }
2468
- __name(fetchManyUsers, "fetchManyUsers");
2469
-
2470
- // src/bot/utilities/users/updateMemberRoles.ts
2471
- async function updateMemberRoles(rolesToAdd, rolesToRemove, member) {
2472
- const current = new Set(member.roles.cache.map((r) => r.id));
2473
- const toAdd = new Set(rolesToAdd);
2474
- const toRemove = new Set(rolesToRemove);
2475
- const updated = current.union(toAdd).difference(toRemove);
2476
- await member.roles.set([
2477
- ...updated
2478
- ]);
2479
- }
2480
- __name(updateMemberRoles, "updateMemberRoles");
2417
+ _Seedcord.isInstantiated = true;
2418
+ this.effects = new EffectsRegistry(this);
2419
+ this.bot = new Bot(this);
2420
+ this.healthCheck = new services.HealthCheck(this.shutdown);
2421
+ this.registerStartupTasks();
2422
+ }
2423
+ /**
2424
+ * Registers default startup tasks
2425
+ * @internal
2426
+ */
2427
+ registerStartupTasks() {
2428
+ this.startup.addTask(services.StartupPhase.Configuration, "Effect Initialization", async () => {
2429
+ this.effects.logger.info(chalk3__default.default.bold("Initializing"));
2430
+ await this.effects.init();
2431
+ this.effects.logger.info(chalk3__default.default.bold("Initialized"));
2432
+ });
2433
+ this.startup.addTask(services.StartupPhase.Instantiation, "Bot Initialization", async () => {
2434
+ this.bot.logger.info(chalk3__default.default.bold("Initializing"));
2435
+ await this.bot.init();
2436
+ this.bot.logger.info(chalk3__default.default.bold("Initialized"));
2437
+ });
2438
+ this.startup.addTask(services.StartupPhase.Ready, "Health Check", async () => {
2439
+ this.healthCheck.logger.info(chalk3__default.default.bold("Initializing"));
2440
+ await this.healthCheck.init();
2441
+ this.healthCheck.logger.info(chalk3__default.default.bold("Initialized"));
2442
+ });
2443
+ }
2444
+ /**
2445
+ * Starts the bot and runs all initialization tasks
2446
+ *
2447
+ * @returns This Seedcord instance when fully initialized
2448
+ */
2449
+ async start() {
2450
+ await super.init();
2451
+ return this;
2452
+ }
2453
+ };
2481
2454
 
2482
2455
  exports.AutocompleteHandler = AutocompleteHandler;
2483
2456
  exports.AutocompleteRoute = AutocompleteRoute;
@@ -2524,7 +2497,6 @@ exports.MiddlewareMetadataKey = MiddlewareMetadataKey;
2524
2497
  exports.MiddlewareType = MiddlewareType;
2525
2498
  exports.MissingPermissions = MissingPermissions;
2526
2499
  exports.ModalRoute = ModalRoute;
2527
- exports.PERM_GROUPS = PERM_GROUPS;
2528
2500
  exports.PermissionNames = PermissionNames;
2529
2501
  exports.Pluggable = Pluggable;
2530
2502
  exports.Plugin = Plugin;
@@ -2547,6 +2519,8 @@ exports.attemptSendDM = attemptSendDM;
2547
2519
  exports.buildSlashRoute = buildSlashRoute;
2548
2520
  exports.checkBotPermissions = checkBotPermissions;
2549
2521
  exports.checkPermissions = checkPermissions;
2522
+ exports.ensureBotPermissions = ensureBotPermissions;
2523
+ exports.ensurePermissions = ensurePermissions;
2550
2524
  exports.extractErrorResponse = extractErrorResponse;
2551
2525
  exports.fetchGuildMember = fetchGuildMember;
2552
2526
  exports.fetchManyGuildMembers = fetchManyGuildMembers;