seedcord 0.13.0 → 0.14.0-next.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 +143 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +36 -21
- package/dist/index.mjs +144 -67
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -1217,67 +1217,144 @@ var EmojiInjector = class {
|
|
|
1217
1217
|
async init() {
|
|
1218
1218
|
this.clearEmojis();
|
|
1219
1219
|
if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
|
|
1220
|
-
this.logger.
|
|
1220
|
+
this.logger.debug(chalk.default.bold.yellow("No emojis configured, skipping emoji injection."));
|
|
1221
1221
|
return;
|
|
1222
1222
|
}
|
|
1223
1223
|
const configEmojis = this.core.config.bot.emojis;
|
|
1224
1224
|
await this.core.bot.client.application?.emojis.fetch();
|
|
1225
|
-
|
|
1226
|
-
const
|
|
1227
|
-
for (const [key, value] of entries)
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
continue;
|
|
1235
|
-
}
|
|
1236
|
-
this.logger.warn(`${chalk.default.bold.yellow("Invalid")}: ${chalk.default.magenta.bold(String(key))} (expected string or [string, string])`);
|
|
1237
|
-
}
|
|
1238
|
-
this.logger.utils.summary("Loaded emojis", { emojis: foundCount });
|
|
1239
|
-
}
|
|
1240
|
-
/**
|
|
1241
|
-
* Tuple `[emojiName, guildId]`: looks up the emoji in that guild. Returns 1 when found and stored as an emoji object, otherwise 0.
|
|
1242
|
-
*/
|
|
1243
|
-
handleTuple(key, value) {
|
|
1225
|
+
const failures = [];
|
|
1226
|
+
const fetchedGuilds = /* @__PURE__ */ new Set();
|
|
1227
|
+
for (const [key, value] of Object.entries(configEmojis)) if (isEmojiTuple(value)) await this.resolveTuple(key, value, failures, fetchedGuilds);
|
|
1228
|
+
else if (typeof value === "string") this.resolveString(key, value, failures);
|
|
1229
|
+
else failures.push(` - "${key}" has an invalid value (expected a name or [name, guildId])`);
|
|
1230
|
+
if (failures.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigEmojiUnresolved, [failures.length, failures.join("\n")]);
|
|
1231
|
+
this.logger.utils.summary("Loaded emojis", { emojis: Object.keys(emojiStorage).length });
|
|
1232
|
+
}
|
|
1233
|
+
async resolveTuple(key, value, failures, fetchedGuilds) {
|
|
1244
1234
|
const [emojiName, guildId] = value;
|
|
1245
1235
|
const guild = this.core.bot.client.guilds.cache.get(guildId);
|
|
1246
1236
|
if (!guild) {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1237
|
+
failures.push(` - "${emojiName}" for "${key}" targets guild ${guildId}, which is not available (check the Guilds intent)`);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (!fetchedGuilds.has(guildId)) {
|
|
1241
|
+
await guild.emojis.fetch();
|
|
1242
|
+
fetchedGuilds.add(guildId);
|
|
1250
1243
|
}
|
|
1251
1244
|
const guildEmoji = guild.emojis.cache.find((e) => e.name === emojiName);
|
|
1252
|
-
if (guildEmoji) {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
return 1;
|
|
1245
|
+
if (!guildEmoji) {
|
|
1246
|
+
failures.push(` - "${emojiName}" for "${key}" was not found in guild ${guildId}`);
|
|
1247
|
+
return;
|
|
1256
1248
|
}
|
|
1257
|
-
emojiStorage[key] =
|
|
1258
|
-
this.logger.warn(`${chalk.default.bold.yellow("Missing")}: ${chalk.default.magenta.bold(emojiName)} in guild ${chalk.default.magenta.bold(guildId)} (using provided string)`);
|
|
1259
|
-
return 0;
|
|
1249
|
+
emojiStorage[key] = guildEmoji;
|
|
1260
1250
|
}
|
|
1261
|
-
|
|
1262
|
-
* Handle emoji config values provided as a simple string (application emoji lookup).
|
|
1263
|
-
* Returns 1 when the emoji was found and stored as an emoji object, otherwise 0.
|
|
1264
|
-
*/
|
|
1265
|
-
handleString(key, emojiName) {
|
|
1251
|
+
resolveString(key, emojiName, failures) {
|
|
1266
1252
|
const appEmoji = this.core.bot.client.application?.emojis.cache.find((e) => e.name === emojiName);
|
|
1267
|
-
if (appEmoji) {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
return 1;
|
|
1253
|
+
if (!appEmoji) {
|
|
1254
|
+
failures.push(` - "${emojiName}" for "${key}" was not found among the application emojis`);
|
|
1255
|
+
return;
|
|
1271
1256
|
}
|
|
1272
|
-
emojiStorage[key] =
|
|
1273
|
-
this.logger.warn(`${chalk.default.bold.yellow("Missing")}: ${chalk.default.magenta.bold(emojiName)} (using provided string)`);
|
|
1274
|
-
return 0;
|
|
1257
|
+
emojiStorage[key] = appEmoji;
|
|
1275
1258
|
}
|
|
1276
1259
|
clearEmojis() {
|
|
1277
1260
|
for (const key of Object.keys(emojiStorage)) Reflect.deleteProperty(emojiStorage, key);
|
|
1278
1261
|
}
|
|
1279
1262
|
};
|
|
1280
1263
|
|
|
1264
|
+
//#endregion
|
|
1265
|
+
//#region src/bot/injectors/CommandMentionInjector.ts
|
|
1266
|
+
const mentionStorage = {};
|
|
1267
|
+
/**
|
|
1268
|
+
* Ready-to-send command mention strings, keyed by {@link SlashOptionRegistry}.
|
|
1269
|
+
*
|
|
1270
|
+
* A value is a clickable `</name:id>` when the command's id resolved from the deploy, otherwise the plain
|
|
1271
|
+
* `/name` text. Populated by {@link CommandMentionInjector} after each deploy.
|
|
1272
|
+
*/
|
|
1273
|
+
const CommandMentions = mentionStorage;
|
|
1274
|
+
const MULTI_GUILD = Symbol("seedcord:commandmentions:multi-guild");
|
|
1275
|
+
/**
|
|
1276
|
+
* Turns each deployed slash route into a clickable command mention exposed through {@link CommandMentions}.
|
|
1277
|
+
*
|
|
1278
|
+
* Consumes the ids {@link DeployResult} carries, so it runs after a deploy rather than resolving from a name
|
|
1279
|
+
* cache (discord.js offers none for command ids). A global command is clickable everywhere by its app-wide id.
|
|
1280
|
+
* A guild command deployed to one guild is clickable by that guild's id. A command deployed to two or more
|
|
1281
|
+
* guilds mints a different id per guild, so one accessor key cannot resolve it, and it falls back to plain
|
|
1282
|
+
* `/name` text with one warn. On a non-owning shard a guild command also falls back to plain text.
|
|
1283
|
+
*
|
|
1284
|
+
* @internal
|
|
1285
|
+
*/
|
|
1286
|
+
var CommandMentionInjector = class {
|
|
1287
|
+
core;
|
|
1288
|
+
logger = new _seedcord_services.Logger("CommandMentions");
|
|
1289
|
+
constructor(core) {
|
|
1290
|
+
this.core = core;
|
|
1291
|
+
}
|
|
1292
|
+
inject(deploy) {
|
|
1293
|
+
this.clear();
|
|
1294
|
+
const globalIds = this.indexByName(deploy.global);
|
|
1295
|
+
const guildIds = this.indexGuilds(deploy.guilds);
|
|
1296
|
+
const warned = /* @__PURE__ */ new Set();
|
|
1297
|
+
let clickable = 0;
|
|
1298
|
+
for (const command of this.allSlashBuilders()) {
|
|
1299
|
+
const id = this.resolveId(command.name, globalIds, guildIds, warned);
|
|
1300
|
+
for (const leaf of (0, _seedcord_utils_internal.routeLeavesOf)(command.toJSON())) {
|
|
1301
|
+
mentionStorage[leaf.route] = id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route);
|
|
1302
|
+
if (id) clickable++;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
this.logger.utils.summary("Linked mentions", {
|
|
1306
|
+
clickable,
|
|
1307
|
+
plain: Object.keys(mentionStorage).length - clickable
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
allSlashBuilders() {
|
|
1311
|
+
const registry = this.core.bot.commands;
|
|
1312
|
+
if (!registry) return [];
|
|
1313
|
+
const slash = [...registry.globalCommands, ...registry.guildCommands.values()].flat().filter((command) => command instanceof discord_js.SlashCommandBuilder);
|
|
1314
|
+
return [...new Set(slash)];
|
|
1315
|
+
}
|
|
1316
|
+
indexByName(collection) {
|
|
1317
|
+
const map = /* @__PURE__ */ new Map();
|
|
1318
|
+
for (const command of collection.values()) map.set(command.name, command.id);
|
|
1319
|
+
return map;
|
|
1320
|
+
}
|
|
1321
|
+
indexGuilds(guilds) {
|
|
1322
|
+
const map = /* @__PURE__ */ new Map();
|
|
1323
|
+
for (const collection of guilds.values()) for (const command of collection.values()) {
|
|
1324
|
+
const existing = map.get(command.name);
|
|
1325
|
+
if (existing === void 0) map.set(command.name, command.id);
|
|
1326
|
+
else if (existing !== command.id) map.set(command.name, MULTI_GUILD);
|
|
1327
|
+
}
|
|
1328
|
+
return map;
|
|
1329
|
+
}
|
|
1330
|
+
resolveId(name, globalIds, guildIds, warned) {
|
|
1331
|
+
const global = globalIds.get(name);
|
|
1332
|
+
if (global) return global;
|
|
1333
|
+
const guild = guildIds.get(name);
|
|
1334
|
+
if (guild === void 0) return void 0;
|
|
1335
|
+
if (guild === MULTI_GUILD) {
|
|
1336
|
+
if (!warned.has(name)) {
|
|
1337
|
+
this.logger.warn(`${name} is deployed to multiple guilds, falling back to plain text (not clickable).`);
|
|
1338
|
+
warned.add(name);
|
|
1339
|
+
}
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
return guild;
|
|
1343
|
+
}
|
|
1344
|
+
toMention(route, id) {
|
|
1345
|
+
const [name, middle, sub] = route.split("/");
|
|
1346
|
+
if (name && middle && sub) return (0, discord_js.chatInputApplicationCommandMention)(name, middle, sub, id);
|
|
1347
|
+
if (name && middle) return (0, discord_js.chatInputApplicationCommandMention)(name, middle, id);
|
|
1348
|
+
return (0, discord_js.chatInputApplicationCommandMention)(name ?? route, id);
|
|
1349
|
+
}
|
|
1350
|
+
toPlain(route) {
|
|
1351
|
+
return `/${route.split("/").join(" ")}`;
|
|
1352
|
+
}
|
|
1353
|
+
clear() {
|
|
1354
|
+
for (const key of Object.keys(mentionStorage)) Reflect.deleteProperty(mentionStorage, key);
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1281
1358
|
//#endregion
|
|
1282
1359
|
//#region src/bot/utilities/channels/fetchText.ts
|
|
1283
1360
|
/**
|
|
@@ -2597,6 +2674,7 @@ function slashRouteLeaves(commands) {
|
|
|
2597
2674
|
*/
|
|
2598
2675
|
var CommandRegistry = class {
|
|
2599
2676
|
core;
|
|
2677
|
+
onDeployed;
|
|
2600
2678
|
name = "Commands";
|
|
2601
2679
|
logger = new _seedcord_services.Logger("Commands");
|
|
2602
2680
|
isInitialised = false;
|
|
@@ -2605,8 +2683,9 @@ var CommandRegistry = class {
|
|
|
2605
2683
|
ctorToCommand = /* @__PURE__ */ new Map();
|
|
2606
2684
|
hmrHandler;
|
|
2607
2685
|
pendingEvents = /* @__PURE__ */ new Map();
|
|
2608
|
-
constructor(core) {
|
|
2686
|
+
constructor(core, onDeployed) {
|
|
2609
2687
|
this.core = core;
|
|
2688
|
+
this.onDeployed = onDeployed;
|
|
2610
2689
|
const commandsDir = this.core.config.bot.commands.path;
|
|
2611
2690
|
if (!commandsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
|
|
2612
2691
|
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
@@ -2711,8 +2790,13 @@ var CommandRegistry = class {
|
|
|
2711
2790
|
this.ctorToCommand.delete(ctor);
|
|
2712
2791
|
}
|
|
2713
2792
|
async setCommands() {
|
|
2793
|
+
const result = {
|
|
2794
|
+
global: new discord_js.Collection(),
|
|
2795
|
+
guilds: new discord_js.Collection()
|
|
2796
|
+
};
|
|
2714
2797
|
if (this.globalCommands.length > 0) {
|
|
2715
|
-
await this.core.bot.client.application?.commands.set(this.globalCommands);
|
|
2798
|
+
const deployed = await this.core.bot.client.application?.commands.set(this.globalCommands);
|
|
2799
|
+
if (deployed) result.global = deployed;
|
|
2716
2800
|
const tag = this.globalCommands.length === 1 ? "command" : "commands";
|
|
2717
2801
|
this.logger.utils.summary("Configured global", { [tag]: this.globalCommands.length });
|
|
2718
2802
|
this.logger.utils.item(`${this.globalCommands.map((command) => chalk.default.bold.cyan(command.name)).join(", ")}`);
|
|
@@ -2723,11 +2807,14 @@ var CommandRegistry = class {
|
|
|
2723
2807
|
this.logger.warn(`Guild with ID ${guildId} not found, skipping command registration.`);
|
|
2724
2808
|
continue;
|
|
2725
2809
|
}
|
|
2726
|
-
await guild.commands.set(commands);
|
|
2810
|
+
const deployed = await guild.commands.set(commands);
|
|
2811
|
+
result.guilds.set(guildId, deployed);
|
|
2727
2812
|
const tag = commands.length === 1 ? "command" : "commands";
|
|
2728
2813
|
this.logger.utils.summary(`Configured commands for ${chalk.default.bold.yellow(guild.name)}`, { [tag]: commands.length });
|
|
2729
2814
|
this.logger.utils.item(`${commands.map((command) => chalk.default.bold.cyan(command.name)).join(", ")}`);
|
|
2730
2815
|
}
|
|
2816
|
+
this.onDeployed?.(result);
|
|
2817
|
+
return result;
|
|
2731
2818
|
}
|
|
2732
2819
|
/** The deduplicated slash route keys across every global and guild command. @internal */
|
|
2733
2820
|
routeLeaves() {
|
|
@@ -3505,22 +3592,6 @@ var InteractionDispatcher = class {
|
|
|
3505
3592
|
}
|
|
3506
3593
|
};
|
|
3507
3594
|
|
|
3508
|
-
//#endregion
|
|
3509
|
-
//#region src/miscellaneous/validateDiscordToken.ts
|
|
3510
|
-
/**
|
|
3511
|
-
* Checks if the token is present, is a string, and matches the general format of a Discord token.
|
|
3512
|
-
*
|
|
3513
|
-
* @internal
|
|
3514
|
-
*/
|
|
3515
|
-
function validateDiscordToken(raw) {
|
|
3516
|
-
if (raw === null || raw === void 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigMissingDiscordToken);
|
|
3517
|
-
if (typeof raw !== "string") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigIncorrectDiscordToken);
|
|
3518
|
-
const value = raw.trim();
|
|
3519
|
-
if (value === "") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigMissingDiscordToken);
|
|
3520
|
-
if (!/^[A-Za-z\d_-]{24,}\.[A-Za-z\d_-]{6,}\.[A-Za-z\d_-]{27,}$/.test(value)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigIncorrectDiscordToken);
|
|
3521
|
-
return value;
|
|
3522
|
-
}
|
|
3523
|
-
|
|
3524
3595
|
//#endregion
|
|
3525
3596
|
//#region \0@oxc-project+runtime@0.135.0/helpers/esm/decorateMetadata.js
|
|
3526
3597
|
function __decorateMetadata(k, v) {
|
|
@@ -3553,6 +3624,7 @@ var Bot = class extends Plugin {
|
|
|
3553
3624
|
commands;
|
|
3554
3625
|
emojiInjector;
|
|
3555
3626
|
emojis = Emojis;
|
|
3627
|
+
mentions = CommandMentions;
|
|
3556
3628
|
/** @internal For use in dev mode */
|
|
3557
3629
|
async onHmr(event) {
|
|
3558
3630
|
if (this.interactions) await this.interactions.onHmr(event);
|
|
@@ -3565,7 +3637,12 @@ var Bot = class extends Plugin {
|
|
|
3565
3637
|
this._client = new discord_js.Client(core.config.bot.clientOptions);
|
|
3566
3638
|
if (core.config.bot.interactions.path) this.interactions = new InteractionDispatcher(core);
|
|
3567
3639
|
if (core.config.bot.events.path) this.events = new EventDispatcher(core);
|
|
3568
|
-
if (core.config.bot.commands.path)
|
|
3640
|
+
if (core.config.bot.commands.path) {
|
|
3641
|
+
const mentionInjector = new CommandMentionInjector(core);
|
|
3642
|
+
this.commands = new CommandRegistry(core, (result) => {
|
|
3643
|
+
mentionInjector.inject(result);
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3569
3646
|
this.emojiInjector = new EmojiInjector(core);
|
|
3570
3647
|
core.shutdown.addTask(_seedcord_services.ShutdownPhase.DiscordCleanup, "stop-bot", async () => await this.stop(), 2e3);
|
|
3571
3648
|
}
|
|
@@ -3580,13 +3657,13 @@ var Bot = class extends Plugin {
|
|
|
3580
3657
|
if (this.interactions) await this.interactions.init();
|
|
3581
3658
|
if (this.events) await this.events.init();
|
|
3582
3659
|
await this.login(token);
|
|
3660
|
+
await this.emojiInjector.init();
|
|
3583
3661
|
if (this.commands) {
|
|
3584
3662
|
await this.commands.init();
|
|
3585
3663
|
await this.commands.setCommands();
|
|
3586
3664
|
this.interactions?.warnUnhandledRoutes(this.commands.routeLeaves());
|
|
3587
3665
|
this.interactions?.warnUnhandledContextMenuRoutes(this.commands.contextMenuLeaves());
|
|
3588
3666
|
}
|
|
3589
|
-
await this.emojiInjector.init();
|
|
3590
3667
|
}
|
|
3591
3668
|
/**
|
|
3592
3669
|
* Stops the bot and cleans up connections
|
|
@@ -3618,7 +3695,7 @@ var Bot = class extends Plugin {
|
|
|
3618
3695
|
return super.emit(event, ...args);
|
|
3619
3696
|
}
|
|
3620
3697
|
};
|
|
3621
|
-
__decorate([(0, envapt.Envapt)("DISCORD_BOT_TOKEN", { converter: (raw) => validateDiscordToken(raw) }), __decorateMetadata("design:type", String)], Bot.prototype, "botToken", void 0);
|
|
3698
|
+
__decorate([(0, envapt.Envapt)("DISCORD_BOT_TOKEN", { converter: (raw) => (0, _seedcord_errors_internal.validateDiscordToken)(raw) }), __decorateMetadata("design:type", String)], Bot.prototype, "botToken", void 0);
|
|
3622
3699
|
|
|
3623
3700
|
//#endregion
|
|
3624
3701
|
//#region src/hmr/HmrManager.ts
|
|
@@ -4048,13 +4125,14 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
4048
4125
|
//#endregion
|
|
4049
4126
|
//#region src/index.ts
|
|
4050
4127
|
/** Package version */
|
|
4051
|
-
const version = "0.
|
|
4128
|
+
const version = "0.14.0-next.0";
|
|
4052
4129
|
|
|
4053
4130
|
//#endregion
|
|
4054
4131
|
exports.AutocompleteHandler = AutocompleteHandler;
|
|
4055
4132
|
exports.AutocompleteRoute = AutocompleteRoute;
|
|
4056
4133
|
exports.ButtonHandler = ButtonHandler;
|
|
4057
4134
|
exports.ButtonRoute = ButtonRoute;
|
|
4135
|
+
exports.CommandMentions = CommandMentions;
|
|
4058
4136
|
exports.ContextMenuHandler = ContextMenuHandler;
|
|
4059
4137
|
exports.ContextMenuRoute = ContextMenuRoute;
|
|
4060
4138
|
exports.Cooldown = Cooldown;
|