@seedcord/http 0.4.0 → 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-D9PlLJ9b.mjs → edge.index-DwRPmkh2.mjs} +361 -48
- package/dist/edge.index-DwRPmkh2.mjs.map +1 -0
- package/dist/{edge.index-Ce1r0bL4.d.mts → edge.index-tJFQMQ2q.d.mts} +199 -20
- package/dist/edge.index.d.mts +3 -3
- package/dist/edge.index.mjs +4 -4
- package/dist/index.d.mts +12 -12
- package/dist/index.mjs +27 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
- package/dist/edge.index-D9PlLJ9b.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,21 @@ 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
|
-
rest: new REST().setToken(token)
|
|
282
|
+
rest: new REST().setToken(token),
|
|
283
|
+
shutdown: edgeShutdown,
|
|
284
|
+
startup: edgeStartup,
|
|
285
|
+
get applicationId() {
|
|
286
|
+
return applicationIdFromToken(token);
|
|
287
|
+
}
|
|
278
288
|
};
|
|
279
289
|
draft.bus = new Bus(draft);
|
|
280
290
|
return draft;
|
|
@@ -434,7 +444,7 @@ async function dispatchInteraction(args) {
|
|
|
434
444
|
core,
|
|
435
445
|
payload,
|
|
436
446
|
routeId,
|
|
437
|
-
sender: handler instanceof RepliableHandler$1 ? handler.
|
|
447
|
+
sender: handler instanceof RepliableHandler$1 ? handler.sender : null
|
|
438
448
|
};
|
|
439
449
|
const refusal = await gateRefusal(ctor, match, payload, core);
|
|
440
450
|
if (refusal) {
|
|
@@ -466,7 +476,7 @@ async function gateRefusal(ctor, match, payload, core) {
|
|
|
466
476
|
//#endregion
|
|
467
477
|
//#region src/dispatch/registerSubscribers.ts
|
|
468
478
|
function registerSubscribers(bus, manifest) {
|
|
469
|
-
for (const row of manifest.subscriberRoutes) bus
|
|
479
|
+
for (const row of manifest.subscriberRoutes) bus[RegisterSubscriber]({
|
|
470
480
|
keys: row.keys,
|
|
471
481
|
frequency: row.frequency,
|
|
472
482
|
resolve: async () => {
|
|
@@ -483,7 +493,7 @@ function registerSubscribers(bus, manifest) {
|
|
|
483
493
|
row.from
|
|
484
494
|
]);
|
|
485
495
|
const exported = module[row.exportName];
|
|
486
|
-
if (typeof exported !== "function" || !(exported.prototype instanceof Subscriber)) throw new
|
|
496
|
+
if (typeof exported !== "function" || !(exported.prototype instanceof Subscriber)) throw new SeedcordTypeError(SeedcordErrorCode.SubscriberRouteNotASubscriber, [
|
|
487
497
|
keys,
|
|
488
498
|
row.exportName,
|
|
489
499
|
row.from
|
|
@@ -620,7 +630,7 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
620
630
|
const { name, value } = this.focused;
|
|
621
631
|
const respond = (choices) => this.respond(choices);
|
|
622
632
|
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
623
|
-
if (!arm) throw new
|
|
633
|
+
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.AutocompleteMatchArmMissing, [name]);
|
|
624
634
|
return await arm(value, respond);
|
|
625
635
|
}
|
|
626
636
|
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
@@ -842,7 +852,7 @@ var Ed25519Verifier = class {
|
|
|
842
852
|
key;
|
|
843
853
|
constructor(publicKeyHex) {
|
|
844
854
|
const publicKey = hexToBytes(publicKeyHex, PUBLIC_KEY_BYTES);
|
|
845
|
-
if (!publicKey) throw new
|
|
855
|
+
if (!publicKey) throw new SeedcordTypeError(SeedcordErrorCode.ConfigInvalidEnv, ["DISCORD_PUBLIC_KEY", "64 hex characters"]);
|
|
846
856
|
this.key = crypto.subtle.importKey("raw", publicKey, "Ed25519", false, ["verify"]);
|
|
847
857
|
}
|
|
848
858
|
async verify(signatureHex, timestamp, body) {
|
|
@@ -979,24 +989,191 @@ function buildEngine(core, maps) {
|
|
|
979
989
|
*
|
|
980
990
|
* Reads `DISCORD_PUBLIC_KEY` and `DISCORD_BOT_TOKEN` from the environment through envapt and throws a
|
|
981
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.
|
|
982
995
|
*/
|
|
983
996
|
function createSeedcord(config, manifest) {
|
|
997
|
+
Logger.configure(config.logger ?? {});
|
|
984
998
|
const token = validateDiscordToken(Envapter.get("DISCORD_BOT_TOKEN"));
|
|
985
999
|
const core = createCore(config, token);
|
|
986
|
-
core.bus
|
|
1000
|
+
core.bus[RegisterDefaults]();
|
|
987
1001
|
registerSubscribers(core.bus, manifest);
|
|
988
1002
|
return buildEngine(core, buildRouteMaps(manifest)).handle;
|
|
989
1003
|
}
|
|
990
1004
|
|
|
991
1005
|
//#endregion
|
|
992
|
-
//#region src/
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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"
|
|
998
1056
|
}
|
|
1057
|
+
};
|
|
1058
|
+
function nameOfChannelType(type) {
|
|
1059
|
+
return ChannelType[type] ?? String(type);
|
|
999
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
|
+
};
|
|
1000
1177
|
|
|
1001
1178
|
//#endregion
|
|
1002
1179
|
//#region src/emojis/EmojiInjector.ts
|
|
@@ -1011,16 +1188,14 @@ function byName(list) {
|
|
|
1011
1188
|
}
|
|
1012
1189
|
/**
|
|
1013
1190
|
* The bot's resolved emojis, keyed by {@link EmojiMap}. Filled by {@link EmojiInjector} during startup.
|
|
1014
|
-
*
|
|
1191
|
+
* Throws if you read a key the injector did not fill.
|
|
1015
1192
|
*/
|
|
1016
1193
|
const Emojis = guardedAccessor("Emojis", emojiStorage);
|
|
1017
1194
|
var EmojiInjector = class {
|
|
1018
1195
|
core;
|
|
1019
|
-
applicationId;
|
|
1020
1196
|
logger = new Logger("Emojis", { channel: "bot" });
|
|
1021
|
-
constructor(core
|
|
1197
|
+
constructor(core) {
|
|
1022
1198
|
this.core = core;
|
|
1023
|
-
this.applicationId = applicationId;
|
|
1024
1199
|
}
|
|
1025
1200
|
async init() {
|
|
1026
1201
|
clearStore(emojiStorage);
|
|
@@ -1042,7 +1217,7 @@ var EmojiInjector = class {
|
|
|
1042
1217
|
}
|
|
1043
1218
|
async applicationEmojis(failures) {
|
|
1044
1219
|
try {
|
|
1045
|
-
const appId =
|
|
1220
|
+
const appId = this.core.applicationId;
|
|
1046
1221
|
return byName((await this.core.rest.get(Routes.applicationEmojis(appId))).items);
|
|
1047
1222
|
} catch (error) {
|
|
1048
1223
|
failures.push(` - the application emojis could not be read (${reasonOf(error)})`);
|
|
@@ -1086,9 +1261,14 @@ var EmojiInjector = class {
|
|
|
1086
1261
|
var SlashHandler = class extends InteractionHandler {
|
|
1087
1262
|
resolver;
|
|
1088
1263
|
/**
|
|
1089
|
-
* The typed options for this command's route.
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
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.
|
|
1092
1272
|
*/
|
|
1093
1273
|
get options() {
|
|
1094
1274
|
this.resolver ??= new HttpSlashOptions(this.event.data);
|
|
@@ -1108,13 +1288,18 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
1108
1288
|
async match(arms) {
|
|
1109
1289
|
const route = slashRouteOf(this.event.data);
|
|
1110
1290
|
const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
|
|
1111
|
-
if (!arm) throw new
|
|
1291
|
+
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.SlashMatchArmMissing, [route]);
|
|
1112
1292
|
return await arm(this.options);
|
|
1113
1293
|
}
|
|
1114
1294
|
};
|
|
1115
1295
|
|
|
1116
1296
|
//#endregion
|
|
1117
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
|
+
}
|
|
1118
1303
|
/**
|
|
1119
1304
|
* Base class for a user context-menu command handler on the HTTP transport (right-click a user).
|
|
1120
1305
|
*
|
|
@@ -1133,6 +1318,37 @@ var UserContextMenuHandler = class extends InteractionHandler {
|
|
|
1133
1318
|
const { target_id: targetId, resolved } = this.event.data;
|
|
1134
1319
|
return resolved.members?.[targetId] ?? null;
|
|
1135
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
|
+
}
|
|
1136
1352
|
};
|
|
1137
1353
|
/**
|
|
1138
1354
|
* Base class for a message context-menu command handler on the HTTP transport (right-click a message).
|
|
@@ -1147,6 +1363,37 @@ var MessageContextMenuHandler = class extends InteractionHandler {
|
|
|
1147
1363
|
const { target_id: targetId, resolved } = this.event.data;
|
|
1148
1364
|
return resolved.messages[targetId];
|
|
1149
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
|
+
}
|
|
1150
1397
|
};
|
|
1151
1398
|
|
|
1152
1399
|
//#endregion
|
|
@@ -1215,7 +1462,7 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1215
1462
|
async match(arms) {
|
|
1216
1463
|
const { prefix, params } = this.route;
|
|
1217
1464
|
const arm = Object.hasOwn(arms, prefix) ? arms[prefix] : void 0;
|
|
1218
|
-
if (!arm) throw new
|
|
1465
|
+
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.CustomIdMatchArmMissing, [prefix]);
|
|
1219
1466
|
return await arm(params);
|
|
1220
1467
|
}
|
|
1221
1468
|
};
|
|
@@ -1240,12 +1487,30 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1240
1487
|
* Base class for a modal submit handler on the HTTP transport.
|
|
1241
1488
|
*
|
|
1242
1489
|
* Register the customId definitions this handler decodes with `@ModalRoute` and list the same ones in the
|
|
1243
|
-
* generic. Read the submitted inputs
|
|
1244
|
-
* 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.
|
|
1245
1492
|
*
|
|
1246
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
|
+
* ```
|
|
1247
1506
|
*/
|
|
1248
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
|
+
}
|
|
1249
1514
|
/** Rewrite the message this modal was opened from. Throws when a command opened the modal. */
|
|
1250
1515
|
update(response) {
|
|
1251
1516
|
this.ensureSourceMessage("update");
|
|
@@ -1267,15 +1532,55 @@ var ModalHandler = class extends ComponentHandler {
|
|
|
1267
1532
|
/**
|
|
1268
1533
|
* Base class for a select menu handler on the HTTP transport.
|
|
1269
1534
|
*
|
|
1270
|
-
* Pass the select kind first and the customId definitions second, the same order as `@SelectMenuRoute
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
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`.
|
|
1274
1541
|
*
|
|
1275
1542
|
* @typeParam Kind - The select kind from {@link SelectMenuKind}, e.g. `SelectMenuKind.User`.
|
|
1276
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
|
+
* ```
|
|
1277
1555
|
*/
|
|
1278
|
-
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
|
+
};
|
|
1279
1584
|
|
|
1280
1585
|
//#endregion
|
|
1281
1586
|
//#region src/subscribers/index.ts
|
|
@@ -1327,25 +1632,33 @@ var Paginator = class extends PaginatorBase {
|
|
|
1327
1632
|
Handler;
|
|
1328
1633
|
constructor(config) {
|
|
1329
1634
|
super(config);
|
|
1330
|
-
const
|
|
1635
|
+
const pageFor = (handler, n) => this.page(handler, n);
|
|
1331
1636
|
this.Handler = class Nav extends ButtonHandler {
|
|
1332
1637
|
async execute() {
|
|
1333
1638
|
await this.deferUpdate();
|
|
1334
|
-
const response = await
|
|
1639
|
+
const response = await pageFor(this, this.params.page);
|
|
1335
1640
|
await this.update(response);
|
|
1336
1641
|
}
|
|
1337
1642
|
};
|
|
1338
1643
|
}
|
|
1339
1644
|
/**
|
|
1340
|
-
* Render page
|
|
1341
|
-
*
|
|
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.
|
|
1342
1655
|
*
|
|
1343
1656
|
* @param handler - The handler starting the paginator, normally `this`.
|
|
1657
|
+
* @param n - The zero-based page to open on.
|
|
1344
1658
|
*/
|
|
1345
|
-
async start(handler) {
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
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 });
|
|
1349
1662
|
}
|
|
1350
1663
|
};
|
|
1351
1664
|
|
|
@@ -1358,7 +1671,7 @@ var Paginator = class extends PaginatorBase {
|
|
|
1358
1671
|
*
|
|
1359
1672
|
* @typeParam Item - The item type, inferred from the loader's return.
|
|
1360
1673
|
*/
|
|
1361
|
-
var ArraySource
|
|
1674
|
+
var ArraySource = class extends ArraySourceBase {};
|
|
1362
1675
|
/**
|
|
1363
1676
|
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
1364
1677
|
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
@@ -1367,7 +1680,7 @@ var ArraySource$1 = class extends ArraySource {};
|
|
|
1367
1680
|
*
|
|
1368
1681
|
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
1369
1682
|
*/
|
|
1370
|
-
var CursorSource
|
|
1683
|
+
var CursorSource = class extends CursorSourceBase {};
|
|
1371
1684
|
|
|
1372
1685
|
//#endregion
|
|
1373
1686
|
//#region src/gates/Gated.ts
|
|
@@ -1412,8 +1725,8 @@ function Gated(...gates) {
|
|
|
1412
1725
|
|
|
1413
1726
|
//#endregion
|
|
1414
1727
|
//#region src/version.ts
|
|
1415
|
-
const version = "0.
|
|
1728
|
+
const version = "0.5.0";
|
|
1416
1729
|
|
|
1417
1730
|
//#endregion
|
|
1418
|
-
export { RepliableHandler$1 as C, AutocompleteHandler as S,
|
|
1419
|
-
//# 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
|