@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
|
@@ -42,8 +42,8 @@ let _seedcord_logger = require("@seedcord/logger");
|
|
|
42
42
|
let _seedcord_rate_limiter = require("@seedcord/rate-limiter");
|
|
43
43
|
let discord_api_types_v10 = require("discord-api-types/v10");
|
|
44
44
|
let _discordjs_core_http_only = require("@discordjs/core/http-only");
|
|
45
|
-
let _seedcord_errors = require("@seedcord/errors");
|
|
46
45
|
let _seedcord_utils_internal = require("@seedcord/utils/internal");
|
|
46
|
+
let _seedcord_errors = require("@seedcord/errors");
|
|
47
47
|
|
|
48
48
|
//#region src/reply/ReplySender.ts
|
|
49
49
|
function wireAllowedMentions(mentions) {
|
|
@@ -77,8 +77,11 @@ function callbackData(reply, flags) {
|
|
|
77
77
|
var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
78
78
|
ref;
|
|
79
79
|
rest;
|
|
80
|
-
constructor(ref, rest, routeId) {
|
|
81
|
-
super(routeId
|
|
80
|
+
constructor(ref, rest, routeId, bus) {
|
|
81
|
+
super(routeId, "unacked", bus && {
|
|
82
|
+
bus,
|
|
83
|
+
interactionId: ref.id
|
|
84
|
+
});
|
|
82
85
|
this.ref = ref;
|
|
83
86
|
this.rest = rest;
|
|
84
87
|
}
|
|
@@ -179,7 +182,7 @@ var RepliableHandler = class extends _seedcord_core.RepliableHandler {
|
|
|
179
182
|
application_id: event.application_id,
|
|
180
183
|
id: event.id,
|
|
181
184
|
token: event.token
|
|
182
|
-
}, core.rest, routeId);
|
|
185
|
+
}, core.rest, routeId, core.bus);
|
|
183
186
|
}
|
|
184
187
|
/**
|
|
185
188
|
* Typed Discord REST API (`@discordjs/core/http-only`) over `core.rest`, one instance per core.
|
|
@@ -212,31 +215,132 @@ function interactionGateContext(payload, core, routeId) {
|
|
|
212
215
|
};
|
|
213
216
|
}
|
|
214
217
|
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/dispatch/slashRouteOf.ts
|
|
220
|
+
/**
|
|
221
|
+
* Computes the dispatch route for a chat-input or autocomplete payload, the raw-payload port of the
|
|
222
|
+
* gateway's `slashRouteOf`. Both assemble keys through `buildSlashRoute`, so a dispatched interaction
|
|
223
|
+
* and a generated registry key always match.
|
|
224
|
+
*/
|
|
225
|
+
function slashRouteOf(data) {
|
|
226
|
+
const first = data.options?.[0];
|
|
227
|
+
if (first?.type === discord_api_types_v10.ApplicationCommandOptionType.SubcommandGroup) return (0, _seedcord_utils_internal.buildSlashRoute)(data.name, first.options[0]?.name, first.name);
|
|
228
|
+
if (first?.type === discord_api_types_v10.ApplicationCommandOptionType.Subcommand) return (0, _seedcord_utils_internal.buildSlashRoute)(data.name, first.name);
|
|
229
|
+
return data.name;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/dispatch/reportFault.ts
|
|
234
|
+
let faultLogger;
|
|
235
|
+
function logger$1() {
|
|
236
|
+
faultLogger ??= new _seedcord_logger.Logger("Faults", { channel: "errors" });
|
|
237
|
+
return faultLogger;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Publishes a fault on the bus, the raw-payload equivalent of the reporting half of gateway's
|
|
241
|
+
* `extractErrorResponse`. A reported {@link Notice} publishes `handledException`, everything else
|
|
242
|
+
* publishes `unknownException`.
|
|
243
|
+
*
|
|
244
|
+
* @internal
|
|
245
|
+
*/
|
|
246
|
+
function reportFault(error, uuid, payload, core) {
|
|
247
|
+
const throttle = _seedcord_core_internal.FaultThrottle.for(core);
|
|
248
|
+
const key = `${routeKeyOf(payload)}:${nameOf(error)}`;
|
|
249
|
+
if (!throttle.shouldReport(key)) {
|
|
250
|
+
logger$1().debug(`throttled duplicate fault ${_seedcord_logger.paint.mute(key)}`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const source = interactionSource(payload);
|
|
254
|
+
if (error instanceof _seedcord_core.Notice && source) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
|
|
255
|
+
denial: error,
|
|
256
|
+
uuid,
|
|
257
|
+
source
|
|
258
|
+
});
|
|
259
|
+
else core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
|
|
260
|
+
uuid,
|
|
261
|
+
error,
|
|
262
|
+
...actors(payload),
|
|
263
|
+
metadata: payload
|
|
264
|
+
});
|
|
265
|
+
throttle.markReported(key);
|
|
266
|
+
}
|
|
267
|
+
function routeKeyOf(payload) {
|
|
268
|
+
if (payload.type === discord_api_types_v10.InteractionType.ApplicationCommandAutocomplete) return `autocomplete:${slashRouteOf(payload.data)}`;
|
|
269
|
+
if (payload.type === discord_api_types_v10.InteractionType.ApplicationCommand) return payload.data.type === discord_api_types_v10.ApplicationCommandType.ChatInput ? slashRouteOf(payload.data) : payload.data.name;
|
|
270
|
+
return (0, _seedcord_core_internal.prefixOf)(payload.data.custom_id) || payload.data.custom_id;
|
|
271
|
+
}
|
|
272
|
+
function nameOf(error) {
|
|
273
|
+
if (error instanceof _seedcord_core.Notice) return error.name;
|
|
274
|
+
if (error instanceof _discordjs_rest.DiscordAPIError) return String(error.code);
|
|
275
|
+
return error.constructor.name;
|
|
276
|
+
}
|
|
277
|
+
function actors(payload) {
|
|
278
|
+
const user = payload.member?.user ?? payload.user;
|
|
279
|
+
return {
|
|
280
|
+
guild: void 0,
|
|
281
|
+
user: user ? {
|
|
282
|
+
id: user.id,
|
|
283
|
+
username: user.username
|
|
284
|
+
} : void 0
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function interactionSource(payload) {
|
|
288
|
+
const user = payload.member?.user ?? payload.user;
|
|
289
|
+
if (!user) return null;
|
|
290
|
+
const common = {
|
|
291
|
+
kind: "interaction",
|
|
292
|
+
userId: user.id,
|
|
293
|
+
guildId: payload.guild_id ?? null,
|
|
294
|
+
channelId: payload.channel?.id ?? null,
|
|
295
|
+
interactionId: payload.id,
|
|
296
|
+
raw: payload
|
|
297
|
+
};
|
|
298
|
+
if (payload.type === discord_api_types_v10.InteractionType.ApplicationCommand) {
|
|
299
|
+
const isSlash = payload.data.type === discord_api_types_v10.ApplicationCommandType.ChatInput;
|
|
300
|
+
return {
|
|
301
|
+
...common,
|
|
302
|
+
interactionKind: isSlash ? "slash" : "context-menu",
|
|
303
|
+
command: routeKeyOf(payload),
|
|
304
|
+
customId: null
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (payload.type === discord_api_types_v10.InteractionType.ModalSubmit) return {
|
|
308
|
+
...common,
|
|
309
|
+
interactionKind: "modal",
|
|
310
|
+
command: null,
|
|
311
|
+
customId: payload.data.custom_id
|
|
312
|
+
};
|
|
313
|
+
if (payload.type === discord_api_types_v10.InteractionType.MessageComponent) {
|
|
314
|
+
const isButton = payload.data.component_type === discord_api_types_v10.ComponentType.Button;
|
|
315
|
+
return {
|
|
316
|
+
...common,
|
|
317
|
+
interactionKind: isButton ? "button" : "select",
|
|
318
|
+
command: null,
|
|
319
|
+
customId: payload.data.custom_id
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
215
325
|
//#endregion
|
|
216
326
|
//#region src/dispatch/dispatchInteraction.ts
|
|
217
327
|
let dispatchLogger;
|
|
218
328
|
function logger() {
|
|
219
|
-
dispatchLogger ??= new _seedcord_logger.Logger("Dispatcher");
|
|
329
|
+
dispatchLogger ??= new _seedcord_logger.Logger("Dispatcher", { channel: "interactions" });
|
|
220
330
|
return dispatchLogger;
|
|
221
331
|
}
|
|
222
332
|
function createCore(config, token) {
|
|
223
|
-
|
|
333
|
+
const draft = {
|
|
224
334
|
config,
|
|
225
335
|
rateLimiter: config.store ?? new _seedcord_rate_limiter.MemoryRateLimiter(),
|
|
226
|
-
rest: new _discordjs_rest.REST().setToken(token)
|
|
227
|
-
version: "0.1.0-next.1",
|
|
228
|
-
username: void 0,
|
|
229
|
-
augmentTarget: "@seedcord/http"
|
|
336
|
+
rest: new _discordjs_rest.REST().setToken(token)
|
|
230
337
|
};
|
|
338
|
+
draft.bus = new _seedcord_core.Bus(draft);
|
|
339
|
+
return draft;
|
|
231
340
|
}
|
|
232
341
|
function isHandlerCtor(value) {
|
|
233
342
|
return typeof value === "function" && value.prototype instanceof _seedcord_core.BaseHandler;
|
|
234
343
|
}
|
|
235
|
-
function handlerCtorOf(moduleExports) {
|
|
236
|
-
if (typeof moduleExports !== "object" || moduleExports === null) return null;
|
|
237
|
-
for (const value of Object.values(moduleExports)) if (isHandlerCtor(value)) return value;
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
344
|
const HARMLESS_API_CODES = new Set([
|
|
241
345
|
discord_api_types_v10.RESTJSONErrorCodes.UnknownInteraction,
|
|
242
346
|
discord_api_types_v10.RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
|
|
@@ -254,10 +358,13 @@ async function sendGuarded(routeId, send) {
|
|
|
254
358
|
}
|
|
255
359
|
}
|
|
256
360
|
async function respondEmptyChoices(scope) {
|
|
257
|
-
await
|
|
361
|
+
await (0, _seedcord_core_internal.reportedWrite)({
|
|
362
|
+
bus: scope.core.bus,
|
|
363
|
+
interactionId: scope.payload.id
|
|
364
|
+
}, scope.routeId, "respond", () => scope.core.rest.post(discord_api_types_v10.Routes.interactionCallback(scope.payload.id, scope.payload.token), { body: {
|
|
258
365
|
type: discord_api_types_v10.InteractionResponseType.ApplicationCommandAutocompleteResult,
|
|
259
366
|
data: { choices: [] }
|
|
260
|
-
} });
|
|
367
|
+
} }));
|
|
261
368
|
}
|
|
262
369
|
function renderContext(core, uuid) {
|
|
263
370
|
const developerUsername = core.config.notifications?.developerUsername;
|
|
@@ -267,7 +374,10 @@ function renderContext(core, uuid) {
|
|
|
267
374
|
};
|
|
268
375
|
}
|
|
269
376
|
async function handleNotice(notice, uuid, scope) {
|
|
270
|
-
if (notice.report)
|
|
377
|
+
if (notice.report) {
|
|
378
|
+
logger().error(`${notice.name}: ${_seedcord_logger.paint.mute(uuid)}`, notice);
|
|
379
|
+
reportFault(notice, uuid, scope.payload, scope.core);
|
|
380
|
+
}
|
|
271
381
|
const { sender } = scope;
|
|
272
382
|
if (!sender) {
|
|
273
383
|
await sendGuarded(scope.routeId, () => respondEmptyChoices(scope));
|
|
@@ -280,6 +390,7 @@ async function handleRawFault(error, uuid, scope) {
|
|
|
280
390
|
const { core, sender } = scope;
|
|
281
391
|
if (core.config.errors?.errorStack ?? false) logger().error(_seedcord_logger.paint.mute(uuid), error);
|
|
282
392
|
else logger().error(`${_seedcord_logger.paint.mute(uuid)} | ${error.message}`);
|
|
393
|
+
reportFault(error, uuid, scope.payload, core);
|
|
283
394
|
if (!sender) {
|
|
284
395
|
await sendGuarded(scope.routeId, () => respondEmptyChoices(scope));
|
|
285
396
|
return;
|
|
@@ -298,7 +409,7 @@ async function handleFault(caught, scope) {
|
|
|
298
409
|
await handleNotice(caught, uuid, scope);
|
|
299
410
|
return;
|
|
300
411
|
}
|
|
301
|
-
const error =
|
|
412
|
+
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
302
413
|
const ignore = new Set(scope.core.config.errors?.ignoreApiCodes ?? []);
|
|
303
414
|
if (error instanceof _discordjs_rest.DiscordAPIError && ignore.has(error.code)) {
|
|
304
415
|
logger().debug(`swallowed api code ${_seedcord_logger.paint.amber(String(error.code))}`);
|
|
@@ -306,79 +417,149 @@ async function handleFault(caught, scope) {
|
|
|
306
417
|
}
|
|
307
418
|
await handleRawFault(error, uuid, scope);
|
|
308
419
|
}
|
|
420
|
+
function unhandledRouteId(match) {
|
|
421
|
+
if (match.routeId) return match.routeId;
|
|
422
|
+
const key = match.attemptedKey ?? "";
|
|
423
|
+
return `${match.kind}:${key.length > 0 ? key : "unrouted"}`;
|
|
424
|
+
}
|
|
309
425
|
function freshScope(match, payload, core) {
|
|
310
426
|
const ref = {
|
|
311
427
|
application_id: payload.application_id,
|
|
312
428
|
id: payload.id,
|
|
313
429
|
token: payload.token
|
|
314
430
|
};
|
|
315
|
-
const routeId = match
|
|
431
|
+
const routeId = unhandledRouteId(match);
|
|
316
432
|
return {
|
|
317
433
|
core,
|
|
318
434
|
payload,
|
|
319
435
|
routeId,
|
|
320
|
-
sender: match.kind === "autocomplete" ? null : new ReplySender(ref, core.rest, routeId)
|
|
436
|
+
sender: match.kind === "autocomplete" ? null : new ReplySender(ref, core.rest, routeId, core.bus)
|
|
321
437
|
};
|
|
322
438
|
}
|
|
439
|
+
function dispatchReporter(match, payload, core) {
|
|
440
|
+
const startedAt = performance.now();
|
|
441
|
+
const queuedMs = (0, _seedcord_core_internal.queuedMsFor)(payload.id);
|
|
442
|
+
return (outcome) => {
|
|
443
|
+
core.bus[_seedcord_core_internal.PublishDefault]("interactionDispatched", (0, _seedcord_core_internal.dispatchedPayload)({
|
|
444
|
+
routeId: unhandledRouteId(match),
|
|
445
|
+
interactionId: payload.id,
|
|
446
|
+
kind: match.kind,
|
|
447
|
+
outcome,
|
|
448
|
+
fallback: match.routeId === null,
|
|
449
|
+
startedAt,
|
|
450
|
+
queuedMs
|
|
451
|
+
}));
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
async function answer(caught, scope, report, outcome = (0, _seedcord_core_internal.outcomeFor)(caught)) {
|
|
455
|
+
try {
|
|
456
|
+
await handleFault(caught, scope);
|
|
457
|
+
} finally {
|
|
458
|
+
report(outcome);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async function loadHandlerCtor(match, payload, core, report) {
|
|
462
|
+
const routeId = unhandledRouteId(match);
|
|
463
|
+
let exported;
|
|
464
|
+
try {
|
|
465
|
+
exported = await match.load();
|
|
466
|
+
} catch (caught) {
|
|
467
|
+
logger().error(`Route ${_seedcord_logger.paint.sky.bold(routeId)} failed to load its handler.`, caught);
|
|
468
|
+
await answer(caught, freshScope(match, payload, core), report, "failed");
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
if (isHandlerCtor(exported)) return exported;
|
|
472
|
+
logger().error(`Route ${_seedcord_logger.paint.sky.bold(routeId)} loaded an export that is not a handler class.`);
|
|
473
|
+
await answer(/* @__PURE__ */ new Error(`route ${routeId} loaded an export that is not a handler class`), freshScope(match, payload, core), report, "failed");
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
323
476
|
/**
|
|
324
|
-
* Runs the pre-ack phase for a matched interaction. Loads the route
|
|
477
|
+
* Runs the pre-ack phase for a matched interaction. Loads the route's handler class, constructs it, and
|
|
325
478
|
* runs its gates, and the sender posts any refusal. Returns the post-ack execute continuation, or null
|
|
326
479
|
* when a gate refuses or nothing can run.
|
|
327
480
|
*/
|
|
328
481
|
async function dispatchInteraction(args) {
|
|
329
482
|
const { match, payload, core } = args;
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
logger().error(`Route ${_seedcord_logger.paint.sky.bold(match.routeId ?? "unhandled")} failed to load its module.`, caught);
|
|
335
|
-
await handleFault(caught, freshScope(match, payload, core));
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
const ctor = handlerCtorOf(moduleExports);
|
|
339
|
-
if (!ctor) {
|
|
340
|
-
const routeId = match.routeId ?? "unhandled";
|
|
341
|
-
logger().error(`Route ${_seedcord_logger.paint.sky.bold(routeId)} loaded a module with no handler class.`);
|
|
342
|
-
await handleFault(/* @__PURE__ */ new Error(`route ${routeId} loaded a module with no handler class`), freshScope(match, payload, core));
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
const dispatch = new _seedcord_core.DispatchContext(match.routeId);
|
|
483
|
+
const report = dispatchReporter(match, payload, core);
|
|
484
|
+
const ctor = await loadHandlerCtor(match, payload, core, report);
|
|
485
|
+
if (!ctor) return null;
|
|
486
|
+
const dispatch = new _seedcord_core.DispatchContext(unhandledRouteId(match));
|
|
346
487
|
let handler;
|
|
347
488
|
try {
|
|
348
489
|
handler = new ctor(payload, core, dispatch);
|
|
349
490
|
} catch (caught) {
|
|
350
|
-
await
|
|
491
|
+
await answer(caught, freshScope(match, payload, core), report);
|
|
351
492
|
return null;
|
|
352
493
|
}
|
|
353
494
|
const scope = {
|
|
354
495
|
core,
|
|
355
496
|
payload,
|
|
356
|
-
routeId: match
|
|
497
|
+
routeId: unhandledRouteId(match),
|
|
357
498
|
sender: handler instanceof RepliableHandler ? handler.getSender() : null
|
|
358
499
|
};
|
|
359
|
-
|
|
500
|
+
const refusal = await gateRefusal(ctor, match, payload, core);
|
|
501
|
+
if (refusal) {
|
|
502
|
+
await answer(refusal.caught, scope, report);
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
360
505
|
return async () => {
|
|
361
506
|
try {
|
|
362
507
|
await handler.execute();
|
|
508
|
+
report("handled");
|
|
363
509
|
} catch (caught) {
|
|
364
|
-
await
|
|
510
|
+
await answer(caught, scope, report);
|
|
365
511
|
}
|
|
366
512
|
};
|
|
367
513
|
}
|
|
368
|
-
async function
|
|
369
|
-
if (match.kind === "autocomplete" || payload.type === discord_api_types_v10.InteractionType.ApplicationCommandAutocomplete) return
|
|
514
|
+
async function gateRefusal(ctor, match, payload, core) {
|
|
515
|
+
if (match.kind === "autocomplete" || payload.type === discord_api_types_v10.InteractionType.ApplicationCommandAutocomplete) return null;
|
|
370
516
|
const monitor = (0, _seedcord_core_internal.slowGateMonitor)();
|
|
371
517
|
try {
|
|
372
518
|
await (0, _seedcord_core_internal.runHandlerGates)(ctor, interactionGateContext(payload, core, match.routeId), match.routeId ?? void 0, monitor?.observe);
|
|
373
|
-
return
|
|
519
|
+
return null;
|
|
374
520
|
} catch (caught) {
|
|
375
|
-
|
|
376
|
-
return false;
|
|
521
|
+
return { caught };
|
|
377
522
|
} finally {
|
|
378
523
|
monitor?.report(match.routeId);
|
|
379
524
|
}
|
|
380
525
|
}
|
|
381
526
|
|
|
527
|
+
//#endregion
|
|
528
|
+
//#region src/dispatch/registerSubscribers.ts
|
|
529
|
+
/**
|
|
530
|
+
* Registers a manifest's subscribers on the bus without importing them. Each row's module loads on
|
|
531
|
+
* the first publish of one of its keys, which keeps an isolate that never publishes free of the cost.
|
|
532
|
+
*
|
|
533
|
+
* @internal
|
|
534
|
+
*/
|
|
535
|
+
function registerSubscribers(bus, manifest) {
|
|
536
|
+
for (const row of manifest.subscriberRoutes) bus.register({
|
|
537
|
+
keys: row.keys,
|
|
538
|
+
frequency: row.frequency,
|
|
539
|
+
resolve: async () => {
|
|
540
|
+
const keys = row.keys.join(", ");
|
|
541
|
+
let module;
|
|
542
|
+
try {
|
|
543
|
+
module = await row.load();
|
|
544
|
+
} catch (caught) {
|
|
545
|
+
throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.RouteModuleLoadFailed, [keys, row.from], { cause: caught });
|
|
546
|
+
}
|
|
547
|
+
if (!Object.hasOwn(module, row.exportName)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionRouteExportMissing, [
|
|
548
|
+
keys,
|
|
549
|
+
row.exportName,
|
|
550
|
+
row.from
|
|
551
|
+
]);
|
|
552
|
+
const exported = module[row.exportName];
|
|
553
|
+
if (typeof exported !== "function" || !(exported.prototype instanceof _seedcord_core.Subscriber)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.SubscriberRouteNotASubscriber, [
|
|
554
|
+
keys,
|
|
555
|
+
row.exportName,
|
|
556
|
+
row.from
|
|
557
|
+
]);
|
|
558
|
+
return exported;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
382
563
|
//#endregion
|
|
383
564
|
//#region src/inputs/HttpSlashOptions.ts
|
|
384
565
|
var HttpSlashOptions = class {
|
|
@@ -455,20 +636,6 @@ function numeric(value) {
|
|
|
455
636
|
return Number.isNaN(parsed) ? null : parsed;
|
|
456
637
|
}
|
|
457
638
|
|
|
458
|
-
//#endregion
|
|
459
|
-
//#region src/dispatch/slashRouteOf.ts
|
|
460
|
-
/**
|
|
461
|
-
* Computes the dispatch route for a chat-input or autocomplete payload, the raw-payload port of the
|
|
462
|
-
* gateway's `slashRouteOf`. Both assemble keys through `buildSlashRoute`, so a dispatched interaction
|
|
463
|
-
* and a generated registry key always match.
|
|
464
|
-
*/
|
|
465
|
-
function slashRouteOf(data) {
|
|
466
|
-
const first = data.options?.[0];
|
|
467
|
-
if (first?.type === discord_api_types_v10.ApplicationCommandOptionType.SubcommandGroup) return (0, _seedcord_utils_internal.buildSlashRoute)(data.name, first.options[0]?.name, first.name);
|
|
468
|
-
if (first?.type === discord_api_types_v10.ApplicationCommandOptionType.Subcommand) return (0, _seedcord_utils_internal.buildSlashRoute)(data.name, first.name);
|
|
469
|
-
return data.name;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
639
|
//#endregion
|
|
473
640
|
//#region src/handlers/interaction/AutocompleteHandler.ts
|
|
474
641
|
/**
|
|
@@ -482,7 +649,7 @@ function slashRouteOf(data) {
|
|
|
482
649
|
*/
|
|
483
650
|
var AutocompleteHandler = class extends _seedcord_core.BaseHandler {
|
|
484
651
|
constructor(event, core, dispatch) {
|
|
485
|
-
super(event, core, dispatch);
|
|
652
|
+
super(event, core, dispatch, "interactions");
|
|
486
653
|
}
|
|
487
654
|
/**
|
|
488
655
|
* Typed Discord REST API (`@discordjs/core/http-only`) over `core.rest`, one instance per core.
|
|
@@ -525,10 +692,14 @@ var AutocompleteHandler = class extends _seedcord_core.BaseHandler {
|
|
|
525
692
|
}
|
|
526
693
|
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
527
694
|
async respond(choices) {
|
|
528
|
-
|
|
695
|
+
const routeId = this.dispatch?.routeId ?? "autocomplete";
|
|
696
|
+
await (0, _seedcord_core_internal.reportedWrite)({
|
|
697
|
+
bus: this.core.bus,
|
|
698
|
+
interactionId: this.event.id
|
|
699
|
+
}, routeId, "respond", () => this.core.rest.post(discord_api_types_v10.Routes.interactionCallback(this.event.id, this.event.token), { body: {
|
|
529
700
|
type: discord_api_types_v10.InteractionResponseType.ApplicationCommandAutocompleteResult,
|
|
530
701
|
data: { choices }
|
|
531
|
-
} });
|
|
702
|
+
} }));
|
|
532
703
|
}
|
|
533
704
|
/** The firing command route, for a field whose completion differs per registered command. */
|
|
534
705
|
get route() {
|
|
@@ -626,14 +797,30 @@ function componentMapKey(type) {
|
|
|
626
797
|
default: return null;
|
|
627
798
|
}
|
|
628
799
|
}
|
|
629
|
-
function
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
800
|
+
function namedExport(routeId, row) {
|
|
801
|
+
return async () => {
|
|
802
|
+
let moduleExports;
|
|
803
|
+
try {
|
|
804
|
+
moduleExports = await row.load();
|
|
805
|
+
} catch (caught) {
|
|
806
|
+
throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.RouteModuleLoadFailed, [routeId, row.from], { cause: caught });
|
|
807
|
+
}
|
|
808
|
+
if (!Object.hasOwn(moduleExports, row.exportName)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionRouteExportMissing, [
|
|
809
|
+
routeId,
|
|
810
|
+
row.exportName,
|
|
811
|
+
row.from
|
|
812
|
+
]);
|
|
813
|
+
return moduleExports[row.exportName];
|
|
814
|
+
};
|
|
635
815
|
}
|
|
636
|
-
|
|
816
|
+
function describeRow(row) {
|
|
817
|
+
return `${row.exportName} (${row.from})`;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Builds the per-kind lookup maps.
|
|
821
|
+
*
|
|
822
|
+
* @throws A **SeedcordError** when two rows resolve to the same route.
|
|
823
|
+
*/
|
|
637
824
|
function buildRouteMaps(manifest) {
|
|
638
825
|
const maps = {
|
|
639
826
|
slash: /* @__PURE__ */ new Map(),
|
|
@@ -650,19 +837,36 @@ function buildRouteMaps(manifest) {
|
|
|
650
837
|
modal: /* @__PURE__ */ new Map()
|
|
651
838
|
}
|
|
652
839
|
};
|
|
653
|
-
|
|
840
|
+
const owners = /* @__PURE__ */ new Map();
|
|
841
|
+
function set(map, kind, key, row) {
|
|
842
|
+
const routeId = `${kind}:${key}`;
|
|
843
|
+
const owner = owners.get(routeId);
|
|
844
|
+
if (owner) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionDuplicateRoute, [
|
|
845
|
+
routeId,
|
|
846
|
+
describeRow(owner),
|
|
847
|
+
describeRow(row)
|
|
848
|
+
]);
|
|
849
|
+
owners.set(routeId, row);
|
|
850
|
+
map.set(key, {
|
|
851
|
+
kind,
|
|
852
|
+
routeId,
|
|
853
|
+
load: namedExport(routeId, row)
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
for (const row of manifest.commandRoutes) {
|
|
654
857
|
const kind = commandKind(row.type);
|
|
655
|
-
if (kind) set(maps[kind], kind, row.name, row
|
|
858
|
+
if (kind) set(maps[kind], kind, row.name, row);
|
|
656
859
|
}
|
|
657
|
-
for (const row of manifest.
|
|
658
|
-
for (const row of manifest.
|
|
860
|
+
for (const row of manifest.autocompleteRoutes) set(maps.autocomplete, "autocomplete", row.name, row);
|
|
861
|
+
for (const row of manifest.componentRoutes) set(maps.components[row.kind], COMPONENT_KIND[row.kind], row.prefix, row);
|
|
659
862
|
return maps;
|
|
660
863
|
}
|
|
661
|
-
function unhandled(kind) {
|
|
864
|
+
function unhandled(kind, attemptedKey) {
|
|
662
865
|
return {
|
|
663
866
|
kind,
|
|
664
867
|
routeId: null,
|
|
665
|
-
|
|
868
|
+
attemptedKey,
|
|
869
|
+
load: () => Promise.resolve(kind === "autocomplete" ? UnhandledAutocomplete : UnhandledRepliable)
|
|
666
870
|
};
|
|
667
871
|
}
|
|
668
872
|
/**
|
|
@@ -675,17 +879,27 @@ function unhandled(kind) {
|
|
|
675
879
|
function resolve(maps, interaction) {
|
|
676
880
|
switch (interaction.type) {
|
|
677
881
|
case discord_api_types_v10.InteractionType.ApplicationCommand: {
|
|
678
|
-
if (interaction.data.type === discord_api_types_v10.ApplicationCommandType.ChatInput)
|
|
882
|
+
if (interaction.data.type === discord_api_types_v10.ApplicationCommandType.ChatInput) {
|
|
883
|
+
const route = slashRouteOf(interaction.data);
|
|
884
|
+
return maps.slash.get(route) ?? unhandled("slash", route);
|
|
885
|
+
}
|
|
679
886
|
const kind = commandKind(interaction.data.type);
|
|
680
|
-
return kind ? maps[kind].get(interaction.data.name) ?? unhandled(kind) : null;
|
|
887
|
+
return kind ? maps[kind].get(interaction.data.name) ?? unhandled(kind, interaction.data.name) : null;
|
|
888
|
+
}
|
|
889
|
+
case discord_api_types_v10.InteractionType.ApplicationCommandAutocomplete: {
|
|
890
|
+
const route = slashRouteOf(interaction.data);
|
|
891
|
+
return maps.autocomplete.get(route) ?? unhandled("autocomplete", route);
|
|
681
892
|
}
|
|
682
|
-
case discord_api_types_v10.InteractionType.ApplicationCommandAutocomplete: return maps.autocomplete.get(slashRouteOf(interaction.data)) ?? unhandled("autocomplete");
|
|
683
893
|
case discord_api_types_v10.InteractionType.MessageComponent: {
|
|
684
894
|
const key = componentMapKey(interaction.data.component_type);
|
|
685
895
|
if (!key) return null;
|
|
686
|
-
|
|
896
|
+
const prefix = (0, _seedcord_core_internal.prefixOf)(interaction.data.custom_id);
|
|
897
|
+
return maps.components[key].get(prefix) ?? unhandled(COMPONENT_KIND[key], prefix);
|
|
898
|
+
}
|
|
899
|
+
case discord_api_types_v10.InteractionType.ModalSubmit: {
|
|
900
|
+
const prefix = (0, _seedcord_core_internal.prefixOf)(interaction.data.custom_id);
|
|
901
|
+
return maps.components.modal.get(prefix) ?? unhandled("modal", prefix);
|
|
687
902
|
}
|
|
688
|
-
case discord_api_types_v10.InteractionType.ModalSubmit: return maps.components.modal.get((0, _seedcord_core_internal.prefixOf)(interaction.data.custom_id)) ?? unhandled("modal");
|
|
689
903
|
default: return null;
|
|
690
904
|
}
|
|
691
905
|
}
|
|
@@ -766,7 +980,7 @@ function buildEngine(core, maps) {
|
|
|
766
980
|
const verifier = new Ed25519Verifier(envapt.Envapter.getRequired("DISCORD_PUBLIC_KEY", envapt.Converters.String));
|
|
767
981
|
const replays = new ReplayGuard();
|
|
768
982
|
const inFlight = /* @__PURE__ */ new Set();
|
|
769
|
-
const logger = new _seedcord_logger.Logger("Engine");
|
|
983
|
+
const logger = new _seedcord_logger.Logger("Engine", { channel: "bot" });
|
|
770
984
|
async function dispatchMatched(match, payload, ctx) {
|
|
771
985
|
const start = await dispatchInteraction({
|
|
772
986
|
match,
|
|
@@ -774,13 +988,18 @@ function buildEngine(core, maps) {
|
|
|
774
988
|
core
|
|
775
989
|
});
|
|
776
990
|
if (!start) return;
|
|
777
|
-
const work = start();
|
|
991
|
+
const work = start().catch(rootFault);
|
|
778
992
|
if (ctx) ctx.waitUntil(work);
|
|
779
993
|
else {
|
|
780
994
|
inFlight.add(work);
|
|
781
995
|
work.finally(() => inFlight.delete(work));
|
|
782
996
|
}
|
|
783
997
|
}
|
|
998
|
+
function rootFault(caught) {
|
|
999
|
+
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
1000
|
+
logger.error("dispatch failed past the boundary", error);
|
|
1001
|
+
core.bus[_seedcord_core_internal.PublishDefault]("unhandledInteractionError", { error });
|
|
1002
|
+
}
|
|
784
1003
|
const handle = async (request, ctx) => {
|
|
785
1004
|
if (request.method !== "POST") return new Response(null, {
|
|
786
1005
|
status: METHOD_NOT_ALLOWED,
|
|
@@ -801,10 +1020,11 @@ function buildEngine(core, maps) {
|
|
|
801
1020
|
if (!hasInteractionType(payload)) return new Response(null, { status: BAD_REQUEST });
|
|
802
1021
|
if (payload.type === PING) return Response.json({ type: discord_api_types_v10.InteractionResponseType.Pong });
|
|
803
1022
|
try {
|
|
1023
|
+
core.bus[_seedcord_core_internal.PublishDefault]("anyInteraction", { interaction: payload });
|
|
804
1024
|
const match = resolve(maps, payload);
|
|
805
1025
|
if (match) await dispatchMatched(match, payload, ctx);
|
|
806
1026
|
} catch (caught) {
|
|
807
|
-
|
|
1027
|
+
rootFault(caught);
|
|
808
1028
|
}
|
|
809
1029
|
return new Response(null, { status: ACCEPTED });
|
|
810
1030
|
};
|
|
@@ -830,7 +1050,10 @@ function buildEngine(core, maps) {
|
|
|
830
1050
|
* `SeedcordError` when either is missing or malformed.
|
|
831
1051
|
*/
|
|
832
1052
|
function createSeedcord(config, manifest) {
|
|
833
|
-
|
|
1053
|
+
const core = createCore(config, (0, _seedcord_errors_internal.validateDiscordToken)(envapt.Envapter.get("DISCORD_BOT_TOKEN")));
|
|
1054
|
+
core.bus.registerDefaults();
|
|
1055
|
+
registerSubscribers(core.bus, manifest);
|
|
1056
|
+
return buildEngine(core, buildRouteMaps(manifest)).handle;
|
|
834
1057
|
}
|
|
835
1058
|
|
|
836
1059
|
//#endregion
|
|
@@ -1029,6 +1252,22 @@ var ModalHandler = class extends ComponentHandler {
|
|
|
1029
1252
|
}
|
|
1030
1253
|
};
|
|
1031
1254
|
|
|
1255
|
+
//#endregion
|
|
1256
|
+
//#region src/subscribers/index.ts
|
|
1257
|
+
/**
|
|
1258
|
+
* Base class for an HTTP subscriber, binding `this.core` to the HTTP {@link Core}.
|
|
1259
|
+
*
|
|
1260
|
+
* @typeParam KeyOfSubscribers - The subscription key this handler receives
|
|
1261
|
+
*/
|
|
1262
|
+
var Subscriber = class extends _seedcord_core.Subscriber {};
|
|
1263
|
+
/**
|
|
1264
|
+
* Base class for an HTTP subscriber that delivers its event to a Discord webhook, binding
|
|
1265
|
+
* `this.core` to the HTTP {@link Core}.
|
|
1266
|
+
*
|
|
1267
|
+
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
1268
|
+
*/
|
|
1269
|
+
var WebhookLog = class extends _seedcord_core.WebhookLog {};
|
|
1270
|
+
|
|
1032
1271
|
//#endregion
|
|
1033
1272
|
//#region src/gates/Gated.ts
|
|
1034
1273
|
/**
|
|
@@ -1073,7 +1312,7 @@ function Gated(...gates) {
|
|
|
1073
1312
|
//#endregion
|
|
1074
1313
|
//#region src/version.ts
|
|
1075
1314
|
/** Package version */
|
|
1076
|
-
const version = "0.1.0-next.
|
|
1315
|
+
const version = "0.1.0-next.2";
|
|
1077
1316
|
|
|
1078
1317
|
//#endregion
|
|
1079
1318
|
Object.defineProperty(exports, 'AutocompleteHandler', {
|
|
@@ -1136,6 +1375,18 @@ Object.defineProperty(exports, 'SlashHandler', {
|
|
|
1136
1375
|
return SlashHandler;
|
|
1137
1376
|
}
|
|
1138
1377
|
});
|
|
1378
|
+
Object.defineProperty(exports, 'Subscriber', {
|
|
1379
|
+
enumerable: true,
|
|
1380
|
+
get: function () {
|
|
1381
|
+
return Subscriber;
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
Object.defineProperty(exports, 'WebhookLog', {
|
|
1385
|
+
enumerable: true,
|
|
1386
|
+
get: function () {
|
|
1387
|
+
return WebhookLog;
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1139
1390
|
Object.defineProperty(exports, '__exportAll', {
|
|
1140
1391
|
enumerable: true,
|
|
1141
1392
|
get: function () {
|
|
@@ -1172,4 +1423,4 @@ Object.defineProperty(exports, 'version', {
|
|
|
1172
1423
|
return version;
|
|
1173
1424
|
}
|
|
1174
1425
|
});
|
|
1175
|
-
//# sourceMappingURL=edge.index-
|
|
1426
|
+
//# sourceMappingURL=edge.index-joDL39cK.cjs.map
|