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