@seedcord/http 0.1.0-next.1 → 0.1.0-next.3
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/{RouteManifest-BHdAeyLG.d.mts → RouteManifest-DE-HCMwd.d.mts} +27 -22
- package/dist/{edge.index-D-60VfMn.cjs → edge.index-Bd6sbr37.cjs} +580 -93
- package/dist/edge.index-Bd6sbr37.cjs.map +1 -0
- package/dist/{edge.index-BAgNb6TV.mjs → edge.index-Cv4plNWY.mjs} +523 -96
- package/dist/edge.index-Cv4plNWY.mjs.map +1 -0
- package/dist/{edge.index-CTOyjsO4.d.mts → edge.index-atnWgXP-.d.mts} +150 -47
- package/dist/edge.index.cjs +7 -1
- package/dist/edge.index.d.mts +4 -3
- package/dist/edge.index.mjs +2 -2
- package/dist/index.cjs +121 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +42 -16
- package/dist/index.mjs +116 -42
- package/dist/index.mjs.map +1 -1
- package/dist/manifest.index.cjs +22 -5
- package/dist/manifest.index.cjs.map +1 -1
- package/dist/manifest.index.d.mts +3 -3
- package/dist/manifest.index.mjs +22 -5
- package/dist/manifest.index.mjs.map +1 -1
- package/package.json +8 -7
- package/dist/edge.index-BAgNb6TV.mjs.map +0 -1
- package/dist/edge.index-D-60VfMn.cjs.map +0 -1
|
@@ -2,14 +2,14 @@ import { n as __reExport, t as __exportAll } from "./rolldown-runtime-BUUfjzRC.m
|
|
|
2
2
|
import { SeedcordError, validateDiscordToken } from "@seedcord/errors/internal";
|
|
3
3
|
import { Converters, Envapter } from "envapt";
|
|
4
4
|
import { DiscordAPIError, REST } from "@discordjs/rest";
|
|
5
|
-
import { BaseHandler, DispatchContext, Fault, Notice, RepliableHandler, Silence } from "@seedcord/core";
|
|
6
|
-
import { BaseReplySender, GatedMetadataKey, decodeComponentRoute, deferFlags, prefixOf, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
|
|
5
|
+
import { ArraySource, BaseHandler, Bus, CursorSource, DispatchContext, Fault, Notice, PaginatorBase, RepliableHandler, Silence, Subscriber, WebhookLog } from "@seedcord/core";
|
|
6
|
+
import { BaseReplySender, FaultThrottle, GatedMetadataKey, PublishDefault, accessorStore, asError, clearStore, decodeComponentRoute, deferFlags, dispatchedPayload, guardedAccessor, isEmojiTuple, outcomeFor, prefixOf, queuedMsFor, reportedWrite, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
|
|
7
7
|
import { Logger, paint } from "@seedcord/logger";
|
|
8
8
|
import { MemoryRateLimiter } from "@seedcord/rate-limiter";
|
|
9
9
|
import { ApplicationCommandOptionType, ApplicationCommandType, ComponentType, InteractionResponseType, InteractionType, MessageFlags, RESTJSONErrorCodes, Routes } from "discord-api-types/v10";
|
|
10
10
|
import { API } from "@discordjs/core/http-only";
|
|
11
|
-
import { SeedcordErrorCode } from "@seedcord/errors";
|
|
12
11
|
import { buildSlashRoute } from "@seedcord/utils/internal";
|
|
12
|
+
import { SeedcordErrorCode } from "@seedcord/errors";
|
|
13
13
|
|
|
14
14
|
//#region src/reply/ReplySender.ts
|
|
15
15
|
function wireAllowedMentions(mentions) {
|
|
@@ -43,8 +43,11 @@ function callbackData(reply, flags) {
|
|
|
43
43
|
var ReplySender = class extends BaseReplySender {
|
|
44
44
|
ref;
|
|
45
45
|
rest;
|
|
46
|
-
constructor(ref, rest, routeId) {
|
|
47
|
-
super(routeId
|
|
46
|
+
constructor(ref, rest, routeId, bus) {
|
|
47
|
+
super(routeId, "unacked", bus && {
|
|
48
|
+
bus,
|
|
49
|
+
interactionId: ref.id
|
|
50
|
+
});
|
|
48
51
|
this.ref = ref;
|
|
49
52
|
this.rest = rest;
|
|
50
53
|
}
|
|
@@ -140,12 +143,16 @@ function apiFor(rest) {
|
|
|
140
143
|
* @typeParam Event - The repliable interaction type this handler processes
|
|
141
144
|
*/
|
|
142
145
|
var RepliableHandler$1 = class extends RepliableHandler {
|
|
146
|
+
/** @internal */
|
|
147
|
+
getEvent() {
|
|
148
|
+
return this.event;
|
|
149
|
+
}
|
|
143
150
|
buildSender(event, core, routeId) {
|
|
144
151
|
return new ReplySender({
|
|
145
152
|
application_id: event.application_id,
|
|
146
153
|
id: event.id,
|
|
147
154
|
token: event.token
|
|
148
|
-
}, core.rest, routeId);
|
|
155
|
+
}, core.rest, routeId, core.bus);
|
|
149
156
|
}
|
|
150
157
|
/**
|
|
151
158
|
* Typed Discord REST API (`@discordjs/core/http-only`) over `core.rest`, one instance per core.
|
|
@@ -178,31 +185,132 @@ function interactionGateContext(payload, core, routeId) {
|
|
|
178
185
|
};
|
|
179
186
|
}
|
|
180
187
|
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/dispatch/slashRouteOf.ts
|
|
190
|
+
/**
|
|
191
|
+
* Computes the dispatch route for a chat-input or autocomplete payload, the raw-payload port of the
|
|
192
|
+
* gateway's `slashRouteOf`. Both assemble keys through `buildSlashRoute`, so a dispatched interaction
|
|
193
|
+
* and a generated registry key always match.
|
|
194
|
+
*/
|
|
195
|
+
function slashRouteOf(data) {
|
|
196
|
+
const first = data.options?.[0];
|
|
197
|
+
if (first?.type === ApplicationCommandOptionType.SubcommandGroup) return buildSlashRoute(data.name, first.options[0]?.name, first.name);
|
|
198
|
+
if (first?.type === ApplicationCommandOptionType.Subcommand) return buildSlashRoute(data.name, first.name);
|
|
199
|
+
return data.name;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/dispatch/reportFault.ts
|
|
204
|
+
let faultLogger;
|
|
205
|
+
function logger$1() {
|
|
206
|
+
faultLogger ??= new Logger("Faults", { channel: "errors" });
|
|
207
|
+
return faultLogger;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Publishes a fault on the bus, the raw-payload equivalent of the reporting half of gateway's
|
|
211
|
+
* `extractErrorResponse`. A reported {@link Notice} publishes `handledException`, everything else
|
|
212
|
+
* publishes `unknownException`.
|
|
213
|
+
*
|
|
214
|
+
* @internal
|
|
215
|
+
*/
|
|
216
|
+
function reportFault(error, uuid, payload, core) {
|
|
217
|
+
const throttle = FaultThrottle.for(core);
|
|
218
|
+
const key = `${routeKeyOf(payload)}:${nameOf(error)}`;
|
|
219
|
+
if (!throttle.shouldReport(key)) {
|
|
220
|
+
logger$1().debug(`throttled duplicate fault ${paint.mute(key)}`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const source = interactionSource(payload);
|
|
224
|
+
if (error instanceof Notice && source) core.bus[PublishDefault]("handledException", {
|
|
225
|
+
denial: error,
|
|
226
|
+
uuid,
|
|
227
|
+
source
|
|
228
|
+
});
|
|
229
|
+
else core.bus[PublishDefault]("unknownException", {
|
|
230
|
+
uuid,
|
|
231
|
+
error,
|
|
232
|
+
...actors(payload),
|
|
233
|
+
metadata: payload
|
|
234
|
+
});
|
|
235
|
+
throttle.markReported(key);
|
|
236
|
+
}
|
|
237
|
+
function routeKeyOf(payload) {
|
|
238
|
+
if (payload.type === InteractionType.ApplicationCommandAutocomplete) return `autocomplete:${slashRouteOf(payload.data)}`;
|
|
239
|
+
if (payload.type === InteractionType.ApplicationCommand) return payload.data.type === ApplicationCommandType.ChatInput ? slashRouteOf(payload.data) : payload.data.name;
|
|
240
|
+
return prefixOf(payload.data.custom_id) || payload.data.custom_id;
|
|
241
|
+
}
|
|
242
|
+
function nameOf(error) {
|
|
243
|
+
if (error instanceof Notice) return error.name;
|
|
244
|
+
if (error instanceof DiscordAPIError) return String(error.code);
|
|
245
|
+
return error.constructor.name;
|
|
246
|
+
}
|
|
247
|
+
function actors(payload) {
|
|
248
|
+
const user = payload.member?.user ?? payload.user;
|
|
249
|
+
return {
|
|
250
|
+
guild: void 0,
|
|
251
|
+
user: user ? {
|
|
252
|
+
id: user.id,
|
|
253
|
+
username: user.username
|
|
254
|
+
} : void 0
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function interactionSource(payload) {
|
|
258
|
+
const user = payload.member?.user ?? payload.user;
|
|
259
|
+
if (!user) return null;
|
|
260
|
+
const common = {
|
|
261
|
+
kind: "interaction",
|
|
262
|
+
userId: user.id,
|
|
263
|
+
guildId: payload.guild_id ?? null,
|
|
264
|
+
channelId: payload.channel?.id ?? null,
|
|
265
|
+
interactionId: payload.id,
|
|
266
|
+
raw: payload
|
|
267
|
+
};
|
|
268
|
+
if (payload.type === InteractionType.ApplicationCommand) {
|
|
269
|
+
const isSlash = payload.data.type === ApplicationCommandType.ChatInput;
|
|
270
|
+
return {
|
|
271
|
+
...common,
|
|
272
|
+
interactionKind: isSlash ? "slash" : "context-menu",
|
|
273
|
+
command: routeKeyOf(payload),
|
|
274
|
+
customId: null
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (payload.type === InteractionType.ModalSubmit) return {
|
|
278
|
+
...common,
|
|
279
|
+
interactionKind: "modal",
|
|
280
|
+
command: null,
|
|
281
|
+
customId: payload.data.custom_id
|
|
282
|
+
};
|
|
283
|
+
if (payload.type === InteractionType.MessageComponent) {
|
|
284
|
+
const isButton = payload.data.component_type === ComponentType.Button;
|
|
285
|
+
return {
|
|
286
|
+
...common,
|
|
287
|
+
interactionKind: isButton ? "button" : "select",
|
|
288
|
+
command: null,
|
|
289
|
+
customId: payload.data.custom_id
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
181
295
|
//#endregion
|
|
182
296
|
//#region src/dispatch/dispatchInteraction.ts
|
|
183
297
|
let dispatchLogger;
|
|
184
298
|
function logger() {
|
|
185
|
-
dispatchLogger ??= new Logger("Dispatcher");
|
|
299
|
+
dispatchLogger ??= new Logger("Dispatcher", { channel: "interactions" });
|
|
186
300
|
return dispatchLogger;
|
|
187
301
|
}
|
|
188
302
|
function createCore(config, token) {
|
|
189
|
-
|
|
303
|
+
const draft = {
|
|
190
304
|
config,
|
|
191
305
|
rateLimiter: config.store ?? new MemoryRateLimiter(),
|
|
192
|
-
rest: new REST().setToken(token)
|
|
193
|
-
version: "0.1.0-next.1",
|
|
194
|
-
username: void 0,
|
|
195
|
-
augmentTarget: "@seedcord/http"
|
|
306
|
+
rest: new REST().setToken(token)
|
|
196
307
|
};
|
|
308
|
+
draft.bus = new Bus(draft);
|
|
309
|
+
return draft;
|
|
197
310
|
}
|
|
198
311
|
function isHandlerCtor(value) {
|
|
199
312
|
return typeof value === "function" && value.prototype instanceof BaseHandler;
|
|
200
313
|
}
|
|
201
|
-
function handlerCtorOf(moduleExports) {
|
|
202
|
-
if (typeof moduleExports !== "object" || moduleExports === null) return null;
|
|
203
|
-
for (const value of Object.values(moduleExports)) if (isHandlerCtor(value)) return value;
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
314
|
const HARMLESS_API_CODES = new Set([
|
|
207
315
|
RESTJSONErrorCodes.UnknownInteraction,
|
|
208
316
|
RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
|
|
@@ -220,10 +328,13 @@ async function sendGuarded(routeId, send) {
|
|
|
220
328
|
}
|
|
221
329
|
}
|
|
222
330
|
async function respondEmptyChoices(scope) {
|
|
223
|
-
await
|
|
331
|
+
await reportedWrite({
|
|
332
|
+
bus: scope.core.bus,
|
|
333
|
+
interactionId: scope.payload.id
|
|
334
|
+
}, scope.routeId, "respond", () => scope.core.rest.post(Routes.interactionCallback(scope.payload.id, scope.payload.token), { body: {
|
|
224
335
|
type: InteractionResponseType.ApplicationCommandAutocompleteResult,
|
|
225
336
|
data: { choices: [] }
|
|
226
|
-
} });
|
|
337
|
+
} }));
|
|
227
338
|
}
|
|
228
339
|
function renderContext(core, uuid) {
|
|
229
340
|
const developerUsername = core.config.notifications?.developerUsername;
|
|
@@ -233,7 +344,10 @@ function renderContext(core, uuid) {
|
|
|
233
344
|
};
|
|
234
345
|
}
|
|
235
346
|
async function handleNotice(notice, uuid, scope) {
|
|
236
|
-
if (notice.report)
|
|
347
|
+
if (notice.report) {
|
|
348
|
+
logger().error(`${notice.name}: ${paint.mute(uuid)}`, notice);
|
|
349
|
+
reportFault(notice, uuid, scope.payload, scope.core);
|
|
350
|
+
}
|
|
237
351
|
const { sender } = scope;
|
|
238
352
|
if (!sender) {
|
|
239
353
|
await sendGuarded(scope.routeId, () => respondEmptyChoices(scope));
|
|
@@ -246,6 +360,7 @@ async function handleRawFault(error, uuid, scope) {
|
|
|
246
360
|
const { core, sender } = scope;
|
|
247
361
|
if (core.config.errors?.errorStack ?? false) logger().error(paint.mute(uuid), error);
|
|
248
362
|
else logger().error(`${paint.mute(uuid)} | ${error.message}`);
|
|
363
|
+
reportFault(error, uuid, scope.payload, core);
|
|
249
364
|
if (!sender) {
|
|
250
365
|
await sendGuarded(scope.routeId, () => respondEmptyChoices(scope));
|
|
251
366
|
return;
|
|
@@ -256,7 +371,7 @@ async function handleRawFault(error, uuid, scope) {
|
|
|
256
371
|
}
|
|
257
372
|
async function handleFault(caught, scope) {
|
|
258
373
|
if (caught instanceof Silence) {
|
|
259
|
-
if (caught.reason !== void 0) logger().debug(`Silence: ${caught.reason}`);
|
|
374
|
+
if (caught.reason !== void 0 && (scope.core.config.errors?.logSilences ?? true)) logger().debug(`Silence: ${caught.reason}`);
|
|
260
375
|
return;
|
|
261
376
|
}
|
|
262
377
|
const uuid = crypto.randomUUID();
|
|
@@ -264,7 +379,7 @@ async function handleFault(caught, scope) {
|
|
|
264
379
|
await handleNotice(caught, uuid, scope);
|
|
265
380
|
return;
|
|
266
381
|
}
|
|
267
|
-
const error =
|
|
382
|
+
const error = asError(caught);
|
|
268
383
|
const ignore = new Set(scope.core.config.errors?.ignoreApiCodes ?? []);
|
|
269
384
|
if (error instanceof DiscordAPIError && ignore.has(error.code)) {
|
|
270
385
|
logger().debug(`swallowed api code ${paint.amber(String(error.code))}`);
|
|
@@ -272,79 +387,149 @@ async function handleFault(caught, scope) {
|
|
|
272
387
|
}
|
|
273
388
|
await handleRawFault(error, uuid, scope);
|
|
274
389
|
}
|
|
390
|
+
function unhandledRouteId(match) {
|
|
391
|
+
if (match.routeId) return match.routeId;
|
|
392
|
+
const key = match.attemptedKey ?? "";
|
|
393
|
+
return `${match.kind}:${key.length > 0 ? key : "unrouted"}`;
|
|
394
|
+
}
|
|
275
395
|
function freshScope(match, payload, core) {
|
|
276
396
|
const ref = {
|
|
277
397
|
application_id: payload.application_id,
|
|
278
398
|
id: payload.id,
|
|
279
399
|
token: payload.token
|
|
280
400
|
};
|
|
281
|
-
const routeId = match
|
|
401
|
+
const routeId = unhandledRouteId(match);
|
|
282
402
|
return {
|
|
283
403
|
core,
|
|
284
404
|
payload,
|
|
285
405
|
routeId,
|
|
286
|
-
sender: match.kind === "autocomplete" ? null : new ReplySender(ref, core.rest, routeId)
|
|
406
|
+
sender: match.kind === "autocomplete" ? null : new ReplySender(ref, core.rest, routeId, core.bus)
|
|
287
407
|
};
|
|
288
408
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
409
|
+
function dispatchReporter(match, payload, core) {
|
|
410
|
+
const startedAt = performance.now();
|
|
411
|
+
const queuedMs = queuedMsFor(payload.id);
|
|
412
|
+
return (outcome) => {
|
|
413
|
+
core.bus[PublishDefault]("interactionDispatched", dispatchedPayload({
|
|
414
|
+
routeId: unhandledRouteId(match),
|
|
415
|
+
interactionId: payload.id,
|
|
416
|
+
kind: match.kind,
|
|
417
|
+
outcome,
|
|
418
|
+
fallback: match.routeId === null,
|
|
419
|
+
startedAt,
|
|
420
|
+
queuedMs
|
|
421
|
+
}));
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
async function answer(caught, scope, report, outcome = outcomeFor(caught)) {
|
|
297
425
|
try {
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
await handleFault(caught, freshScope(match, payload, core));
|
|
302
|
-
return null;
|
|
426
|
+
await handleFault(caught, scope);
|
|
427
|
+
} finally {
|
|
428
|
+
report(outcome);
|
|
303
429
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
430
|
+
}
|
|
431
|
+
async function loadHandlerCtor(match, payload, core, report) {
|
|
432
|
+
const routeId = unhandledRouteId(match);
|
|
433
|
+
let exported;
|
|
434
|
+
try {
|
|
435
|
+
exported = await match.load();
|
|
436
|
+
} catch (caught) {
|
|
437
|
+
logger().error(`Route ${paint.sky.bold(routeId)} failed to load its handler.`, caught);
|
|
438
|
+
await answer(caught, freshScope(match, payload, core), report, "failed");
|
|
309
439
|
return null;
|
|
310
440
|
}
|
|
311
|
-
|
|
441
|
+
if (isHandlerCtor(exported)) return exported;
|
|
442
|
+
logger().error(`Route ${paint.sky.bold(routeId)} loaded an export that is not a handler class.`);
|
|
443
|
+
await answer(/* @__PURE__ */ new Error(`route ${routeId} loaded an export that is not a handler class`), freshScope(match, payload, core), report, "failed");
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Runs the pre-ack phase for a matched interaction. Loads the route's handler class, constructs it, and
|
|
448
|
+
* runs its gates. The sender posts any refusal. Returns the post-ack execute continuation, or null when a
|
|
449
|
+
* gate refuses or nothing can run.
|
|
450
|
+
*/
|
|
451
|
+
async function dispatchInteraction(args) {
|
|
452
|
+
const { match, payload, core } = args;
|
|
453
|
+
const report = dispatchReporter(match, payload, core);
|
|
454
|
+
const ctor = await loadHandlerCtor(match, payload, core, report);
|
|
455
|
+
if (!ctor) return null;
|
|
456
|
+
const dispatch = new DispatchContext(unhandledRouteId(match));
|
|
312
457
|
let handler;
|
|
313
458
|
try {
|
|
314
459
|
handler = new ctor(payload, core, dispatch);
|
|
315
460
|
} catch (caught) {
|
|
316
|
-
await
|
|
461
|
+
await answer(caught, freshScope(match, payload, core), report);
|
|
317
462
|
return null;
|
|
318
463
|
}
|
|
319
464
|
const scope = {
|
|
320
465
|
core,
|
|
321
466
|
payload,
|
|
322
|
-
routeId: match
|
|
467
|
+
routeId: unhandledRouteId(match),
|
|
323
468
|
sender: handler instanceof RepliableHandler$1 ? handler.getSender() : null
|
|
324
469
|
};
|
|
325
|
-
|
|
470
|
+
const refusal = await gateRefusal(ctor, match, payload, core);
|
|
471
|
+
if (refusal) {
|
|
472
|
+
await answer(refusal.caught, scope, report);
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
326
475
|
return async () => {
|
|
327
476
|
try {
|
|
328
477
|
await handler.execute();
|
|
478
|
+
report("handled");
|
|
329
479
|
} catch (caught) {
|
|
330
|
-
await
|
|
480
|
+
await answer(caught, scope, report);
|
|
331
481
|
}
|
|
332
482
|
};
|
|
333
483
|
}
|
|
334
|
-
async function
|
|
335
|
-
if (match.kind === "autocomplete" || payload.type === InteractionType.ApplicationCommandAutocomplete) return
|
|
484
|
+
async function gateRefusal(ctor, match, payload, core) {
|
|
485
|
+
if (match.kind === "autocomplete" || payload.type === InteractionType.ApplicationCommandAutocomplete) return null;
|
|
336
486
|
const monitor = slowGateMonitor();
|
|
337
487
|
try {
|
|
338
488
|
await runHandlerGates(ctor, interactionGateContext(payload, core, match.routeId), match.routeId ?? void 0, monitor?.observe);
|
|
339
|
-
return
|
|
489
|
+
return null;
|
|
340
490
|
} catch (caught) {
|
|
341
|
-
|
|
342
|
-
return false;
|
|
491
|
+
return { caught };
|
|
343
492
|
} finally {
|
|
344
493
|
monitor?.report(match.routeId);
|
|
345
494
|
}
|
|
346
495
|
}
|
|
347
496
|
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/dispatch/registerSubscribers.ts
|
|
499
|
+
/**
|
|
500
|
+
* Registers a manifest's subscribers on the bus without importing them. Each row's module loads on
|
|
501
|
+
* the first publish of one of its keys, which keeps an isolate that never publishes free of the cost.
|
|
502
|
+
*
|
|
503
|
+
* @internal
|
|
504
|
+
*/
|
|
505
|
+
function registerSubscribers(bus, manifest) {
|
|
506
|
+
for (const row of manifest.subscriberRoutes) bus.register({
|
|
507
|
+
keys: row.keys,
|
|
508
|
+
frequency: row.frequency,
|
|
509
|
+
resolve: async () => {
|
|
510
|
+
const keys = row.keys.join(", ");
|
|
511
|
+
let module;
|
|
512
|
+
try {
|
|
513
|
+
module = await row.load();
|
|
514
|
+
} catch (caught) {
|
|
515
|
+
throw new SeedcordError(SeedcordErrorCode.RouteModuleLoadFailed, [keys, row.from], { cause: caught });
|
|
516
|
+
}
|
|
517
|
+
if (!Object.hasOwn(module, row.exportName)) throw new SeedcordError(SeedcordErrorCode.InteractionRouteExportMissing, [
|
|
518
|
+
keys,
|
|
519
|
+
row.exportName,
|
|
520
|
+
row.from
|
|
521
|
+
]);
|
|
522
|
+
const exported = module[row.exportName];
|
|
523
|
+
if (typeof exported !== "function" || !(exported.prototype instanceof Subscriber)) throw new SeedcordError(SeedcordErrorCode.SubscriberRouteNotASubscriber, [
|
|
524
|
+
keys,
|
|
525
|
+
row.exportName,
|
|
526
|
+
row.from
|
|
527
|
+
]);
|
|
528
|
+
return exported;
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
348
533
|
//#endregion
|
|
349
534
|
//#region src/inputs/HttpSlashOptions.ts
|
|
350
535
|
var HttpSlashOptions = class {
|
|
@@ -421,20 +606,6 @@ function numeric(value) {
|
|
|
421
606
|
return Number.isNaN(parsed) ? null : parsed;
|
|
422
607
|
}
|
|
423
608
|
|
|
424
|
-
//#endregion
|
|
425
|
-
//#region src/dispatch/slashRouteOf.ts
|
|
426
|
-
/**
|
|
427
|
-
* Computes the dispatch route for a chat-input or autocomplete payload, the raw-payload port of the
|
|
428
|
-
* gateway's `slashRouteOf`. Both assemble keys through `buildSlashRoute`, so a dispatched interaction
|
|
429
|
-
* and a generated registry key always match.
|
|
430
|
-
*/
|
|
431
|
-
function slashRouteOf(data) {
|
|
432
|
-
const first = data.options?.[0];
|
|
433
|
-
if (first?.type === ApplicationCommandOptionType.SubcommandGroup) return buildSlashRoute(data.name, first.options[0]?.name, first.name);
|
|
434
|
-
if (first?.type === ApplicationCommandOptionType.Subcommand) return buildSlashRoute(data.name, first.name);
|
|
435
|
-
return data.name;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
609
|
//#endregion
|
|
439
610
|
//#region src/handlers/interaction/AutocompleteHandler.ts
|
|
440
611
|
/**
|
|
@@ -448,7 +619,7 @@ function slashRouteOf(data) {
|
|
|
448
619
|
*/
|
|
449
620
|
var AutocompleteHandler = class extends BaseHandler {
|
|
450
621
|
constructor(event, core, dispatch) {
|
|
451
|
-
super(event, core, dispatch);
|
|
622
|
+
super(event, core, dispatch, "interactions");
|
|
452
623
|
}
|
|
453
624
|
/**
|
|
454
625
|
* Typed Discord REST API (`@discordjs/core/http-only`) over `core.rest`, one instance per core.
|
|
@@ -491,10 +662,14 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
491
662
|
}
|
|
492
663
|
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
493
664
|
async respond(choices) {
|
|
494
|
-
|
|
665
|
+
const routeId = this.dispatch?.routeId ?? "autocomplete";
|
|
666
|
+
await reportedWrite({
|
|
667
|
+
bus: this.core.bus,
|
|
668
|
+
interactionId: this.event.id
|
|
669
|
+
}, routeId, "respond", () => this.core.rest.post(Routes.interactionCallback(this.event.id, this.event.token), { body: {
|
|
495
670
|
type: InteractionResponseType.ApplicationCommandAutocompleteResult,
|
|
496
671
|
data: { choices }
|
|
497
|
-
} });
|
|
672
|
+
} }));
|
|
498
673
|
}
|
|
499
674
|
/** The firing command route, for a field whose completion differs per registered command. */
|
|
500
675
|
get route() {
|
|
@@ -592,14 +767,30 @@ function componentMapKey(type) {
|
|
|
592
767
|
default: return null;
|
|
593
768
|
}
|
|
594
769
|
}
|
|
595
|
-
function
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
770
|
+
function namedExport(routeId, row) {
|
|
771
|
+
return async () => {
|
|
772
|
+
let moduleExports;
|
|
773
|
+
try {
|
|
774
|
+
moduleExports = await row.load();
|
|
775
|
+
} catch (caught) {
|
|
776
|
+
throw new SeedcordError(SeedcordErrorCode.RouteModuleLoadFailed, [routeId, row.from], { cause: caught });
|
|
777
|
+
}
|
|
778
|
+
if (!Object.hasOwn(moduleExports, row.exportName)) throw new SeedcordError(SeedcordErrorCode.InteractionRouteExportMissing, [
|
|
779
|
+
routeId,
|
|
780
|
+
row.exportName,
|
|
781
|
+
row.from
|
|
782
|
+
]);
|
|
783
|
+
return moduleExports[row.exportName];
|
|
784
|
+
};
|
|
601
785
|
}
|
|
602
|
-
|
|
786
|
+
function describeRow(row) {
|
|
787
|
+
return `${row.exportName} (${row.from})`;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Builds the per-kind lookup maps.
|
|
791
|
+
*
|
|
792
|
+
* @throws A **SeedcordError** when two rows resolve to the same route.
|
|
793
|
+
*/
|
|
603
794
|
function buildRouteMaps(manifest) {
|
|
604
795
|
const maps = {
|
|
605
796
|
slash: /* @__PURE__ */ new Map(),
|
|
@@ -616,19 +807,36 @@ function buildRouteMaps(manifest) {
|
|
|
616
807
|
modal: /* @__PURE__ */ new Map()
|
|
617
808
|
}
|
|
618
809
|
};
|
|
619
|
-
|
|
810
|
+
const owners = /* @__PURE__ */ new Map();
|
|
811
|
+
function set(map, kind, key, row) {
|
|
812
|
+
const routeId = `${kind}:${key}`;
|
|
813
|
+
const owner = owners.get(routeId);
|
|
814
|
+
if (owner) throw new SeedcordError(SeedcordErrorCode.InteractionDuplicateRoute, [
|
|
815
|
+
routeId,
|
|
816
|
+
describeRow(owner),
|
|
817
|
+
describeRow(row)
|
|
818
|
+
]);
|
|
819
|
+
owners.set(routeId, row);
|
|
820
|
+
map.set(key, {
|
|
821
|
+
kind,
|
|
822
|
+
routeId,
|
|
823
|
+
load: namedExport(routeId, row)
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
for (const row of manifest.commandRoutes) {
|
|
620
827
|
const kind = commandKind(row.type);
|
|
621
|
-
if (kind) set(maps[kind], kind, row.name, row
|
|
828
|
+
if (kind) set(maps[kind], kind, row.name, row);
|
|
622
829
|
}
|
|
623
|
-
for (const row of manifest.
|
|
624
|
-
for (const row of manifest.
|
|
830
|
+
for (const row of manifest.autocompleteRoutes) set(maps.autocomplete, "autocomplete", row.name, row);
|
|
831
|
+
for (const row of manifest.componentRoutes) set(maps.components[row.kind], COMPONENT_KIND[row.kind], row.prefix, row);
|
|
625
832
|
return maps;
|
|
626
833
|
}
|
|
627
|
-
function unhandled(kind) {
|
|
834
|
+
function unhandled(kind, attemptedKey) {
|
|
628
835
|
return {
|
|
629
836
|
kind,
|
|
630
837
|
routeId: null,
|
|
631
|
-
|
|
838
|
+
attemptedKey,
|
|
839
|
+
load: () => Promise.resolve(kind === "autocomplete" ? UnhandledAutocomplete : UnhandledRepliable)
|
|
632
840
|
};
|
|
633
841
|
}
|
|
634
842
|
/**
|
|
@@ -641,17 +849,27 @@ function unhandled(kind) {
|
|
|
641
849
|
function resolve(maps, interaction) {
|
|
642
850
|
switch (interaction.type) {
|
|
643
851
|
case InteractionType.ApplicationCommand: {
|
|
644
|
-
if (interaction.data.type === ApplicationCommandType.ChatInput)
|
|
852
|
+
if (interaction.data.type === ApplicationCommandType.ChatInput) {
|
|
853
|
+
const route = slashRouteOf(interaction.data);
|
|
854
|
+
return maps.slash.get(route) ?? unhandled("slash", route);
|
|
855
|
+
}
|
|
645
856
|
const kind = commandKind(interaction.data.type);
|
|
646
|
-
return kind ? maps[kind].get(interaction.data.name) ?? unhandled(kind) : null;
|
|
857
|
+
return kind ? maps[kind].get(interaction.data.name) ?? unhandled(kind, interaction.data.name) : null;
|
|
858
|
+
}
|
|
859
|
+
case InteractionType.ApplicationCommandAutocomplete: {
|
|
860
|
+
const route = slashRouteOf(interaction.data);
|
|
861
|
+
return maps.autocomplete.get(route) ?? unhandled("autocomplete", route);
|
|
647
862
|
}
|
|
648
|
-
case InteractionType.ApplicationCommandAutocomplete: return maps.autocomplete.get(slashRouteOf(interaction.data)) ?? unhandled("autocomplete");
|
|
649
863
|
case InteractionType.MessageComponent: {
|
|
650
864
|
const key = componentMapKey(interaction.data.component_type);
|
|
651
865
|
if (!key) return null;
|
|
652
|
-
|
|
866
|
+
const prefix = prefixOf(interaction.data.custom_id);
|
|
867
|
+
return maps.components[key].get(prefix) ?? unhandled(COMPONENT_KIND[key], prefix);
|
|
868
|
+
}
|
|
869
|
+
case InteractionType.ModalSubmit: {
|
|
870
|
+
const prefix = prefixOf(interaction.data.custom_id);
|
|
871
|
+
return maps.components.modal.get(prefix) ?? unhandled("modal", prefix);
|
|
653
872
|
}
|
|
654
|
-
case InteractionType.ModalSubmit: return maps.components.modal.get(prefixOf(interaction.data.custom_id)) ?? unhandled("modal");
|
|
655
873
|
default: return null;
|
|
656
874
|
}
|
|
657
875
|
}
|
|
@@ -732,7 +950,7 @@ function buildEngine(core, maps) {
|
|
|
732
950
|
const verifier = new Ed25519Verifier(Envapter.getRequired("DISCORD_PUBLIC_KEY", Converters.String));
|
|
733
951
|
const replays = new ReplayGuard();
|
|
734
952
|
const inFlight = /* @__PURE__ */ new Set();
|
|
735
|
-
const logger = new Logger("Engine");
|
|
953
|
+
const logger = new Logger("Engine", { channel: "bot" });
|
|
736
954
|
async function dispatchMatched(match, payload, ctx) {
|
|
737
955
|
const start = await dispatchInteraction({
|
|
738
956
|
match,
|
|
@@ -740,13 +958,18 @@ function buildEngine(core, maps) {
|
|
|
740
958
|
core
|
|
741
959
|
});
|
|
742
960
|
if (!start) return;
|
|
743
|
-
const work = start();
|
|
961
|
+
const work = start().catch(rootFault);
|
|
744
962
|
if (ctx) ctx.waitUntil(work);
|
|
745
963
|
else {
|
|
746
964
|
inFlight.add(work);
|
|
747
965
|
work.finally(() => inFlight.delete(work));
|
|
748
966
|
}
|
|
749
967
|
}
|
|
968
|
+
function rootFault(caught) {
|
|
969
|
+
const error = asError(caught);
|
|
970
|
+
logger.error("dispatch failed past the boundary", error);
|
|
971
|
+
core.bus[PublishDefault]("unhandledInteractionError", { error });
|
|
972
|
+
}
|
|
750
973
|
const handle = async (request, ctx) => {
|
|
751
974
|
if (request.method !== "POST") return new Response(null, {
|
|
752
975
|
status: METHOD_NOT_ALLOWED,
|
|
@@ -767,10 +990,11 @@ function buildEngine(core, maps) {
|
|
|
767
990
|
if (!hasInteractionType(payload)) return new Response(null, { status: BAD_REQUEST });
|
|
768
991
|
if (payload.type === PING) return Response.json({ type: InteractionResponseType.Pong });
|
|
769
992
|
try {
|
|
993
|
+
core.bus[PublishDefault]("anyInteraction", { interaction: payload });
|
|
770
994
|
const match = resolve(maps, payload);
|
|
771
995
|
if (match) await dispatchMatched(match, payload, ctx);
|
|
772
996
|
} catch (caught) {
|
|
773
|
-
|
|
997
|
+
rootFault(caught);
|
|
774
998
|
}
|
|
775
999
|
return new Response(null, { status: ACCEPTED });
|
|
776
1000
|
};
|
|
@@ -796,9 +1020,120 @@ function buildEngine(core, maps) {
|
|
|
796
1020
|
* `SeedcordError` when either is missing or malformed.
|
|
797
1021
|
*/
|
|
798
1022
|
function createSeedcord(config, manifest) {
|
|
799
|
-
|
|
1023
|
+
const core = createCore(config, validateDiscordToken(Envapter.get("DISCORD_BOT_TOKEN")));
|
|
1024
|
+
core.bus.registerDefaults();
|
|
1025
|
+
registerSubscribers(core.bus, manifest);
|
|
1026
|
+
return buildEngine(core, buildRouteMaps(manifest)).handle;
|
|
800
1027
|
}
|
|
801
1028
|
|
|
1029
|
+
//#endregion
|
|
1030
|
+
//#region src/applicationId.ts
|
|
1031
|
+
/**
|
|
1032
|
+
* Reads the bot's own application id over REST. A gateway host takes it off the logged-in client, and
|
|
1033
|
+
* an http host has no session to read it from.
|
|
1034
|
+
*
|
|
1035
|
+
* @internal
|
|
1036
|
+
*/
|
|
1037
|
+
async function fetchApplicationId(rest) {
|
|
1038
|
+
try {
|
|
1039
|
+
return (await rest.get(Routes.currentApplication())).id;
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
throw new SeedcordError(SeedcordErrorCode.CoreApplicationUnavailable, { cause: error });
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
//#endregion
|
|
1046
|
+
//#region src/emojis/EmojiInjector.ts
|
|
1047
|
+
var Emoji = class {
|
|
1048
|
+
name;
|
|
1049
|
+
id;
|
|
1050
|
+
animated;
|
|
1051
|
+
constructor(name, id, animated = false) {
|
|
1052
|
+
this.name = name;
|
|
1053
|
+
this.id = id;
|
|
1054
|
+
this.animated = animated;
|
|
1055
|
+
}
|
|
1056
|
+
toString() {
|
|
1057
|
+
return `<${this.animated ? "a" : ""}:${this.name}:${this.id}>`;
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
const emojiStorage = accessorStore();
|
|
1061
|
+
function reasonOf(error) {
|
|
1062
|
+
return Error.isError(error) ? error.message : String(error);
|
|
1063
|
+
}
|
|
1064
|
+
function byName(list) {
|
|
1065
|
+
const map = /* @__PURE__ */ new Map();
|
|
1066
|
+
for (const entry of list) if (entry.name) map.set(entry.name, entry);
|
|
1067
|
+
return map;
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* The bot's resolved emojis, keyed by {@link EmojiMap}. Filled by {@link EmojiInjector} during startup.
|
|
1071
|
+
* A read before that throws.
|
|
1072
|
+
*/
|
|
1073
|
+
const Emojis = guardedAccessor("Emojis", emojiStorage);
|
|
1074
|
+
/**
|
|
1075
|
+
* Resolves the configured emojis over REST at startup.
|
|
1076
|
+
*
|
|
1077
|
+
* @internal
|
|
1078
|
+
*/
|
|
1079
|
+
var EmojiInjector = class {
|
|
1080
|
+
core;
|
|
1081
|
+
applicationId;
|
|
1082
|
+
logger = new Logger("Emojis", { channel: "bot" });
|
|
1083
|
+
constructor(core, applicationId = () => fetchApplicationId(core.rest)) {
|
|
1084
|
+
this.core = core;
|
|
1085
|
+
this.applicationId = applicationId;
|
|
1086
|
+
}
|
|
1087
|
+
async init() {
|
|
1088
|
+
clearStore(emojiStorage);
|
|
1089
|
+
const configured = this.core.config.bot.emojis;
|
|
1090
|
+
if (!configured || Object.keys(configured).length === 0) {
|
|
1091
|
+
this.logger.debug("No emojis configured, skipping emoji injection.");
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const failures = [];
|
|
1095
|
+
const guilds = /* @__PURE__ */ new Map();
|
|
1096
|
+
let application;
|
|
1097
|
+
for (const [key, value] of Object.entries(configured)) if (isEmojiTuple(value)) await this.resolveTuple(key, value, guilds, failures);
|
|
1098
|
+
else if (typeof value === "string") {
|
|
1099
|
+
application ??= await this.applicationEmojis(failures);
|
|
1100
|
+
this.resolve(key, value, application, failures, "the application");
|
|
1101
|
+
} else failures.push(` - "${key}" has an invalid value (expected a name or [name, guildId])`);
|
|
1102
|
+
if (failures.length > 0) throw new SeedcordError(SeedcordErrorCode.ConfigEmojiUnresolved, [failures.length, failures.join("\n")]);
|
|
1103
|
+
this.logger.utils.summary("Loaded emojis", { emojis: Object.keys(emojiStorage).length });
|
|
1104
|
+
}
|
|
1105
|
+
async applicationEmojis(failures) {
|
|
1106
|
+
try {
|
|
1107
|
+
const appId = await this.applicationId();
|
|
1108
|
+
return byName((await this.core.rest.get(Routes.applicationEmojis(appId))).items);
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
failures.push(` - the application emojis could not be read (${reasonOf(error)})`);
|
|
1111
|
+
return /* @__PURE__ */ new Map();
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
async resolveTuple(key, [name, guildId], guilds, failures) {
|
|
1115
|
+
let guild = guilds.get(guildId);
|
|
1116
|
+
if (!guild) {
|
|
1117
|
+
try {
|
|
1118
|
+
guild = byName(await this.core.rest.get(Routes.guildEmojis(guildId)));
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
failures.push(` - "${name}" for "${key}" targets guild ${guildId}, which could not be read (${reasonOf(error)})`);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
guilds.set(guildId, guild);
|
|
1124
|
+
}
|
|
1125
|
+
this.resolve(key, name, guild, failures, `guild ${guildId}`);
|
|
1126
|
+
}
|
|
1127
|
+
resolve(key, name, source, failures, where) {
|
|
1128
|
+
const found = source.get(name);
|
|
1129
|
+
if (!found?.id) {
|
|
1130
|
+
failures.push(` - "${name}" for "${key}" was not found in ${where}`);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
emojiStorage[key] = new Emoji(name, found.id, found.animated ?? false);
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
|
|
802
1137
|
//#endregion
|
|
803
1138
|
//#region src/handlers/interaction/SlashHandler.ts
|
|
804
1139
|
/**
|
|
@@ -979,12 +1314,12 @@ var SelectMenuHandler = class extends ComponentHandler {};
|
|
|
979
1314
|
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ConfigId]`.
|
|
980
1315
|
*/
|
|
981
1316
|
var ModalHandler = class extends ComponentHandler {
|
|
982
|
-
/** Rewrite the message this modal was opened from
|
|
1317
|
+
/** Rewrite the message this modal was opened from. Throws when a command opened the modal. */
|
|
983
1318
|
update(response) {
|
|
984
1319
|
this.ensureSourceMessage("update");
|
|
985
1320
|
return super.update(response);
|
|
986
1321
|
}
|
|
987
|
-
/**
|
|
1322
|
+
/** Acknowledge the submit without changing the source message. Throws when a command opened the modal. */
|
|
988
1323
|
deferUpdate() {
|
|
989
1324
|
this.ensureSourceMessage("deferUpdate");
|
|
990
1325
|
return super.deferUpdate();
|
|
@@ -995,6 +1330,98 @@ var ModalHandler = class extends ComponentHandler {
|
|
|
995
1330
|
}
|
|
996
1331
|
};
|
|
997
1332
|
|
|
1333
|
+
//#endregion
|
|
1334
|
+
//#region src/subscribers/index.ts
|
|
1335
|
+
/**
|
|
1336
|
+
* Base class for an HTTP subscriber, binding `this.core` to the HTTP {@link Core}.
|
|
1337
|
+
*
|
|
1338
|
+
* @typeParam KeyOfSubscribers - The subscription key this handler receives
|
|
1339
|
+
*/
|
|
1340
|
+
var Subscriber$1 = class extends Subscriber {};
|
|
1341
|
+
/**
|
|
1342
|
+
* Base class for an HTTP subscriber that delivers its event to a Discord webhook, binding
|
|
1343
|
+
* `this.core` to the HTTP {@link Core}.
|
|
1344
|
+
*
|
|
1345
|
+
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
1346
|
+
*/
|
|
1347
|
+
var WebhookLog$1 = class extends WebhookLog {};
|
|
1348
|
+
|
|
1349
|
+
//#endregion
|
|
1350
|
+
//#region src/pagination/Paginator.ts
|
|
1351
|
+
function contextOf(interaction, core) {
|
|
1352
|
+
return {
|
|
1353
|
+
interaction,
|
|
1354
|
+
user: interaction.member?.user ?? interaction.user,
|
|
1355
|
+
guildId: interaction.guild_id ?? null,
|
|
1356
|
+
core
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* A restart-proof paginator. Each nav button's customId encodes its full target page, so clicks are
|
|
1361
|
+
* idempotent and survive a restart. A persistent `@ButtonRoute` on `Handler` dispatches the clicks.
|
|
1362
|
+
*
|
|
1363
|
+
* @typeParam Item - The item type, inferred from the source.
|
|
1364
|
+
* @typeParam Prefix - The route prefix, inferred from `config.prefix`.
|
|
1365
|
+
*
|
|
1366
|
+
* @example
|
|
1367
|
+
* ```ts
|
|
1368
|
+
* export const Bans = new Paginator({
|
|
1369
|
+
* prefix: 'bans',
|
|
1370
|
+
* source: new ArraySource((ctx) => loadBans(ctx.guildId), { perPage: 10 }),
|
|
1371
|
+
* renderItem: (ban) => ban.user.username
|
|
1372
|
+
* });
|
|
1373
|
+
*
|
|
1374
|
+
* \@ButtonRoute(Bans.cursor)
|
|
1375
|
+
* export class BansNav extends Bans.Handler {}
|
|
1376
|
+
* ```
|
|
1377
|
+
*/
|
|
1378
|
+
var Paginator = class extends PaginatorBase {
|
|
1379
|
+
/** The nav handler base. Extend it with an empty body and decorate it, `@ButtonRoute(p.cursor)`. */
|
|
1380
|
+
Handler;
|
|
1381
|
+
constructor(config) {
|
|
1382
|
+
super(config);
|
|
1383
|
+
const loadPage = (ctx, n) => this.page(ctx, n);
|
|
1384
|
+
this.Handler = class Nav extends ButtonHandler {
|
|
1385
|
+
async execute() {
|
|
1386
|
+
await this.deferUpdate();
|
|
1387
|
+
const response = await loadPage(contextOf(this.event, this.core), this.params.page);
|
|
1388
|
+
await this.update(response);
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Render page 0 and send it through the handler's sender, which picks reply, edit, or followUp from its
|
|
1394
|
+
* ack state.
|
|
1395
|
+
*
|
|
1396
|
+
* @param handler - The handler starting the paginator, normally `this`.
|
|
1397
|
+
*/
|
|
1398
|
+
async start(handler) {
|
|
1399
|
+
const interaction = handler.getEvent();
|
|
1400
|
+
const response = await this.page(contextOf(interaction, handler.core), 0);
|
|
1401
|
+
return handler.getSender().send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
//#endregion
|
|
1406
|
+
//#region src/pagination/sources.ts
|
|
1407
|
+
/**
|
|
1408
|
+
* A source for a bounded list you can load whole. It loads the full list on every click, then slices the
|
|
1409
|
+
* page, so the real total is known (a real last button, "Page X of Y"). Cache inside your loader if the
|
|
1410
|
+
* load is expensive.
|
|
1411
|
+
*
|
|
1412
|
+
* @typeParam Item - The item type, inferred from the loader's return.
|
|
1413
|
+
*/
|
|
1414
|
+
var ArraySource$1 = class extends ArraySource {};
|
|
1415
|
+
/**
|
|
1416
|
+
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
1417
|
+
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
1418
|
+
* source has no cheap total, so `totalPages` is undefined, the last button is omitted, and the indicator
|
|
1419
|
+
* reads "Page X".
|
|
1420
|
+
*
|
|
1421
|
+
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
1422
|
+
*/
|
|
1423
|
+
var CursorSource$1 = class extends CursorSource {};
|
|
1424
|
+
|
|
998
1425
|
//#endregion
|
|
999
1426
|
//#region src/gates/Gated.ts
|
|
1000
1427
|
/**
|
|
@@ -1039,8 +1466,8 @@ function Gated(...gates) {
|
|
|
1039
1466
|
//#endregion
|
|
1040
1467
|
//#region src/version.ts
|
|
1041
1468
|
/** Package version */
|
|
1042
|
-
const version = "0.1.0-next.
|
|
1469
|
+
const version = "0.1.0-next.3";
|
|
1043
1470
|
|
|
1044
1471
|
//#endregion
|
|
1045
|
-
export {
|
|
1046
|
-
//# sourceMappingURL=edge.index-
|
|
1472
|
+
export { RepliableHandler$1 as S, createSeedcord as _, Paginator as a, InteractionHandler as b, ModalHandler as c, ComponentHandler as d, ContextMenuHandler as f, fetchApplicationId as g, Emojis as h, CursorSource$1 as i, SelectMenuHandler as l, EmojiInjector as m, Gated as n, Subscriber$1 as o, SlashHandler as p, ArraySource$1 as r, WebhookLog$1 as s, version as t, ButtonHandler as u, buildEngine as v, AutocompleteHandler as x, buildRouteMaps as y };
|
|
1473
|
+
//# sourceMappingURL=edge.index-Cv4plNWY.mjs.map
|