@seedcord/http 0.1.0-next.1 → 0.1.0-next.2
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-BAgNb6TV.mjs → edge.index-BBRnxxkS.mjs} +330 -91
- package/dist/edge.index-BBRnxxkS.mjs.map +1 -0
- package/dist/{edge.index-CTOyjsO4.d.mts → edge.index-C30OZ55m.d.mts} +60 -36
- package/dist/{edge.index-D-60VfMn.cjs → edge.index-joDL39cK.cjs} +339 -88
- package/dist/edge.index-joDL39cK.cjs.map +1 -0
- package/dist/edge.index.cjs +3 -1
- package/dist/edge.index.d.mts +3 -3
- package/dist/edge.index.mjs +2 -2
- package/dist/index.cjs +56 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +34 -15
- package/dist/index.mjs +55 -32
- 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 +7 -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 { BaseHandler, Bus, DispatchContext, Fault, Notice, RepliableHandler, Silence, Subscriber, WebhookLog } from "@seedcord/core";
|
|
6
|
+
import { BaseReplySender, FaultThrottle, GatedMetadataKey, PublishDefault, asError, decodeComponentRoute, deferFlags, dispatchedPayload, 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
|
}
|
|
@@ -145,7 +148,7 @@ var RepliableHandler$1 = class extends RepliableHandler {
|
|
|
145
148
|
application_id: event.application_id,
|
|
146
149
|
id: event.id,
|
|
147
150
|
token: event.token
|
|
148
|
-
}, core.rest, routeId);
|
|
151
|
+
}, core.rest, routeId, core.bus);
|
|
149
152
|
}
|
|
150
153
|
/**
|
|
151
154
|
* Typed Discord REST API (`@discordjs/core/http-only`) over `core.rest`, one instance per core.
|
|
@@ -178,31 +181,132 @@ function interactionGateContext(payload, core, routeId) {
|
|
|
178
181
|
};
|
|
179
182
|
}
|
|
180
183
|
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/dispatch/slashRouteOf.ts
|
|
186
|
+
/**
|
|
187
|
+
* Computes the dispatch route for a chat-input or autocomplete payload, the raw-payload port of the
|
|
188
|
+
* gateway's `slashRouteOf`. Both assemble keys through `buildSlashRoute`, so a dispatched interaction
|
|
189
|
+
* and a generated registry key always match.
|
|
190
|
+
*/
|
|
191
|
+
function slashRouteOf(data) {
|
|
192
|
+
const first = data.options?.[0];
|
|
193
|
+
if (first?.type === ApplicationCommandOptionType.SubcommandGroup) return buildSlashRoute(data.name, first.options[0]?.name, first.name);
|
|
194
|
+
if (first?.type === ApplicationCommandOptionType.Subcommand) return buildSlashRoute(data.name, first.name);
|
|
195
|
+
return data.name;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/dispatch/reportFault.ts
|
|
200
|
+
let faultLogger;
|
|
201
|
+
function logger$1() {
|
|
202
|
+
faultLogger ??= new Logger("Faults", { channel: "errors" });
|
|
203
|
+
return faultLogger;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Publishes a fault on the bus, the raw-payload equivalent of the reporting half of gateway's
|
|
207
|
+
* `extractErrorResponse`. A reported {@link Notice} publishes `handledException`, everything else
|
|
208
|
+
* publishes `unknownException`.
|
|
209
|
+
*
|
|
210
|
+
* @internal
|
|
211
|
+
*/
|
|
212
|
+
function reportFault(error, uuid, payload, core) {
|
|
213
|
+
const throttle = FaultThrottle.for(core);
|
|
214
|
+
const key = `${routeKeyOf(payload)}:${nameOf(error)}`;
|
|
215
|
+
if (!throttle.shouldReport(key)) {
|
|
216
|
+
logger$1().debug(`throttled duplicate fault ${paint.mute(key)}`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const source = interactionSource(payload);
|
|
220
|
+
if (error instanceof Notice && source) core.bus[PublishDefault]("handledException", {
|
|
221
|
+
denial: error,
|
|
222
|
+
uuid,
|
|
223
|
+
source
|
|
224
|
+
});
|
|
225
|
+
else core.bus[PublishDefault]("unknownException", {
|
|
226
|
+
uuid,
|
|
227
|
+
error,
|
|
228
|
+
...actors(payload),
|
|
229
|
+
metadata: payload
|
|
230
|
+
});
|
|
231
|
+
throttle.markReported(key);
|
|
232
|
+
}
|
|
233
|
+
function routeKeyOf(payload) {
|
|
234
|
+
if (payload.type === InteractionType.ApplicationCommandAutocomplete) return `autocomplete:${slashRouteOf(payload.data)}`;
|
|
235
|
+
if (payload.type === InteractionType.ApplicationCommand) return payload.data.type === ApplicationCommandType.ChatInput ? slashRouteOf(payload.data) : payload.data.name;
|
|
236
|
+
return prefixOf(payload.data.custom_id) || payload.data.custom_id;
|
|
237
|
+
}
|
|
238
|
+
function nameOf(error) {
|
|
239
|
+
if (error instanceof Notice) return error.name;
|
|
240
|
+
if (error instanceof DiscordAPIError) return String(error.code);
|
|
241
|
+
return error.constructor.name;
|
|
242
|
+
}
|
|
243
|
+
function actors(payload) {
|
|
244
|
+
const user = payload.member?.user ?? payload.user;
|
|
245
|
+
return {
|
|
246
|
+
guild: void 0,
|
|
247
|
+
user: user ? {
|
|
248
|
+
id: user.id,
|
|
249
|
+
username: user.username
|
|
250
|
+
} : void 0
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function interactionSource(payload) {
|
|
254
|
+
const user = payload.member?.user ?? payload.user;
|
|
255
|
+
if (!user) return null;
|
|
256
|
+
const common = {
|
|
257
|
+
kind: "interaction",
|
|
258
|
+
userId: user.id,
|
|
259
|
+
guildId: payload.guild_id ?? null,
|
|
260
|
+
channelId: payload.channel?.id ?? null,
|
|
261
|
+
interactionId: payload.id,
|
|
262
|
+
raw: payload
|
|
263
|
+
};
|
|
264
|
+
if (payload.type === InteractionType.ApplicationCommand) {
|
|
265
|
+
const isSlash = payload.data.type === ApplicationCommandType.ChatInput;
|
|
266
|
+
return {
|
|
267
|
+
...common,
|
|
268
|
+
interactionKind: isSlash ? "slash" : "context-menu",
|
|
269
|
+
command: routeKeyOf(payload),
|
|
270
|
+
customId: null
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (payload.type === InteractionType.ModalSubmit) return {
|
|
274
|
+
...common,
|
|
275
|
+
interactionKind: "modal",
|
|
276
|
+
command: null,
|
|
277
|
+
customId: payload.data.custom_id
|
|
278
|
+
};
|
|
279
|
+
if (payload.type === InteractionType.MessageComponent) {
|
|
280
|
+
const isButton = payload.data.component_type === ComponentType.Button;
|
|
281
|
+
return {
|
|
282
|
+
...common,
|
|
283
|
+
interactionKind: isButton ? "button" : "select",
|
|
284
|
+
command: null,
|
|
285
|
+
customId: payload.data.custom_id
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
181
291
|
//#endregion
|
|
182
292
|
//#region src/dispatch/dispatchInteraction.ts
|
|
183
293
|
let dispatchLogger;
|
|
184
294
|
function logger() {
|
|
185
|
-
dispatchLogger ??= new Logger("Dispatcher");
|
|
295
|
+
dispatchLogger ??= new Logger("Dispatcher", { channel: "interactions" });
|
|
186
296
|
return dispatchLogger;
|
|
187
297
|
}
|
|
188
298
|
function createCore(config, token) {
|
|
189
|
-
|
|
299
|
+
const draft = {
|
|
190
300
|
config,
|
|
191
301
|
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"
|
|
302
|
+
rest: new REST().setToken(token)
|
|
196
303
|
};
|
|
304
|
+
draft.bus = new Bus(draft);
|
|
305
|
+
return draft;
|
|
197
306
|
}
|
|
198
307
|
function isHandlerCtor(value) {
|
|
199
308
|
return typeof value === "function" && value.prototype instanceof BaseHandler;
|
|
200
309
|
}
|
|
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
310
|
const HARMLESS_API_CODES = new Set([
|
|
207
311
|
RESTJSONErrorCodes.UnknownInteraction,
|
|
208
312
|
RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
|
|
@@ -220,10 +324,13 @@ async function sendGuarded(routeId, send) {
|
|
|
220
324
|
}
|
|
221
325
|
}
|
|
222
326
|
async function respondEmptyChoices(scope) {
|
|
223
|
-
await
|
|
327
|
+
await reportedWrite({
|
|
328
|
+
bus: scope.core.bus,
|
|
329
|
+
interactionId: scope.payload.id
|
|
330
|
+
}, scope.routeId, "respond", () => scope.core.rest.post(Routes.interactionCallback(scope.payload.id, scope.payload.token), { body: {
|
|
224
331
|
type: InteractionResponseType.ApplicationCommandAutocompleteResult,
|
|
225
332
|
data: { choices: [] }
|
|
226
|
-
} });
|
|
333
|
+
} }));
|
|
227
334
|
}
|
|
228
335
|
function renderContext(core, uuid) {
|
|
229
336
|
const developerUsername = core.config.notifications?.developerUsername;
|
|
@@ -233,7 +340,10 @@ function renderContext(core, uuid) {
|
|
|
233
340
|
};
|
|
234
341
|
}
|
|
235
342
|
async function handleNotice(notice, uuid, scope) {
|
|
236
|
-
if (notice.report)
|
|
343
|
+
if (notice.report) {
|
|
344
|
+
logger().error(`${notice.name}: ${paint.mute(uuid)}`, notice);
|
|
345
|
+
reportFault(notice, uuid, scope.payload, scope.core);
|
|
346
|
+
}
|
|
237
347
|
const { sender } = scope;
|
|
238
348
|
if (!sender) {
|
|
239
349
|
await sendGuarded(scope.routeId, () => respondEmptyChoices(scope));
|
|
@@ -246,6 +356,7 @@ async function handleRawFault(error, uuid, scope) {
|
|
|
246
356
|
const { core, sender } = scope;
|
|
247
357
|
if (core.config.errors?.errorStack ?? false) logger().error(paint.mute(uuid), error);
|
|
248
358
|
else logger().error(`${paint.mute(uuid)} | ${error.message}`);
|
|
359
|
+
reportFault(error, uuid, scope.payload, core);
|
|
249
360
|
if (!sender) {
|
|
250
361
|
await sendGuarded(scope.routeId, () => respondEmptyChoices(scope));
|
|
251
362
|
return;
|
|
@@ -264,7 +375,7 @@ async function handleFault(caught, scope) {
|
|
|
264
375
|
await handleNotice(caught, uuid, scope);
|
|
265
376
|
return;
|
|
266
377
|
}
|
|
267
|
-
const error =
|
|
378
|
+
const error = asError(caught);
|
|
268
379
|
const ignore = new Set(scope.core.config.errors?.ignoreApiCodes ?? []);
|
|
269
380
|
if (error instanceof DiscordAPIError && ignore.has(error.code)) {
|
|
270
381
|
logger().debug(`swallowed api code ${paint.amber(String(error.code))}`);
|
|
@@ -272,79 +383,149 @@ async function handleFault(caught, scope) {
|
|
|
272
383
|
}
|
|
273
384
|
await handleRawFault(error, uuid, scope);
|
|
274
385
|
}
|
|
386
|
+
function unhandledRouteId(match) {
|
|
387
|
+
if (match.routeId) return match.routeId;
|
|
388
|
+
const key = match.attemptedKey ?? "";
|
|
389
|
+
return `${match.kind}:${key.length > 0 ? key : "unrouted"}`;
|
|
390
|
+
}
|
|
275
391
|
function freshScope(match, payload, core) {
|
|
276
392
|
const ref = {
|
|
277
393
|
application_id: payload.application_id,
|
|
278
394
|
id: payload.id,
|
|
279
395
|
token: payload.token
|
|
280
396
|
};
|
|
281
|
-
const routeId = match
|
|
397
|
+
const routeId = unhandledRouteId(match);
|
|
282
398
|
return {
|
|
283
399
|
core,
|
|
284
400
|
payload,
|
|
285
401
|
routeId,
|
|
286
|
-
sender: match.kind === "autocomplete" ? null : new ReplySender(ref, core.rest, routeId)
|
|
402
|
+
sender: match.kind === "autocomplete" ? null : new ReplySender(ref, core.rest, routeId, core.bus)
|
|
287
403
|
};
|
|
288
404
|
}
|
|
405
|
+
function dispatchReporter(match, payload, core) {
|
|
406
|
+
const startedAt = performance.now();
|
|
407
|
+
const queuedMs = queuedMsFor(payload.id);
|
|
408
|
+
return (outcome) => {
|
|
409
|
+
core.bus[PublishDefault]("interactionDispatched", dispatchedPayload({
|
|
410
|
+
routeId: unhandledRouteId(match),
|
|
411
|
+
interactionId: payload.id,
|
|
412
|
+
kind: match.kind,
|
|
413
|
+
outcome,
|
|
414
|
+
fallback: match.routeId === null,
|
|
415
|
+
startedAt,
|
|
416
|
+
queuedMs
|
|
417
|
+
}));
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
async function answer(caught, scope, report, outcome = outcomeFor(caught)) {
|
|
421
|
+
try {
|
|
422
|
+
await handleFault(caught, scope);
|
|
423
|
+
} finally {
|
|
424
|
+
report(outcome);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function loadHandlerCtor(match, payload, core, report) {
|
|
428
|
+
const routeId = unhandledRouteId(match);
|
|
429
|
+
let exported;
|
|
430
|
+
try {
|
|
431
|
+
exported = await match.load();
|
|
432
|
+
} catch (caught) {
|
|
433
|
+
logger().error(`Route ${paint.sky.bold(routeId)} failed to load its handler.`, caught);
|
|
434
|
+
await answer(caught, freshScope(match, payload, core), report, "failed");
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
if (isHandlerCtor(exported)) return exported;
|
|
438
|
+
logger().error(`Route ${paint.sky.bold(routeId)} loaded an export that is not a handler class.`);
|
|
439
|
+
await answer(/* @__PURE__ */ new Error(`route ${routeId} loaded an export that is not a handler class`), freshScope(match, payload, core), report, "failed");
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
289
442
|
/**
|
|
290
|
-
* Runs the pre-ack phase for a matched interaction. Loads the route
|
|
443
|
+
* Runs the pre-ack phase for a matched interaction. Loads the route's handler class, constructs it, and
|
|
291
444
|
* runs its gates, and the sender posts any refusal. Returns the post-ack execute continuation, or null
|
|
292
445
|
* when a gate refuses or nothing can run.
|
|
293
446
|
*/
|
|
294
447
|
async function dispatchInteraction(args) {
|
|
295
448
|
const { match, payload, core } = args;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
logger().error(`Route ${paint.sky.bold(match.routeId ?? "unhandled")} failed to load its module.`, caught);
|
|
301
|
-
await handleFault(caught, freshScope(match, payload, core));
|
|
302
|
-
return null;
|
|
303
|
-
}
|
|
304
|
-
const ctor = handlerCtorOf(moduleExports);
|
|
305
|
-
if (!ctor) {
|
|
306
|
-
const routeId = match.routeId ?? "unhandled";
|
|
307
|
-
logger().error(`Route ${paint.sky.bold(routeId)} loaded a module with no handler class.`);
|
|
308
|
-
await handleFault(/* @__PURE__ */ new Error(`route ${routeId} loaded a module with no handler class`), freshScope(match, payload, core));
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
|
-
const dispatch = new DispatchContext(match.routeId);
|
|
449
|
+
const report = dispatchReporter(match, payload, core);
|
|
450
|
+
const ctor = await loadHandlerCtor(match, payload, core, report);
|
|
451
|
+
if (!ctor) return null;
|
|
452
|
+
const dispatch = new DispatchContext(unhandledRouteId(match));
|
|
312
453
|
let handler;
|
|
313
454
|
try {
|
|
314
455
|
handler = new ctor(payload, core, dispatch);
|
|
315
456
|
} catch (caught) {
|
|
316
|
-
await
|
|
457
|
+
await answer(caught, freshScope(match, payload, core), report);
|
|
317
458
|
return null;
|
|
318
459
|
}
|
|
319
460
|
const scope = {
|
|
320
461
|
core,
|
|
321
462
|
payload,
|
|
322
|
-
routeId: match
|
|
463
|
+
routeId: unhandledRouteId(match),
|
|
323
464
|
sender: handler instanceof RepliableHandler$1 ? handler.getSender() : null
|
|
324
465
|
};
|
|
325
|
-
|
|
466
|
+
const refusal = await gateRefusal(ctor, match, payload, core);
|
|
467
|
+
if (refusal) {
|
|
468
|
+
await answer(refusal.caught, scope, report);
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
326
471
|
return async () => {
|
|
327
472
|
try {
|
|
328
473
|
await handler.execute();
|
|
474
|
+
report("handled");
|
|
329
475
|
} catch (caught) {
|
|
330
|
-
await
|
|
476
|
+
await answer(caught, scope, report);
|
|
331
477
|
}
|
|
332
478
|
};
|
|
333
479
|
}
|
|
334
|
-
async function
|
|
335
|
-
if (match.kind === "autocomplete" || payload.type === InteractionType.ApplicationCommandAutocomplete) return
|
|
480
|
+
async function gateRefusal(ctor, match, payload, core) {
|
|
481
|
+
if (match.kind === "autocomplete" || payload.type === InteractionType.ApplicationCommandAutocomplete) return null;
|
|
336
482
|
const monitor = slowGateMonitor();
|
|
337
483
|
try {
|
|
338
484
|
await runHandlerGates(ctor, interactionGateContext(payload, core, match.routeId), match.routeId ?? void 0, monitor?.observe);
|
|
339
|
-
return
|
|
485
|
+
return null;
|
|
340
486
|
} catch (caught) {
|
|
341
|
-
|
|
342
|
-
return false;
|
|
487
|
+
return { caught };
|
|
343
488
|
} finally {
|
|
344
489
|
monitor?.report(match.routeId);
|
|
345
490
|
}
|
|
346
491
|
}
|
|
347
492
|
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region src/dispatch/registerSubscribers.ts
|
|
495
|
+
/**
|
|
496
|
+
* Registers a manifest's subscribers on the bus without importing them. Each row's module loads on
|
|
497
|
+
* the first publish of one of its keys, which keeps an isolate that never publishes free of the cost.
|
|
498
|
+
*
|
|
499
|
+
* @internal
|
|
500
|
+
*/
|
|
501
|
+
function registerSubscribers(bus, manifest) {
|
|
502
|
+
for (const row of manifest.subscriberRoutes) bus.register({
|
|
503
|
+
keys: row.keys,
|
|
504
|
+
frequency: row.frequency,
|
|
505
|
+
resolve: async () => {
|
|
506
|
+
const keys = row.keys.join(", ");
|
|
507
|
+
let module;
|
|
508
|
+
try {
|
|
509
|
+
module = await row.load();
|
|
510
|
+
} catch (caught) {
|
|
511
|
+
throw new SeedcordError(SeedcordErrorCode.RouteModuleLoadFailed, [keys, row.from], { cause: caught });
|
|
512
|
+
}
|
|
513
|
+
if (!Object.hasOwn(module, row.exportName)) throw new SeedcordError(SeedcordErrorCode.InteractionRouteExportMissing, [
|
|
514
|
+
keys,
|
|
515
|
+
row.exportName,
|
|
516
|
+
row.from
|
|
517
|
+
]);
|
|
518
|
+
const exported = module[row.exportName];
|
|
519
|
+
if (typeof exported !== "function" || !(exported.prototype instanceof Subscriber)) throw new SeedcordError(SeedcordErrorCode.SubscriberRouteNotASubscriber, [
|
|
520
|
+
keys,
|
|
521
|
+
row.exportName,
|
|
522
|
+
row.from
|
|
523
|
+
]);
|
|
524
|
+
return exported;
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
348
529
|
//#endregion
|
|
349
530
|
//#region src/inputs/HttpSlashOptions.ts
|
|
350
531
|
var HttpSlashOptions = class {
|
|
@@ -421,20 +602,6 @@ function numeric(value) {
|
|
|
421
602
|
return Number.isNaN(parsed) ? null : parsed;
|
|
422
603
|
}
|
|
423
604
|
|
|
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
605
|
//#endregion
|
|
439
606
|
//#region src/handlers/interaction/AutocompleteHandler.ts
|
|
440
607
|
/**
|
|
@@ -448,7 +615,7 @@ function slashRouteOf(data) {
|
|
|
448
615
|
*/
|
|
449
616
|
var AutocompleteHandler = class extends BaseHandler {
|
|
450
617
|
constructor(event, core, dispatch) {
|
|
451
|
-
super(event, core, dispatch);
|
|
618
|
+
super(event, core, dispatch, "interactions");
|
|
452
619
|
}
|
|
453
620
|
/**
|
|
454
621
|
* Typed Discord REST API (`@discordjs/core/http-only`) over `core.rest`, one instance per core.
|
|
@@ -491,10 +658,14 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
491
658
|
}
|
|
492
659
|
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
493
660
|
async respond(choices) {
|
|
494
|
-
|
|
661
|
+
const routeId = this.dispatch?.routeId ?? "autocomplete";
|
|
662
|
+
await reportedWrite({
|
|
663
|
+
bus: this.core.bus,
|
|
664
|
+
interactionId: this.event.id
|
|
665
|
+
}, routeId, "respond", () => this.core.rest.post(Routes.interactionCallback(this.event.id, this.event.token), { body: {
|
|
495
666
|
type: InteractionResponseType.ApplicationCommandAutocompleteResult,
|
|
496
667
|
data: { choices }
|
|
497
|
-
} });
|
|
668
|
+
} }));
|
|
498
669
|
}
|
|
499
670
|
/** The firing command route, for a field whose completion differs per registered command. */
|
|
500
671
|
get route() {
|
|
@@ -592,14 +763,30 @@ function componentMapKey(type) {
|
|
|
592
763
|
default: return null;
|
|
593
764
|
}
|
|
594
765
|
}
|
|
595
|
-
function
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
766
|
+
function namedExport(routeId, row) {
|
|
767
|
+
return async () => {
|
|
768
|
+
let moduleExports;
|
|
769
|
+
try {
|
|
770
|
+
moduleExports = await row.load();
|
|
771
|
+
} catch (caught) {
|
|
772
|
+
throw new SeedcordError(SeedcordErrorCode.RouteModuleLoadFailed, [routeId, row.from], { cause: caught });
|
|
773
|
+
}
|
|
774
|
+
if (!Object.hasOwn(moduleExports, row.exportName)) throw new SeedcordError(SeedcordErrorCode.InteractionRouteExportMissing, [
|
|
775
|
+
routeId,
|
|
776
|
+
row.exportName,
|
|
777
|
+
row.from
|
|
778
|
+
]);
|
|
779
|
+
return moduleExports[row.exportName];
|
|
780
|
+
};
|
|
601
781
|
}
|
|
602
|
-
|
|
782
|
+
function describeRow(row) {
|
|
783
|
+
return `${row.exportName} (${row.from})`;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Builds the per-kind lookup maps.
|
|
787
|
+
*
|
|
788
|
+
* @throws A **SeedcordError** when two rows resolve to the same route.
|
|
789
|
+
*/
|
|
603
790
|
function buildRouteMaps(manifest) {
|
|
604
791
|
const maps = {
|
|
605
792
|
slash: /* @__PURE__ */ new Map(),
|
|
@@ -616,19 +803,36 @@ function buildRouteMaps(manifest) {
|
|
|
616
803
|
modal: /* @__PURE__ */ new Map()
|
|
617
804
|
}
|
|
618
805
|
};
|
|
619
|
-
|
|
806
|
+
const owners = /* @__PURE__ */ new Map();
|
|
807
|
+
function set(map, kind, key, row) {
|
|
808
|
+
const routeId = `${kind}:${key}`;
|
|
809
|
+
const owner = owners.get(routeId);
|
|
810
|
+
if (owner) throw new SeedcordError(SeedcordErrorCode.InteractionDuplicateRoute, [
|
|
811
|
+
routeId,
|
|
812
|
+
describeRow(owner),
|
|
813
|
+
describeRow(row)
|
|
814
|
+
]);
|
|
815
|
+
owners.set(routeId, row);
|
|
816
|
+
map.set(key, {
|
|
817
|
+
kind,
|
|
818
|
+
routeId,
|
|
819
|
+
load: namedExport(routeId, row)
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
for (const row of manifest.commandRoutes) {
|
|
620
823
|
const kind = commandKind(row.type);
|
|
621
|
-
if (kind) set(maps[kind], kind, row.name, row
|
|
824
|
+
if (kind) set(maps[kind], kind, row.name, row);
|
|
622
825
|
}
|
|
623
|
-
for (const row of manifest.
|
|
624
|
-
for (const row of manifest.
|
|
826
|
+
for (const row of manifest.autocompleteRoutes) set(maps.autocomplete, "autocomplete", row.name, row);
|
|
827
|
+
for (const row of manifest.componentRoutes) set(maps.components[row.kind], COMPONENT_KIND[row.kind], row.prefix, row);
|
|
625
828
|
return maps;
|
|
626
829
|
}
|
|
627
|
-
function unhandled(kind) {
|
|
830
|
+
function unhandled(kind, attemptedKey) {
|
|
628
831
|
return {
|
|
629
832
|
kind,
|
|
630
833
|
routeId: null,
|
|
631
|
-
|
|
834
|
+
attemptedKey,
|
|
835
|
+
load: () => Promise.resolve(kind === "autocomplete" ? UnhandledAutocomplete : UnhandledRepliable)
|
|
632
836
|
};
|
|
633
837
|
}
|
|
634
838
|
/**
|
|
@@ -641,17 +845,27 @@ function unhandled(kind) {
|
|
|
641
845
|
function resolve(maps, interaction) {
|
|
642
846
|
switch (interaction.type) {
|
|
643
847
|
case InteractionType.ApplicationCommand: {
|
|
644
|
-
if (interaction.data.type === ApplicationCommandType.ChatInput)
|
|
848
|
+
if (interaction.data.type === ApplicationCommandType.ChatInput) {
|
|
849
|
+
const route = slashRouteOf(interaction.data);
|
|
850
|
+
return maps.slash.get(route) ?? unhandled("slash", route);
|
|
851
|
+
}
|
|
645
852
|
const kind = commandKind(interaction.data.type);
|
|
646
|
-
return kind ? maps[kind].get(interaction.data.name) ?? unhandled(kind) : null;
|
|
853
|
+
return kind ? maps[kind].get(interaction.data.name) ?? unhandled(kind, interaction.data.name) : null;
|
|
854
|
+
}
|
|
855
|
+
case InteractionType.ApplicationCommandAutocomplete: {
|
|
856
|
+
const route = slashRouteOf(interaction.data);
|
|
857
|
+
return maps.autocomplete.get(route) ?? unhandled("autocomplete", route);
|
|
647
858
|
}
|
|
648
|
-
case InteractionType.ApplicationCommandAutocomplete: return maps.autocomplete.get(slashRouteOf(interaction.data)) ?? unhandled("autocomplete");
|
|
649
859
|
case InteractionType.MessageComponent: {
|
|
650
860
|
const key = componentMapKey(interaction.data.component_type);
|
|
651
861
|
if (!key) return null;
|
|
652
|
-
|
|
862
|
+
const prefix = prefixOf(interaction.data.custom_id);
|
|
863
|
+
return maps.components[key].get(prefix) ?? unhandled(COMPONENT_KIND[key], prefix);
|
|
864
|
+
}
|
|
865
|
+
case InteractionType.ModalSubmit: {
|
|
866
|
+
const prefix = prefixOf(interaction.data.custom_id);
|
|
867
|
+
return maps.components.modal.get(prefix) ?? unhandled("modal", prefix);
|
|
653
868
|
}
|
|
654
|
-
case InteractionType.ModalSubmit: return maps.components.modal.get(prefixOf(interaction.data.custom_id)) ?? unhandled("modal");
|
|
655
869
|
default: return null;
|
|
656
870
|
}
|
|
657
871
|
}
|
|
@@ -732,7 +946,7 @@ function buildEngine(core, maps) {
|
|
|
732
946
|
const verifier = new Ed25519Verifier(Envapter.getRequired("DISCORD_PUBLIC_KEY", Converters.String));
|
|
733
947
|
const replays = new ReplayGuard();
|
|
734
948
|
const inFlight = /* @__PURE__ */ new Set();
|
|
735
|
-
const logger = new Logger("Engine");
|
|
949
|
+
const logger = new Logger("Engine", { channel: "bot" });
|
|
736
950
|
async function dispatchMatched(match, payload, ctx) {
|
|
737
951
|
const start = await dispatchInteraction({
|
|
738
952
|
match,
|
|
@@ -740,13 +954,18 @@ function buildEngine(core, maps) {
|
|
|
740
954
|
core
|
|
741
955
|
});
|
|
742
956
|
if (!start) return;
|
|
743
|
-
const work = start();
|
|
957
|
+
const work = start().catch(rootFault);
|
|
744
958
|
if (ctx) ctx.waitUntil(work);
|
|
745
959
|
else {
|
|
746
960
|
inFlight.add(work);
|
|
747
961
|
work.finally(() => inFlight.delete(work));
|
|
748
962
|
}
|
|
749
963
|
}
|
|
964
|
+
function rootFault(caught) {
|
|
965
|
+
const error = asError(caught);
|
|
966
|
+
logger.error("dispatch failed past the boundary", error);
|
|
967
|
+
core.bus[PublishDefault]("unhandledInteractionError", { error });
|
|
968
|
+
}
|
|
750
969
|
const handle = async (request, ctx) => {
|
|
751
970
|
if (request.method !== "POST") return new Response(null, {
|
|
752
971
|
status: METHOD_NOT_ALLOWED,
|
|
@@ -767,10 +986,11 @@ function buildEngine(core, maps) {
|
|
|
767
986
|
if (!hasInteractionType(payload)) return new Response(null, { status: BAD_REQUEST });
|
|
768
987
|
if (payload.type === PING) return Response.json({ type: InteractionResponseType.Pong });
|
|
769
988
|
try {
|
|
989
|
+
core.bus[PublishDefault]("anyInteraction", { interaction: payload });
|
|
770
990
|
const match = resolve(maps, payload);
|
|
771
991
|
if (match) await dispatchMatched(match, payload, ctx);
|
|
772
992
|
} catch (caught) {
|
|
773
|
-
|
|
993
|
+
rootFault(caught);
|
|
774
994
|
}
|
|
775
995
|
return new Response(null, { status: ACCEPTED });
|
|
776
996
|
};
|
|
@@ -796,7 +1016,10 @@ function buildEngine(core, maps) {
|
|
|
796
1016
|
* `SeedcordError` when either is missing or malformed.
|
|
797
1017
|
*/
|
|
798
1018
|
function createSeedcord(config, manifest) {
|
|
799
|
-
|
|
1019
|
+
const core = createCore(config, validateDiscordToken(Envapter.get("DISCORD_BOT_TOKEN")));
|
|
1020
|
+
core.bus.registerDefaults();
|
|
1021
|
+
registerSubscribers(core.bus, manifest);
|
|
1022
|
+
return buildEngine(core, buildRouteMaps(manifest)).handle;
|
|
800
1023
|
}
|
|
801
1024
|
|
|
802
1025
|
//#endregion
|
|
@@ -995,6 +1218,22 @@ var ModalHandler = class extends ComponentHandler {
|
|
|
995
1218
|
}
|
|
996
1219
|
};
|
|
997
1220
|
|
|
1221
|
+
//#endregion
|
|
1222
|
+
//#region src/subscribers/index.ts
|
|
1223
|
+
/**
|
|
1224
|
+
* Base class for an HTTP subscriber, binding `this.core` to the HTTP {@link Core}.
|
|
1225
|
+
*
|
|
1226
|
+
* @typeParam KeyOfSubscribers - The subscription key this handler receives
|
|
1227
|
+
*/
|
|
1228
|
+
var Subscriber$1 = class extends Subscriber {};
|
|
1229
|
+
/**
|
|
1230
|
+
* Base class for an HTTP subscriber that delivers its event to a Discord webhook, binding
|
|
1231
|
+
* `this.core` to the HTTP {@link Core}.
|
|
1232
|
+
*
|
|
1233
|
+
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
1234
|
+
*/
|
|
1235
|
+
var WebhookLog$1 = class extends WebhookLog {};
|
|
1236
|
+
|
|
998
1237
|
//#endregion
|
|
999
1238
|
//#region src/gates/Gated.ts
|
|
1000
1239
|
/**
|
|
@@ -1039,8 +1278,8 @@ function Gated(...gates) {
|
|
|
1039
1278
|
//#endregion
|
|
1040
1279
|
//#region src/version.ts
|
|
1041
1280
|
/** Package version */
|
|
1042
|
-
const version = "0.1.0-next.
|
|
1281
|
+
const version = "0.1.0-next.2";
|
|
1043
1282
|
|
|
1044
1283
|
//#endregion
|
|
1045
|
-
export {
|
|
1046
|
-
//# sourceMappingURL=edge.index-
|
|
1284
|
+
export { ModalHandler as a, ComponentHandler as c, createSeedcord as d, buildEngine as f, RepliableHandler$1 as g, AutocompleteHandler as h, WebhookLog$1 as i, ContextMenuHandler as l, InteractionHandler as m, Gated as n, SelectMenuHandler as o, buildRouteMaps as p, Subscriber$1 as r, ButtonHandler as s, version as t, SlashHandler as u };
|
|
1285
|
+
//# sourceMappingURL=edge.index-BBRnxxkS.mjs.map
|