@seedcord/http 0.4.1 → 0.5.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/{edge.index-DK-Y14ge.mjs → edge.index-DwRPmkh2.mjs} +358 -36
- package/dist/edge.index-DwRPmkh2.mjs.map +1 -0
- package/dist/{edge.index-QUU3_n9k.d.mts → edge.index-tJFQMQ2q.d.mts} +198 -19
- package/dist/edge.index.d.mts +3 -3
- package/dist/edge.index.mjs +4 -4
- package/dist/index.d.mts +9 -8
- package/dist/index.mjs +17 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
- package/dist/edge.index-DK-Y14ge.mjs.map +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
|
-
import {
|
|
2
|
+
import { GatedMetadataKey, PublishDefault, RegisterDefaults, RegisterSubscriber, accessorStore, asError, clearStore, decodeComponentRoute, deferFlags, guardedAccessor, isEmojiTuple, outcomeFor, prefixOf, queuedMsFor, reportDispatch, reportedWrite, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
|
|
3
|
+
import { SeedcordError, SeedcordTypeError, applicationIdFromToken, validateDiscordToken } from "@seedcord/errors/internal";
|
|
4
|
+
import { Logger } from "@seedcord/logger";
|
|
3
5
|
import { Converters, Envapter } from "envapt";
|
|
4
6
|
import { DiscordAPIError, REST } from "@discordjs/rest";
|
|
5
|
-
import {
|
|
6
|
-
import { BaseReplySender, GatedMetadataKey, PublishDefault, accessorStore, asError, clearStore, decodeComponentRoute, deferFlags, guardedAccessor, isEmojiTuple, outcomeFor, prefixOf, queuedMsFor, reportDispatch, reportedWrite, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
|
|
7
|
+
import { ArraySourceBase, BaseHandler, BaseReplySender, Bus, CursorSourceBase, DispatchContext, Fault, Notice, PaginatorBase, RepliableHandler, ResolvedEmoji, Silence, Subscriber, WebhookLog } from "@seedcord/core";
|
|
7
8
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
8
|
-
import { Logger } from "@seedcord/logger";
|
|
9
9
|
import { MemoryRateLimiter } from "@seedcord/rate-limiter";
|
|
10
|
-
import { ApplicationCommandOptionType, ApplicationCommandType, ComponentType, InteractionResponseType, InteractionType, MessageFlags, RESTJSONErrorCodes, Routes } from "discord-api-types/v10";
|
|
10
|
+
import { ApplicationCommandOptionType, ApplicationCommandType, ChannelType, ComponentType, InteractionResponseType, InteractionType, MessageFlags, RESTJSONErrorCodes, Routes } from "discord-api-types/v10";
|
|
11
11
|
import { API } from "@discordjs/core/http-only";
|
|
12
12
|
import { buildSlashRoute } from "@seedcord/utils/internal";
|
|
13
13
|
|
|
@@ -270,11 +270,18 @@ function logger() {
|
|
|
270
270
|
dispatchLogger ??= new Logger("Dispatcher", { channel: "interactions" });
|
|
271
271
|
return dispatchLogger;
|
|
272
272
|
}
|
|
273
|
+
function noLifecycle(accessor) {
|
|
274
|
+
throw new SeedcordError(SeedcordErrorCode.CoreLifecycleUnavailable, [accessor]);
|
|
275
|
+
}
|
|
276
|
+
const edgeShutdown = { addTask: () => noLifecycle("shutdown") };
|
|
277
|
+
const edgeStartup = { addTask: () => noLifecycle("startup") };
|
|
273
278
|
function createCore(config, token) {
|
|
274
279
|
const draft = {
|
|
275
280
|
config,
|
|
276
281
|
rateLimiter: config.store ?? new MemoryRateLimiter(),
|
|
277
282
|
rest: new REST().setToken(token),
|
|
283
|
+
shutdown: edgeShutdown,
|
|
284
|
+
startup: edgeStartup,
|
|
278
285
|
get applicationId() {
|
|
279
286
|
return applicationIdFromToken(token);
|
|
280
287
|
}
|
|
@@ -437,7 +444,7 @@ async function dispatchInteraction(args) {
|
|
|
437
444
|
core,
|
|
438
445
|
payload,
|
|
439
446
|
routeId,
|
|
440
|
-
sender: handler instanceof RepliableHandler$1 ? handler.
|
|
447
|
+
sender: handler instanceof RepliableHandler$1 ? handler.sender : null
|
|
441
448
|
};
|
|
442
449
|
const refusal = await gateRefusal(ctor, match, payload, core);
|
|
443
450
|
if (refusal) {
|
|
@@ -469,7 +476,7 @@ async function gateRefusal(ctor, match, payload, core) {
|
|
|
469
476
|
//#endregion
|
|
470
477
|
//#region src/dispatch/registerSubscribers.ts
|
|
471
478
|
function registerSubscribers(bus, manifest) {
|
|
472
|
-
for (const row of manifest.subscriberRoutes) bus
|
|
479
|
+
for (const row of manifest.subscriberRoutes) bus[RegisterSubscriber]({
|
|
473
480
|
keys: row.keys,
|
|
474
481
|
frequency: row.frequency,
|
|
475
482
|
resolve: async () => {
|
|
@@ -486,7 +493,7 @@ function registerSubscribers(bus, manifest) {
|
|
|
486
493
|
row.from
|
|
487
494
|
]);
|
|
488
495
|
const exported = module[row.exportName];
|
|
489
|
-
if (typeof exported !== "function" || !(exported.prototype instanceof Subscriber)) throw new
|
|
496
|
+
if (typeof exported !== "function" || !(exported.prototype instanceof Subscriber)) throw new SeedcordTypeError(SeedcordErrorCode.SubscriberRouteNotASubscriber, [
|
|
490
497
|
keys,
|
|
491
498
|
row.exportName,
|
|
492
499
|
row.from
|
|
@@ -623,7 +630,7 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
623
630
|
const { name, value } = this.focused;
|
|
624
631
|
const respond = (choices) => this.respond(choices);
|
|
625
632
|
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
626
|
-
if (!arm) throw new
|
|
633
|
+
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.AutocompleteMatchArmMissing, [name]);
|
|
627
634
|
return await arm(value, respond);
|
|
628
635
|
}
|
|
629
636
|
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
@@ -845,7 +852,7 @@ var Ed25519Verifier = class {
|
|
|
845
852
|
key;
|
|
846
853
|
constructor(publicKeyHex) {
|
|
847
854
|
const publicKey = hexToBytes(publicKeyHex, PUBLIC_KEY_BYTES);
|
|
848
|
-
if (!publicKey) throw new
|
|
855
|
+
if (!publicKey) throw new SeedcordTypeError(SeedcordErrorCode.ConfigInvalidEnv, ["DISCORD_PUBLIC_KEY", "64 hex characters"]);
|
|
849
856
|
this.key = crypto.subtle.importKey("raw", publicKey, "Ed25519", false, ["verify"]);
|
|
850
857
|
}
|
|
851
858
|
async verify(signatureHex, timestamp, body) {
|
|
@@ -982,15 +989,192 @@ function buildEngine(core, maps) {
|
|
|
982
989
|
*
|
|
983
990
|
* Reads `DISCORD_PUBLIC_KEY` and `DISCORD_BOT_TOKEN` from the environment through envapt and throws a
|
|
984
991
|
* `SeedcordError` when either is missing or malformed.
|
|
992
|
+
*
|
|
993
|
+
* Applies `config.logger`. An omitted `logger` resets the level and the sinks to their defaults, the
|
|
994
|
+
* way `installNodeDefaults` does on the node hosts.
|
|
985
995
|
*/
|
|
986
996
|
function createSeedcord(config, manifest) {
|
|
997
|
+
Logger.configure(config.logger ?? {});
|
|
987
998
|
const token = validateDiscordToken(Envapter.get("DISCORD_BOT_TOKEN"));
|
|
988
999
|
const core = createCore(config, token);
|
|
989
|
-
core.bus
|
|
1000
|
+
core.bus[RegisterDefaults]();
|
|
990
1001
|
registerSubscribers(core.bus, manifest);
|
|
991
1002
|
return buildEngine(core, buildRouteMaps(manifest)).handle;
|
|
992
1003
|
}
|
|
993
1004
|
|
|
1005
|
+
//#endregion
|
|
1006
|
+
//#region src/inputs/pick.ts
|
|
1007
|
+
function pick(ids, bucket) {
|
|
1008
|
+
const found = /* @__PURE__ */ new Map();
|
|
1009
|
+
if (!ids || !bucket) return found;
|
|
1010
|
+
for (const id of ids) if (Object.hasOwn(bucket, id)) found.set(id, bucket[id]);
|
|
1011
|
+
return found;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
//#endregion
|
|
1015
|
+
//#region src/inputs/ModalFields.ts
|
|
1016
|
+
const FIELD_KINDS = {
|
|
1017
|
+
[ComponentType.TextInput]: {
|
|
1018
|
+
label: "a text input",
|
|
1019
|
+
getter: "getTextInputValue"
|
|
1020
|
+
},
|
|
1021
|
+
[ComponentType.StringSelect]: {
|
|
1022
|
+
label: "a string select",
|
|
1023
|
+
getter: "getStringSelectValues"
|
|
1024
|
+
},
|
|
1025
|
+
[ComponentType.UserSelect]: {
|
|
1026
|
+
label: "a user select",
|
|
1027
|
+
getter: "getSelectedUsers"
|
|
1028
|
+
},
|
|
1029
|
+
[ComponentType.RoleSelect]: {
|
|
1030
|
+
label: "a role select",
|
|
1031
|
+
getter: "getSelectedRoles"
|
|
1032
|
+
},
|
|
1033
|
+
[ComponentType.MentionableSelect]: {
|
|
1034
|
+
label: "a mentionable select",
|
|
1035
|
+
getter: "getSelectedMentionables"
|
|
1036
|
+
},
|
|
1037
|
+
[ComponentType.ChannelSelect]: {
|
|
1038
|
+
label: "a channel select",
|
|
1039
|
+
getter: "getSelectedChannels"
|
|
1040
|
+
},
|
|
1041
|
+
[ComponentType.FileUpload]: {
|
|
1042
|
+
label: "a file upload",
|
|
1043
|
+
getter: "getUploadedFiles"
|
|
1044
|
+
},
|
|
1045
|
+
[ComponentType.RadioGroup]: {
|
|
1046
|
+
label: "a radio group",
|
|
1047
|
+
getter: "getRadioGroup"
|
|
1048
|
+
},
|
|
1049
|
+
[ComponentType.CheckboxGroup]: {
|
|
1050
|
+
label: "a checkbox group",
|
|
1051
|
+
getter: "getCheckboxGroup"
|
|
1052
|
+
},
|
|
1053
|
+
[ComponentType.Checkbox]: {
|
|
1054
|
+
label: "a checkbox",
|
|
1055
|
+
getter: "getCheckbox"
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
function nameOfChannelType(type) {
|
|
1059
|
+
return ChannelType[type] ?? String(type);
|
|
1060
|
+
}
|
|
1061
|
+
function listOf(values) {
|
|
1062
|
+
return values ?? [];
|
|
1063
|
+
}
|
|
1064
|
+
function isKind(entry, kinds) {
|
|
1065
|
+
return kinds.includes(entry.type);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* The fields a modal carries, read by custom id.
|
|
1069
|
+
*
|
|
1070
|
+
* Every getter throws when no field in the modal has that custom id. It also throws when the field holds a
|
|
1071
|
+
* kind that getter does not read. The message names the getter that does.
|
|
1072
|
+
*
|
|
1073
|
+
* A select getter resolves the ids its field picked against the interaction's `resolved` data and returns a
|
|
1074
|
+
* `Map` keyed by id. A field that picked nothing returns null. Pass `required` to throw there instead.
|
|
1075
|
+
*/
|
|
1076
|
+
var ModalFields = class {
|
|
1077
|
+
entries = /* @__PURE__ */ new Map();
|
|
1078
|
+
resolved;
|
|
1079
|
+
constructor(data) {
|
|
1080
|
+
this.resolved = data.resolved;
|
|
1081
|
+
for (const component of data.components ?? []) {
|
|
1082
|
+
if ("components" in component) for (const child of component.components) this.entries.set(child.custom_id, child);
|
|
1083
|
+
if ("component" in component) this.entries.set(component.component.custom_id, component.component);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
entry(customId) {
|
|
1087
|
+
const found = this.entries.get(customId);
|
|
1088
|
+
if (!found) throw new SeedcordTypeError(SeedcordErrorCode.ModalFieldNotFound, [customId]);
|
|
1089
|
+
return found;
|
|
1090
|
+
}
|
|
1091
|
+
field(customId, kinds) {
|
|
1092
|
+
const entry = this.entry(customId);
|
|
1093
|
+
if (!isKind(entry, kinds)) {
|
|
1094
|
+
const known = FIELD_KINDS[entry.type];
|
|
1095
|
+
const args = known ? [
|
|
1096
|
+
customId,
|
|
1097
|
+
known.label,
|
|
1098
|
+
known.getter
|
|
1099
|
+
] : [customId, `component type ${entry.type}`];
|
|
1100
|
+
throw new SeedcordTypeError(SeedcordErrorCode.ModalFieldWrongKind, [...args]);
|
|
1101
|
+
}
|
|
1102
|
+
return entry;
|
|
1103
|
+
}
|
|
1104
|
+
selected(customId, kinds, bucket, required) {
|
|
1105
|
+
const { values } = this.field(customId, kinds);
|
|
1106
|
+
const found = pick(values, bucket);
|
|
1107
|
+
if (found.size > 0) return found;
|
|
1108
|
+
if (required) throw new SeedcordTypeError(SeedcordErrorCode.ModalFieldEmpty, [customId]);
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
getField(customId, kind) {
|
|
1112
|
+
return kind === void 0 ? this.entry(customId) : this.field(customId, [kind]);
|
|
1113
|
+
}
|
|
1114
|
+
getTextInputValue(customId) {
|
|
1115
|
+
return this.field(customId, [ComponentType.TextInput]).value;
|
|
1116
|
+
}
|
|
1117
|
+
getStringSelectValues(customId) {
|
|
1118
|
+
return listOf(this.field(customId, [ComponentType.StringSelect]).values);
|
|
1119
|
+
}
|
|
1120
|
+
getSelectedUsers(customId, required = false) {
|
|
1121
|
+
const kinds = [ComponentType.UserSelect, ComponentType.MentionableSelect];
|
|
1122
|
+
return this.selected(customId, kinds, this.resolved?.users, required);
|
|
1123
|
+
}
|
|
1124
|
+
/** The guild members behind the picked users. Discord resolves these only inside a guild. */
|
|
1125
|
+
getSelectedMembers(customId) {
|
|
1126
|
+
const kinds = [ComponentType.UserSelect, ComponentType.MentionableSelect];
|
|
1127
|
+
return this.selected(customId, kinds, this.resolved?.members, false);
|
|
1128
|
+
}
|
|
1129
|
+
getSelectedRoles(customId, required = false) {
|
|
1130
|
+
const kinds = [ComponentType.RoleSelect, ComponentType.MentionableSelect];
|
|
1131
|
+
return this.selected(customId, kinds, this.resolved?.roles, required);
|
|
1132
|
+
}
|
|
1133
|
+
getSelectedChannels(customId, required = false, channelTypes = []) {
|
|
1134
|
+
const kinds = [ComponentType.ChannelSelect];
|
|
1135
|
+
const picked = this.selected(customId, kinds, this.resolved?.channels, required);
|
|
1136
|
+
if (!picked || channelTypes.length === 0) return picked;
|
|
1137
|
+
for (const found of picked.values()) {
|
|
1138
|
+
if (channelTypes.includes(found.type)) continue;
|
|
1139
|
+
const allowed = channelTypes.map(nameOfChannelType).join(", ");
|
|
1140
|
+
throw new SeedcordTypeError(SeedcordErrorCode.ModalFieldChannelType, [
|
|
1141
|
+
customId,
|
|
1142
|
+
nameOfChannelType(found.type),
|
|
1143
|
+
allowed
|
|
1144
|
+
]);
|
|
1145
|
+
}
|
|
1146
|
+
return picked;
|
|
1147
|
+
}
|
|
1148
|
+
getSelectedMentionables(customId, required = false) {
|
|
1149
|
+
const { values } = this.field(customId, [ComponentType.MentionableSelect]);
|
|
1150
|
+
const resolved = this.resolved;
|
|
1151
|
+
const picked = {
|
|
1152
|
+
users: pick(values, resolved?.users),
|
|
1153
|
+
members: pick(values, resolved?.members),
|
|
1154
|
+
roles: pick(values, resolved?.roles)
|
|
1155
|
+
};
|
|
1156
|
+
if (picked.users.size + picked.members.size + picked.roles.size > 0) return picked;
|
|
1157
|
+
if (required) throw new SeedcordTypeError(SeedcordErrorCode.ModalFieldEmpty, [customId]);
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
getUploadedFiles(customId, required = false) {
|
|
1161
|
+
const kinds = [ComponentType.FileUpload];
|
|
1162
|
+
return this.selected(customId, kinds, this.resolved?.attachments, required);
|
|
1163
|
+
}
|
|
1164
|
+
getRadioGroup(customId, required = false) {
|
|
1165
|
+
const value = this.field(customId, [ComponentType.RadioGroup]).value ?? null;
|
|
1166
|
+
if (value !== null) return value;
|
|
1167
|
+
if (required) throw new SeedcordTypeError(SeedcordErrorCode.ModalFieldEmpty, [customId]);
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
getCheckboxGroup(customId) {
|
|
1171
|
+
return listOf(this.field(customId, [ComponentType.CheckboxGroup]).values);
|
|
1172
|
+
}
|
|
1173
|
+
getCheckbox(customId) {
|
|
1174
|
+
return this.field(customId, [ComponentType.Checkbox]).value;
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
|
|
994
1178
|
//#endregion
|
|
995
1179
|
//#region src/emojis/EmojiInjector.ts
|
|
996
1180
|
const emojiStorage = accessorStore();
|
|
@@ -1077,9 +1261,14 @@ var EmojiInjector = class {
|
|
|
1077
1261
|
var SlashHandler = class extends InteractionHandler {
|
|
1078
1262
|
resolver;
|
|
1079
1263
|
/**
|
|
1080
|
-
* The typed options for this command's route.
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1264
|
+
* The typed options for this command's route. Only the getters for kinds this command declares appear.
|
|
1265
|
+
* Each getter's name argument is restricted to that command's options of its kind.
|
|
1266
|
+
*
|
|
1267
|
+
* - A required option drops the null.
|
|
1268
|
+
* - Choices narrow to their literal union.
|
|
1269
|
+
* - A channel option declared with `addChannelTypes` narrows to those types.
|
|
1270
|
+
*
|
|
1271
|
+
* Rich kinds resolve from the interaction's `resolved` data.
|
|
1083
1272
|
*/
|
|
1084
1273
|
get options() {
|
|
1085
1274
|
this.resolver ??= new HttpSlashOptions(this.event.data);
|
|
@@ -1099,13 +1288,18 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
1099
1288
|
async match(arms) {
|
|
1100
1289
|
const route = slashRouteOf(this.event.data);
|
|
1101
1290
|
const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
|
|
1102
|
-
if (!arm) throw new
|
|
1291
|
+
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.SlashMatchArmMissing, [route]);
|
|
1103
1292
|
return await arm(this.options);
|
|
1104
1293
|
}
|
|
1105
1294
|
};
|
|
1106
1295
|
|
|
1107
1296
|
//#endregion
|
|
1108
1297
|
//#region src/handlers/interaction/ContextMenuHandler.ts
|
|
1298
|
+
function armFor(arms, name) {
|
|
1299
|
+
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
1300
|
+
if (typeof arm !== "function") throw new SeedcordTypeError(SeedcordErrorCode.ContextMenuMatchArmMissing, [name]);
|
|
1301
|
+
return arm;
|
|
1302
|
+
}
|
|
1109
1303
|
/**
|
|
1110
1304
|
* Base class for a user context-menu command handler on the HTTP transport (right-click a user).
|
|
1111
1305
|
*
|
|
@@ -1124,6 +1318,37 @@ var UserContextMenuHandler = class extends InteractionHandler {
|
|
|
1124
1318
|
const { target_id: targetId, resolved } = this.event.data;
|
|
1125
1319
|
return resolved.members?.[targetId] ?? null;
|
|
1126
1320
|
}
|
|
1321
|
+
/** Which of the registered commands fired. */
|
|
1322
|
+
get commandName() {
|
|
1323
|
+
return this.event.data.name;
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Run the arm for whichever command fired. Use this only when the handler is registered for several
|
|
1327
|
+
* names.
|
|
1328
|
+
*
|
|
1329
|
+
* Provide one arm per registered name, keyed by the name Discord shows in the right-click menu. Each
|
|
1330
|
+
* arm receives the clicked user and that user's guild member. The arms must cover every name in the
|
|
1331
|
+
* generic. A missing name or an unknown key is a compile error.
|
|
1332
|
+
*
|
|
1333
|
+
* @param arms - One handler per registered command name.
|
|
1334
|
+
* @returns The result of the arm that ran.
|
|
1335
|
+
*
|
|
1336
|
+
* @example
|
|
1337
|
+
* ```ts
|
|
1338
|
+
* \@UserContextMenuRoute('View Profile', 'Warn')
|
|
1339
|
+
* class Moderation extends UserContextMenuHandler<'View Profile' | 'Warn'> {
|
|
1340
|
+
* async execute() {
|
|
1341
|
+
* await this.match({
|
|
1342
|
+
* 'View Profile': (user) => this.reply(`Profile for ${user.username}.`),
|
|
1343
|
+
* Warn: (user, member) => this.reply(`Warned ${member?.nick ?? user.username}.`)
|
|
1344
|
+
* });
|
|
1345
|
+
* }
|
|
1346
|
+
* }
|
|
1347
|
+
* ```
|
|
1348
|
+
*/
|
|
1349
|
+
async match(arms) {
|
|
1350
|
+
return await armFor(arms, this.event.data.name)(this.target, this.targetMember);
|
|
1351
|
+
}
|
|
1127
1352
|
};
|
|
1128
1353
|
/**
|
|
1129
1354
|
* Base class for a message context-menu command handler on the HTTP transport (right-click a message).
|
|
@@ -1138,6 +1363,37 @@ var MessageContextMenuHandler = class extends InteractionHandler {
|
|
|
1138
1363
|
const { target_id: targetId, resolved } = this.event.data;
|
|
1139
1364
|
return resolved.messages[targetId];
|
|
1140
1365
|
}
|
|
1366
|
+
/** Which of the registered commands fired. */
|
|
1367
|
+
get commandName() {
|
|
1368
|
+
return this.event.data.name;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Run the arm for whichever command fired. Use this only when the handler is registered for several
|
|
1372
|
+
* names.
|
|
1373
|
+
*
|
|
1374
|
+
* Provide one arm per registered name, keyed by the name Discord shows in the right-click menu. Each
|
|
1375
|
+
* arm receives the clicked message. The arms must cover every name in the generic. A missing name or
|
|
1376
|
+
* an unknown key is a compile error.
|
|
1377
|
+
*
|
|
1378
|
+
* @param arms - One handler per registered command name.
|
|
1379
|
+
* @returns The result of the arm that ran.
|
|
1380
|
+
*
|
|
1381
|
+
* @example
|
|
1382
|
+
* ```ts
|
|
1383
|
+
* \@MessageContextMenuRoute('Report Message', 'Quote Message')
|
|
1384
|
+
* class MessageTools extends MessageContextMenuHandler<'Report Message' | 'Quote Message'> {
|
|
1385
|
+
* async execute() {
|
|
1386
|
+
* await this.match({
|
|
1387
|
+
* 'Report Message': (message) => this.report(message),
|
|
1388
|
+
* 'Quote Message': (message) => this.reply(`> ${message.content}`)
|
|
1389
|
+
* });
|
|
1390
|
+
* }
|
|
1391
|
+
* }
|
|
1392
|
+
* ```
|
|
1393
|
+
*/
|
|
1394
|
+
async match(arms) {
|
|
1395
|
+
return await armFor(arms, this.event.data.name)(this.target);
|
|
1396
|
+
}
|
|
1141
1397
|
};
|
|
1142
1398
|
|
|
1143
1399
|
//#endregion
|
|
@@ -1206,7 +1462,7 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1206
1462
|
async match(arms) {
|
|
1207
1463
|
const { prefix, params } = this.route;
|
|
1208
1464
|
const arm = Object.hasOwn(arms, prefix) ? arms[prefix] : void 0;
|
|
1209
|
-
if (!arm) throw new
|
|
1465
|
+
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.CustomIdMatchArmMissing, [prefix]);
|
|
1210
1466
|
return await arm(params);
|
|
1211
1467
|
}
|
|
1212
1468
|
};
|
|
@@ -1231,12 +1487,30 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1231
1487
|
* Base class for a modal submit handler on the HTTP transport.
|
|
1232
1488
|
*
|
|
1233
1489
|
* Register the customId definitions this handler decodes with `@ModalRoute` and list the same ones in the
|
|
1234
|
-
* generic. Read the submitted inputs
|
|
1235
|
-
* Discord forbids opening a modal in response to a modal
|
|
1490
|
+
* generic. Read the submitted inputs from `this.fields`. Reply through the handler members. `showModal`
|
|
1491
|
+
* fails compilation on this kind, because Discord forbids opening a modal in response to a modal.
|
|
1236
1492
|
*
|
|
1237
1493
|
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ConfigId]`.
|
|
1494
|
+
*
|
|
1495
|
+
* @example
|
|
1496
|
+
* ```ts
|
|
1497
|
+
* \@ModalRoute(ConfigId)
|
|
1498
|
+
* class ConfigModal extends ModalHandler<[typeof ConfigId]> {
|
|
1499
|
+
* async execute() {
|
|
1500
|
+
* const { guildId } = this.params;
|
|
1501
|
+
* const name = this.fields.getTextInputValue('name');
|
|
1502
|
+
* await this.reply(`saved ${name} for ${guildId}`);
|
|
1503
|
+
* }
|
|
1504
|
+
* }
|
|
1505
|
+
* ```
|
|
1238
1506
|
*/
|
|
1239
1507
|
var ModalHandler = class extends ComponentHandler {
|
|
1508
|
+
reader;
|
|
1509
|
+
/** The fields this modal submitted, read by custom id. */
|
|
1510
|
+
get fields() {
|
|
1511
|
+
this.reader ??= new ModalFields(this.event.data);
|
|
1512
|
+
return this.reader;
|
|
1513
|
+
}
|
|
1240
1514
|
/** Rewrite the message this modal was opened from. Throws when a command opened the modal. */
|
|
1241
1515
|
update(response) {
|
|
1242
1516
|
this.ensureSourceMessage("update");
|
|
@@ -1258,15 +1532,55 @@ var ModalHandler = class extends ComponentHandler {
|
|
|
1258
1532
|
/**
|
|
1259
1533
|
* Base class for a select menu handler on the HTTP transport.
|
|
1260
1534
|
*
|
|
1261
|
-
* Pass the select kind first and the customId definitions second, the same order as `@SelectMenuRoute
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1535
|
+
* Pass the select kind first and the customId definitions second, the same order as `@SelectMenuRoute`.
|
|
1536
|
+
* Read the picked ids from `this.values`. The kind decides which of `this.users`, `this.members`,
|
|
1537
|
+
* `this.roles`, and `this.channels` this handler carries. The rest are `never`.
|
|
1538
|
+
*
|
|
1539
|
+
* Read `this.params` for a single route or `this.match` for several. Reply through the handler members, or
|
|
1540
|
+
* rewrite the source message with `this.update`.
|
|
1265
1541
|
*
|
|
1266
1542
|
* @typeParam Kind - The select kind from {@link SelectMenuKind}, e.g. `SelectMenuKind.User`.
|
|
1267
1543
|
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof AssignId]`.
|
|
1544
|
+
*
|
|
1545
|
+
* @example
|
|
1546
|
+
* ```ts
|
|
1547
|
+
* \@SelectMenuRoute(SelectMenuKind.User, AssignId)
|
|
1548
|
+
* class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {
|
|
1549
|
+
* async execute() {
|
|
1550
|
+
* const { roleId } = this.params;
|
|
1551
|
+
* await this.reply(`assigning ${this.users.size} member(s) to <@&${roleId}>`);
|
|
1552
|
+
* }
|
|
1553
|
+
* }
|
|
1554
|
+
* ```
|
|
1268
1555
|
*/
|
|
1269
|
-
var SelectMenuHandler = class extends ComponentHandler {
|
|
1556
|
+
var SelectMenuHandler = class extends ComponentHandler {
|
|
1557
|
+
get selection() {
|
|
1558
|
+
return this.event.data;
|
|
1559
|
+
}
|
|
1560
|
+
resolvedMap(bucket) {
|
|
1561
|
+
return pick(this.selection.values, bucket);
|
|
1562
|
+
}
|
|
1563
|
+
/** The ids this select picked. */
|
|
1564
|
+
get values() {
|
|
1565
|
+
return this.selection.values ?? [];
|
|
1566
|
+
}
|
|
1567
|
+
/** The users this select picked. */
|
|
1568
|
+
get users() {
|
|
1569
|
+
return this.resolvedMap(this.selection.resolved?.users);
|
|
1570
|
+
}
|
|
1571
|
+
/** The guild members behind the picked users. Discord resolves these only inside a guild. */
|
|
1572
|
+
get members() {
|
|
1573
|
+
return this.resolvedMap(this.selection.resolved?.members);
|
|
1574
|
+
}
|
|
1575
|
+
/** The roles this select picked. */
|
|
1576
|
+
get roles() {
|
|
1577
|
+
return this.resolvedMap(this.selection.resolved?.roles);
|
|
1578
|
+
}
|
|
1579
|
+
/** The channels this select picked. */
|
|
1580
|
+
get channels() {
|
|
1581
|
+
return this.resolvedMap(this.selection.resolved?.channels);
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1270
1584
|
|
|
1271
1585
|
//#endregion
|
|
1272
1586
|
//#region src/subscribers/index.ts
|
|
@@ -1318,25 +1632,33 @@ var Paginator = class extends PaginatorBase {
|
|
|
1318
1632
|
Handler;
|
|
1319
1633
|
constructor(config) {
|
|
1320
1634
|
super(config);
|
|
1321
|
-
const
|
|
1635
|
+
const pageFor = (handler, n) => this.page(handler, n);
|
|
1322
1636
|
this.Handler = class Nav extends ButtonHandler {
|
|
1323
1637
|
async execute() {
|
|
1324
1638
|
await this.deferUpdate();
|
|
1325
|
-
const response = await
|
|
1639
|
+
const response = await pageFor(this, this.params.page);
|
|
1326
1640
|
await this.update(response);
|
|
1327
1641
|
}
|
|
1328
1642
|
};
|
|
1329
1643
|
}
|
|
1330
1644
|
/**
|
|
1331
|
-
* Render page
|
|
1332
|
-
*
|
|
1645
|
+
* Render a page as a {@link ReplyResponse} without sending anything.
|
|
1646
|
+
*
|
|
1647
|
+
* @param handler - The handler rendering the page, normally `this`.
|
|
1648
|
+
* @param n - The zero-based page.
|
|
1649
|
+
*/
|
|
1650
|
+
async page(handler, n) {
|
|
1651
|
+
return this.buildPage(contextOf(handler.getEvent(), handler.core), n);
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Send a page through the handler's sender. The sender picks reply, edit, or followUp from its ack state.
|
|
1333
1655
|
*
|
|
1334
1656
|
* @param handler - The handler starting the paginator, normally `this`.
|
|
1657
|
+
* @param n - The zero-based page to open on.
|
|
1335
1658
|
*/
|
|
1336
|
-
async start(handler) {
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1339
|
-
return handler.getSender().send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
1659
|
+
async start(handler, n = 0) {
|
|
1660
|
+
const response = await this.page(handler, n);
|
|
1661
|
+
return handler.sender.send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
1340
1662
|
}
|
|
1341
1663
|
};
|
|
1342
1664
|
|
|
@@ -1349,7 +1671,7 @@ var Paginator = class extends PaginatorBase {
|
|
|
1349
1671
|
*
|
|
1350
1672
|
* @typeParam Item - The item type, inferred from the loader's return.
|
|
1351
1673
|
*/
|
|
1352
|
-
var ArraySource
|
|
1674
|
+
var ArraySource = class extends ArraySourceBase {};
|
|
1353
1675
|
/**
|
|
1354
1676
|
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
1355
1677
|
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
@@ -1358,7 +1680,7 @@ var ArraySource$1 = class extends ArraySource {};
|
|
|
1358
1680
|
*
|
|
1359
1681
|
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
1360
1682
|
*/
|
|
1361
|
-
var CursorSource
|
|
1683
|
+
var CursorSource = class extends CursorSourceBase {};
|
|
1362
1684
|
|
|
1363
1685
|
//#endregion
|
|
1364
1686
|
//#region src/gates/Gated.ts
|
|
@@ -1403,8 +1725,8 @@ function Gated(...gates) {
|
|
|
1403
1725
|
|
|
1404
1726
|
//#endregion
|
|
1405
1727
|
//#region src/version.ts
|
|
1406
|
-
const version = "0.
|
|
1728
|
+
const version = "0.5.0";
|
|
1407
1729
|
|
|
1408
1730
|
//#endregion
|
|
1409
|
-
export { RepliableHandler$1 as S,
|
|
1410
|
-
//# sourceMappingURL=edge.index-
|
|
1731
|
+
export { RepliableHandler$1 as C, AutocompleteHandler as S, ModalFields as _, Paginator as a, buildRouteMaps as b, SelectMenuHandler as c, ComponentHandler as d, MessageContextMenuHandler as f, Emojis as g, EmojiInjector as h, CursorSource as i, ModalHandler as l, SlashHandler as m, Gated as n, Subscriber$1 as o, UserContextMenuHandler as p, ArraySource as r, WebhookLog$1 as s, version as t, ButtonHandler as u, createSeedcord as v, ReplySender as w, InteractionHandler as x, buildEngine as y };
|
|
1732
|
+
//# sourceMappingURL=edge.index-DwRPmkh2.mjs.map
|