@seedcord/gateway 0.8.0 → 0.9.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +37 -35
- package/dist/index.mjs +92 -106
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -12
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { DRAIN_TASK_TIMEOUT_MS, DRAIN_WINDOW_MS, HealthCheck, InFlight, ShutdownPhase, StartupPhase, setEnvaptBaseDir, shutdownOf } from "@seedcord/core/node/internal";
|
|
3
3
|
import { WinstonConsoleSink, WinstonFileSink, installNodeConsole } from "@seedcord/logger/node";
|
|
4
|
-
import { COMMAND_DEPLOY_TIMEOUT_MS, CommandRegistry, EventMetadataKey, EventMiddlewareMetadataKey, FixedScope, GateNotice, GatedMetadataKey, HmrManager, InteractionMetadataKey, InteractionMiddlewareMetadataKey, MiddlewareRegistry, MissingPermissions, NoticeCard, PermissionNames, PublishDefault, SubscriberLoader, accessorStore, attachmentsOf, bindBotColor, busLoggerOf, clearStore, connectDevChannel, decodeComponentRoute, deferFlags, eventResultFor, guardedAccessor, interactionMiddleware, interactionRoutesOf, isEmojiTuple, logUnknownFault, outcomeFor, pickNotice, queuedMsFor, readBotToken, reportDispatch, reportedWrite, resultFor, runAfter, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
|
|
4
|
+
import { COMMAND_DEPLOY_TIMEOUT_MS, CommandRegistry, EventMetadataKey, EventMiddlewareMetadataKey, FixedScope, GateNotice, GatedMetadataKey, HmrManager, InteractionMetadataKey, InteractionMiddlewareMetadataKey, MiddlewareRegistry, MissingPermissions, NoticeCard, OnceTracker, PermissionNames, PublishDefault, SubscriberLoader, accessorStore, attachmentsOf, bindBotColor, busLoggerOf, clearStore, connectDevChannel, decodeComponentRoute, deferFlags, eventResultFor, guardedAccessor, interactionMiddleware, interactionRoutesOf, isEmojiTuple, logUnknownFault, outcomeFor, pickNotice, queuedMsFor, readBotToken, reportDispatch, reportedWrite, resultFor, runAfter, runHandlerGates, sendFlags, slowGateMonitor } from "@seedcord/core/internal";
|
|
5
5
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
6
6
|
import { SeedcordError, SeedcordTypeError, throwSingleOrAggregate } from "@seedcord/errors/internal";
|
|
7
|
-
import { ArraySourceBase, BaseHandler as BaseHandler$1, BaseReplySender, BuilderComponent,
|
|
7
|
+
import { ArraySourceBase, BaseHandler as BaseHandler$1, BaseReplySender, BuilderComponent, CursorSourceBase, CustomId, DispatchContext, Fault, InteractionKind, Notice, PaginatorBase, RepliableHandler as RepliableHandler$1, ResolvedEmoji, RowComponent, Silence, Subscriber as Subscriber$1, WebhookLog as WebhookLog$1, assertPermissions, defineGate } from "@seedcord/core";
|
|
8
8
|
import { Logger } from "@seedcord/logger";
|
|
9
9
|
import { ButtonStyle, Client, ComponentType, DiscordAPIError, Events, GatewayIntentBits, Guild, GuildMember, IntentsBitField, InteractionContextType, Message, MessageFlags, PermissionFlagsBits, RESTJSONErrorCodes, Role, User } from "discord.js";
|
|
10
10
|
import { ButtonBuilder, TextDisplayBuilder } from "@discordjs/builders";
|
|
11
11
|
import { asError, buildSlashRoute, isStringArray } from "@seedcord/utils/internal";
|
|
12
12
|
import { Plugin as Plugin$1 } from "@seedcord/core/plugin";
|
|
13
13
|
import { CoordinatedShutdown, CoordinatedStartup, ServerHost } from "@seedcord/core/node";
|
|
14
|
-
import { MemoryRateLimiter } from "@seedcord/rate-limiter";
|
|
15
14
|
import { HostAugmentTarget, HostVersion, SeedcordBrand } from "@seedcord/types/internal";
|
|
16
15
|
import { Envapter } from "envapt";
|
|
17
16
|
import { HmrModuleHandler, HmrModuleHandler as HmrModuleHandler$1 } from "@seedcord/core/hmr";
|
|
@@ -25,8 +24,6 @@ export * from "@seedcord/core"
|
|
|
25
24
|
|
|
26
25
|
export * from "@seedcord/logger"
|
|
27
26
|
|
|
28
|
-
export * from "@seedcord/rate-limiter"
|
|
29
|
-
|
|
30
27
|
export * from "@seedcord/utils"
|
|
31
28
|
|
|
32
29
|
export * from "@seedcord/event-emitter"
|
|
@@ -97,7 +94,7 @@ function RegisterEvent(...defs) {
|
|
|
97
94
|
* Attaches gates to a handler. The gates run before `execute`, and a gate refusing stops the handler with
|
|
98
95
|
* the reply or drop the gate threw. Multiple gates are ANDed, so each must pass. A gate that requires a
|
|
99
96
|
* context the handler does not provide (a button gate on a slash handler, an interaction gate on an event
|
|
100
|
-
* handler) is a compile error at this line, and the error
|
|
97
|
+
* handler) is a compile error at this line, and the error message shows the gate and the handler kind. Place it
|
|
101
98
|
* above the route decorator.
|
|
102
99
|
*
|
|
103
100
|
* Combine gates with {@link and} or {@link or} to build a single gate from multiple arms.
|
|
@@ -738,6 +735,10 @@ function slashRouteOf(interaction) {
|
|
|
738
735
|
* ```
|
|
739
736
|
*/
|
|
740
737
|
var SlashHandler = class extends InteractionHandler {
|
|
738
|
+
/** The selected command's path, such as `search/docs`. */
|
|
739
|
+
get route() {
|
|
740
|
+
return slashRouteOf(this.event);
|
|
741
|
+
}
|
|
741
742
|
/**
|
|
742
743
|
* The typed options for this command's route. Only the getters for kinds this command declares appear.
|
|
743
744
|
* Each getter's name argument is restricted to that command's options of its kind.
|
|
@@ -755,7 +756,7 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
755
756
|
return this.event.options;
|
|
756
757
|
}
|
|
757
758
|
/**
|
|
758
|
-
* Run the arm for
|
|
759
|
+
* Run the arm for the selected command. Use this only when the handler is registered for several
|
|
759
760
|
* routes.
|
|
760
761
|
*
|
|
761
762
|
* Provide one arm per registered route, keyed by its route string, and each arm receives that route's
|
|
@@ -779,7 +780,7 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
779
780
|
* ```
|
|
780
781
|
*/
|
|
781
782
|
async match(arms) {
|
|
782
|
-
const route =
|
|
783
|
+
const route = this.route;
|
|
783
784
|
const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
|
|
784
785
|
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
785
786
|
this.constructor.name,
|
|
@@ -828,12 +829,12 @@ var UserContextMenuHandler = class extends InteractionHandler {
|
|
|
828
829
|
get targetMember() {
|
|
829
830
|
return this.event.targetMember;
|
|
830
831
|
}
|
|
831
|
-
/**
|
|
832
|
-
get
|
|
832
|
+
/** The selected context-menu command's name, without the interaction-kind prefix. */
|
|
833
|
+
get route() {
|
|
833
834
|
return this.event.commandName;
|
|
834
835
|
}
|
|
835
836
|
/**
|
|
836
|
-
* Run the arm for
|
|
837
|
+
* Run the arm for the selected command. Use this only when the handler is registered for several
|
|
837
838
|
* names.
|
|
838
839
|
*
|
|
839
840
|
* Provide one arm per registered name, keyed by the name Discord shows in the right-click menu. Each
|
|
@@ -858,7 +859,7 @@ var UserContextMenuHandler = class extends InteractionHandler {
|
|
|
858
859
|
* ```
|
|
859
860
|
*/
|
|
860
861
|
async match(arms) {
|
|
861
|
-
return await armFor(this, arms, this.
|
|
862
|
+
return await armFor(this, arms, this.route)(this.target, this.targetMember);
|
|
862
863
|
}
|
|
863
864
|
};
|
|
864
865
|
/**
|
|
@@ -884,12 +885,12 @@ var MessageContextMenuHandler = class extends InteractionHandler {
|
|
|
884
885
|
get target() {
|
|
885
886
|
return this.event.targetMessage;
|
|
886
887
|
}
|
|
887
|
-
/**
|
|
888
|
-
get
|
|
888
|
+
/** The selected context-menu command's name, without the interaction-kind prefix. */
|
|
889
|
+
get route() {
|
|
889
890
|
return this.event.commandName;
|
|
890
891
|
}
|
|
891
892
|
/**
|
|
892
|
-
* Run the arm for
|
|
893
|
+
* Run the arm for the selected command. Use this only when the handler is registered for several
|
|
893
894
|
* names.
|
|
894
895
|
*
|
|
895
896
|
* Provide one arm per registered name, keyed by the name Discord shows in the right-click menu. Each
|
|
@@ -913,7 +914,7 @@ var MessageContextMenuHandler = class extends InteractionHandler {
|
|
|
913
914
|
* ```
|
|
914
915
|
*/
|
|
915
916
|
async match(arms) {
|
|
916
|
-
return await armFor(this, arms, this.
|
|
917
|
+
return await armFor(this, arms, this.route)(this.target);
|
|
917
918
|
}
|
|
918
919
|
};
|
|
919
920
|
|
|
@@ -936,7 +937,7 @@ var BaseHandler = class extends BaseHandler$1 {
|
|
|
936
937
|
*
|
|
937
938
|
* Pass the command route(s) from the generated registry as the generic, the same string(s) as
|
|
938
939
|
* `@AutocompleteRoute`. Branch on the focused field with `this.match`, read already-entered sibling options
|
|
939
|
-
* with `this.options`, and
|
|
940
|
+
* with `this.options`, and read the selected command's path from `this.route`.
|
|
940
941
|
*
|
|
941
942
|
* @typeParam Route - One or more route keys from {@link SlashRegistry}, e.g. `'search'` or `'search' | 'find'`.
|
|
942
943
|
* @typeParam Cache - The interaction cache state. The command's contexts set it.
|
|
@@ -1016,7 +1017,7 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1016
1017
|
interactionId: this.event.id
|
|
1017
1018
|
}, "respond", () => this.event.respond(choices));
|
|
1018
1019
|
}
|
|
1019
|
-
/** The
|
|
1020
|
+
/** The selected command's path, such as `search/docs`, without the interaction-kind prefix. */
|
|
1020
1021
|
get route() {
|
|
1021
1022
|
return slashRouteOf(this.event);
|
|
1022
1023
|
}
|
|
@@ -1051,11 +1052,20 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1051
1052
|
return this.sender.deferUpdate();
|
|
1052
1053
|
}
|
|
1053
1054
|
decoded;
|
|
1054
|
-
get
|
|
1055
|
+
get decodedRoute() {
|
|
1055
1056
|
this.decoded ??= decodeComponentRoute(this.constructor, this.event.customId);
|
|
1056
1057
|
return this.decoded;
|
|
1057
1058
|
}
|
|
1058
1059
|
/**
|
|
1060
|
+
* The matched custom-ID prefix, such as `approve`, without encoded params.
|
|
1061
|
+
*
|
|
1062
|
+
* @throws A **StaleCustomId** when the wire was encoded under an older shape.
|
|
1063
|
+
* @throws An **InvalidCustomId** when the wire is corrupt.
|
|
1064
|
+
*/
|
|
1065
|
+
get route() {
|
|
1066
|
+
return this.decodedRoute.prefix;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1059
1069
|
* The decoded params of the single route this handler is registered for.
|
|
1060
1070
|
*
|
|
1061
1071
|
* Reading this decodes `this.event.customId` once and caches the result. On a handler registered
|
|
@@ -1065,7 +1075,7 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1065
1075
|
* @throws An **InvalidCustomId** when the wire is corrupt. The controller boundary turns it into a reply.
|
|
1066
1076
|
*/
|
|
1067
1077
|
get params() {
|
|
1068
|
-
return this.
|
|
1078
|
+
return this.decodedRoute.params;
|
|
1069
1079
|
}
|
|
1070
1080
|
/**
|
|
1071
1081
|
* Run the arm for whichever route the component was minted from. Use this only when the handler is
|
|
@@ -1096,7 +1106,7 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1096
1106
|
* ```
|
|
1097
1107
|
*/
|
|
1098
1108
|
async match(arms) {
|
|
1099
|
-
const { prefix, params } = this.
|
|
1109
|
+
const { prefix, params } = this.decodedRoute;
|
|
1100
1110
|
const arm = Object.hasOwn(arms, prefix) ? arms[prefix] : void 0;
|
|
1101
1111
|
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
1102
1112
|
this.constructor.name,
|
|
@@ -1175,7 +1185,7 @@ var ModalHandler = class extends ComponentHandler {
|
|
|
1175
1185
|
}
|
|
1176
1186
|
ensureSourceMessage(method) {
|
|
1177
1187
|
if (this.event.isFromMessage()) return;
|
|
1178
|
-
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
1188
|
+
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.dispatch.routeId]);
|
|
1179
1189
|
}
|
|
1180
1190
|
};
|
|
1181
1191
|
|
|
@@ -1364,7 +1374,7 @@ var EventHandler = class extends BaseHandler {
|
|
|
1364
1374
|
this.firedEvent = eventName;
|
|
1365
1375
|
}
|
|
1366
1376
|
/**
|
|
1367
|
-
* Run the arm for
|
|
1377
|
+
* Run the arm for the current event. Use this only when the handler is registered for several
|
|
1368
1378
|
* events. A single-event handler reads `this.event` directly. On a multi-event handler `this.event` is
|
|
1369
1379
|
* `never`, so match is the only way to read the payload.
|
|
1370
1380
|
*
|
|
@@ -1421,7 +1431,7 @@ var EventMiddleware = class extends BaseHandler {
|
|
|
1421
1431
|
this.firedEvent = eventName;
|
|
1422
1432
|
}
|
|
1423
1433
|
/**
|
|
1424
|
-
* The
|
|
1434
|
+
* The name of the event being processed. Read it on a catchall or multi-event middleware, where `this.event`
|
|
1425
1435
|
* is `never`, to do work that does not depend on the payload shape. A single-event middleware reads
|
|
1426
1436
|
* `this.event` directly and does not need this.
|
|
1427
1437
|
*/
|
|
@@ -1818,19 +1828,20 @@ const eventMiddleware = (ctor) => {
|
|
|
1818
1828
|
*/
|
|
1819
1829
|
var EventDispatcher = class {
|
|
1820
1830
|
core;
|
|
1831
|
+
inFlight;
|
|
1821
1832
|
logger = new Logger("Events", { channel: "events" });
|
|
1822
1833
|
isInitialized = false;
|
|
1823
1834
|
eventMap = /* @__PURE__ */ new Map();
|
|
1824
1835
|
middlewares = new MiddlewareRegistry(eventMiddleware);
|
|
1825
|
-
|
|
1836
|
+
onceRuns = new OnceTracker();
|
|
1826
1837
|
attachedEvents = /* @__PURE__ */ new Set();
|
|
1827
|
-
inFlight = new InFlight(this.logger, "Events");
|
|
1828
1838
|
loading = false;
|
|
1829
1839
|
loadedHandlers = [];
|
|
1830
1840
|
loadedMiddlewares = [];
|
|
1831
1841
|
hmrHandler;
|
|
1832
|
-
constructor(core) {
|
|
1842
|
+
constructor(core, inFlight) {
|
|
1833
1843
|
this.core = core;
|
|
1844
|
+
this.inFlight = inFlight;
|
|
1834
1845
|
const eventsDir = this.core.config.bot.events.path;
|
|
1835
1846
|
if (!eventsDir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["EventDispatcher", "events"]);
|
|
1836
1847
|
if (!Envapter.isDevelopment && !Envapter.isTest) return;
|
|
@@ -1980,26 +1991,19 @@ var EventDispatcher = class {
|
|
|
1980
1991
|
const handlerEntries = this.eventMap.get(eventName);
|
|
1981
1992
|
this.logger.debug(`Attaching ${paint.sky.bold(eventName)} to the client with ${paint.mute(handlerEntries?.length ?? 0)} handler(s)`);
|
|
1982
1993
|
this.core.bot.client.on(eventName, (...args) => {
|
|
1983
|
-
|
|
1984
|
-
const run = this.processEvent(eventName, args).catch((caught) => {
|
|
1985
|
-
const error = asError(caught);
|
|
1986
|
-
this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
1987
|
-
this.core.bus[PublishDefault]("unhandledEventError", { error });
|
|
1988
|
-
});
|
|
1989
|
-
this.inFlight.track(run);
|
|
1994
|
+
this.inFlight.admit(() => this.processEvent(eventName, args).catch((caught) => this.reportUnhandled(caught)));
|
|
1990
1995
|
});
|
|
1991
1996
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
return this.inFlight.drain(timeoutMs);
|
|
1997
|
+
reportUnhandled(caught) {
|
|
1998
|
+
const error = asError(caught);
|
|
1999
|
+
this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
2000
|
+
this.core.bus[PublishDefault]("unhandledEventError", { error });
|
|
1997
2001
|
}
|
|
1998
2002
|
async processEvent(eventName, args) {
|
|
1999
2003
|
const startedAt = performance.now();
|
|
2000
2004
|
const handlerEntries = this.eventMap.get(eventName);
|
|
2001
2005
|
if (!handlerEntries || handlerEntries.length === 0) return;
|
|
2002
|
-
const handlersToExecute = handlerEntries.filter((entry) =>
|
|
2006
|
+
const handlersToExecute = handlerEntries.filter((entry) => !this.onceRuns.isSpent(entry, eventName));
|
|
2003
2007
|
if (handlersToExecute.length === 0) return;
|
|
2004
2008
|
const dispatch = new DispatchContext(`event:${String(eventName)}`);
|
|
2005
2009
|
reportEventDispatching(this.core, dispatch.id, eventName, args);
|
|
@@ -2009,13 +2013,8 @@ var EventDispatcher = class {
|
|
|
2009
2013
|
try {
|
|
2010
2014
|
stopped = await this.runMiddlewares(eventName, args, dispatch, ran);
|
|
2011
2015
|
if (stopped) return;
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
if (this.executedOnceHandlers.has(entry.ctor)) continue;
|
|
2015
|
-
this.executedOnceHandlers.add(entry.ctor);
|
|
2016
|
-
}
|
|
2017
|
-
handlers.push(await this.processHandler(eventName, entry.ctor, args, dispatch));
|
|
2018
|
-
}
|
|
2016
|
+
const results = handlersToExecute.filter((entry) => this.onceRuns.claim(entry, eventName)).map((entry) => this.processHandler(eventName, entry.ctor, args, dispatch));
|
|
2017
|
+
handlers.push(...await Promise.all(results));
|
|
2019
2018
|
} finally {
|
|
2020
2019
|
const result = eventResultFor(stopped, handlers);
|
|
2021
2020
|
reportEventDispatched(this.core, dispatch.id, eventName, result, startedAt);
|
|
@@ -2024,7 +2023,7 @@ var EventDispatcher = class {
|
|
|
2024
2023
|
}
|
|
2025
2024
|
async processHandler(eventName, Ctor, args, dispatch) {
|
|
2026
2025
|
try {
|
|
2027
|
-
this.logger.
|
|
2026
|
+
this.logger.trace(`Processing ${paint.sky.bold(eventName)} with ${paint.mute(Ctor.name)}`);
|
|
2028
2027
|
const handler = new Ctor(args, this.core, dispatch, eventName);
|
|
2029
2028
|
const eventCtx = eventGateContext(eventName, args, this.core, dispatch);
|
|
2030
2029
|
await runHandlerGates(Ctor, eventCtx);
|
|
@@ -2034,12 +2033,16 @@ var EventDispatcher = class {
|
|
|
2034
2033
|
outcome: "handled"
|
|
2035
2034
|
};
|
|
2036
2035
|
} catch (caught) {
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2036
|
+
try {
|
|
2037
|
+
handleEventFault(caught, {
|
|
2038
|
+
eventName: String(eventName),
|
|
2039
|
+
handlerName: Ctor.name,
|
|
2040
|
+
args,
|
|
2041
|
+
dispatch
|
|
2042
|
+
}, this.core);
|
|
2043
|
+
} catch (reportFailed) {
|
|
2044
|
+
this.reportUnhandled(reportFailed);
|
|
2045
|
+
}
|
|
2043
2046
|
return {
|
|
2044
2047
|
handler: Ctor.name,
|
|
2045
2048
|
...resultFor(caught)
|
|
@@ -2112,7 +2115,8 @@ async function handleInteractionFault(caught, interaction, core, dispatch, sende
|
|
|
2112
2115
|
user: interaction.user,
|
|
2113
2116
|
metadata: interaction
|
|
2114
2117
|
});
|
|
2115
|
-
|
|
2118
|
+
const liveSender = sender ?? new ReplySender(interaction, dispatch, core.bus);
|
|
2119
|
+
await sendGuarded(() => liveSender.send(response, { ephemeral: error instanceof Notice ? error.ephemeral : true }));
|
|
2116
2120
|
}
|
|
2117
2121
|
async function sendEmptyChoices(interaction, core, dispatch) {
|
|
2118
2122
|
const telemetry = {
|
|
@@ -2120,21 +2124,17 @@ async function sendEmptyChoices(interaction, core, dispatch) {
|
|
|
2120
2124
|
dispatch,
|
|
2121
2125
|
interactionId: interaction.id
|
|
2122
2126
|
};
|
|
2123
|
-
|
|
2124
|
-
await reportedWrite(telemetry, "respond", () => interaction.respond([]));
|
|
2125
|
-
} catch (error) {
|
|
2126
|
-
logger.debug(`autocomplete empty-choices send failed: ${String(error)}`);
|
|
2127
|
-
}
|
|
2127
|
+
await sendGuarded(() => reportedWrite(telemetry, "respond", () => interaction.respond([])));
|
|
2128
2128
|
}
|
|
2129
|
-
async function sendGuarded(
|
|
2129
|
+
async function sendGuarded(send) {
|
|
2130
2130
|
try {
|
|
2131
|
-
await
|
|
2131
|
+
await send();
|
|
2132
2132
|
} catch (error) {
|
|
2133
2133
|
if (error instanceof DiscordAPIError && HARMLESS_API_CODES.has(error.code)) {
|
|
2134
2134
|
logger.debug(`reply send hit harmless code ${error.code}`);
|
|
2135
2135
|
return;
|
|
2136
2136
|
}
|
|
2137
|
-
logger.error("
|
|
2137
|
+
logger.error("fault response send failed", error);
|
|
2138
2138
|
}
|
|
2139
2139
|
}
|
|
2140
2140
|
|
|
@@ -2142,6 +2142,7 @@ async function sendGuarded(sender, response, ephemeral) {
|
|
|
2142
2142
|
//#region src/bot/controllers/InteractionDispatcher.ts
|
|
2143
2143
|
var InteractionDispatcher = class {
|
|
2144
2144
|
core;
|
|
2145
|
+
inFlight;
|
|
2145
2146
|
logger = new Logger("Interactions", { channel: "interactions" });
|
|
2146
2147
|
isInitialized = false;
|
|
2147
2148
|
maps = {
|
|
@@ -2160,13 +2161,13 @@ var InteractionDispatcher = class {
|
|
|
2160
2161
|
handlerFiles = /* @__PURE__ */ new Map();
|
|
2161
2162
|
keysToIgnore = /* @__PURE__ */ new Set();
|
|
2162
2163
|
middlewares = new MiddlewareRegistry(interactionMiddleware);
|
|
2163
|
-
inFlight = new InFlight(this.logger, "Interactions");
|
|
2164
2164
|
loading = false;
|
|
2165
2165
|
loadedHandlers = [];
|
|
2166
2166
|
loadedMiddlewares = [];
|
|
2167
2167
|
hmrHandler;
|
|
2168
|
-
constructor(core) {
|
|
2168
|
+
constructor(core, inFlight) {
|
|
2169
2169
|
this.core = core;
|
|
2170
|
+
this.inFlight = inFlight;
|
|
2170
2171
|
const ignoredKeysFromConfig = hasKeys(this.core.config.bot.interactions, ["ignoreCustomIds"]) ? this.core.config.bot.interactions.ignoreCustomIds : void 0;
|
|
2171
2172
|
if (ignoredKeysFromConfig) for (const ignoredKey of ignoredKeysFromConfig) this.keysToIgnore.add(ignoredKey);
|
|
2172
2173
|
this.keysToIgnore.add(CONFIRM_DEF);
|
|
@@ -2304,22 +2305,16 @@ var InteractionDispatcher = class {
|
|
|
2304
2305
|
}
|
|
2305
2306
|
attachToClient() {
|
|
2306
2307
|
this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2308
|
+
this.inFlight.admit(() => {
|
|
2309
|
+
this.core.bus[PublishDefault]("anyInteraction", { interaction });
|
|
2310
|
+
return this.handleInteraction(interaction).catch((caught) => {
|
|
2311
|
+
const error = asError(caught);
|
|
2312
|
+
this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
2313
|
+
this.core.bus[PublishDefault]("unhandledInteractionError", { error });
|
|
2314
|
+
});
|
|
2313
2315
|
});
|
|
2314
|
-
this.inFlight.track(run);
|
|
2315
2316
|
});
|
|
2316
2317
|
}
|
|
2317
|
-
stopAccepting() {
|
|
2318
|
-
this.inFlight.close();
|
|
2319
|
-
}
|
|
2320
|
-
drain(timeoutMs) {
|
|
2321
|
-
return this.inFlight.drain(timeoutMs);
|
|
2322
|
-
}
|
|
2323
2318
|
async handleCustomIdInteraction(interaction, kind) {
|
|
2324
2319
|
if ([...this.keysToIgnore].some((matcher) => matcher.owns(interaction.customId))) return;
|
|
2325
2320
|
const prefix = prefixOf(interaction.customId);
|
|
@@ -2380,7 +2375,7 @@ var InteractionDispatcher = class {
|
|
|
2380
2375
|
}
|
|
2381
2376
|
buildHandler(HandlerCtor, interaction, dispatch, key, isFallback) {
|
|
2382
2377
|
if (isFallback) this.logger.warn(`No handler found for key ${paint.sky.bold(key)}. Falling back to ${HandlerCtor.name}.`);
|
|
2383
|
-
this.logger.
|
|
2378
|
+
this.logger.trace(`Processing ${paint.sky.bold(key)} with ${paint.mute(HandlerCtor.name)}`);
|
|
2384
2379
|
return new HandlerCtor(interaction, this.core, dispatch);
|
|
2385
2380
|
}
|
|
2386
2381
|
reporterFor(row) {
|
|
@@ -2535,6 +2530,7 @@ var Bot = class {
|
|
|
2535
2530
|
#logger = new Logger("Bot", { channel: "bot" });
|
|
2536
2531
|
/** @internal */
|
|
2537
2532
|
[loggerSlot] = this.#logger;
|
|
2533
|
+
#inFlight = new InFlight(this.#logger, "Dispatch");
|
|
2538
2534
|
#isInitialized = false;
|
|
2539
2535
|
#readyToDeploy = Promise.withResolvers();
|
|
2540
2536
|
#intents;
|
|
@@ -2553,8 +2549,8 @@ var Bot = class {
|
|
|
2553
2549
|
constructor(core) {
|
|
2554
2550
|
this.#intents = core.config.bot.clientOptions.intents;
|
|
2555
2551
|
this.#client = new Client(core.config.bot.clientOptions);
|
|
2556
|
-
if (core.config.bot.interactions.path) this.#interactions = new InteractionDispatcher(core);
|
|
2557
|
-
if (core.config.bot.events.path) this.#events = new EventDispatcher(core);
|
|
2552
|
+
if (core.config.bot.interactions.path) this.#interactions = new InteractionDispatcher(core, this.#inFlight);
|
|
2553
|
+
if (core.config.bot.events.path) this.#events = new EventDispatcher(core, this.#inFlight);
|
|
2558
2554
|
if (core.config.bot.commands.path) this.#commandRegistry = new CommandRegistry(core);
|
|
2559
2555
|
this.#emojiInjector = new EmojiInjector(core);
|
|
2560
2556
|
this.#registerStartupTasks(core);
|
|
@@ -2575,39 +2571,36 @@ var Bot = class {
|
|
|
2575
2571
|
return application.id;
|
|
2576
2572
|
}
|
|
2577
2573
|
#registerStartupTasks(core) {
|
|
2574
|
+
const interactions = this.#interactions;
|
|
2575
|
+
if (interactions) core.startup.addTask(StartupPhase.Configuration, "interactions-initialization", () => interactions.init());
|
|
2576
|
+
const events = this.#events;
|
|
2577
|
+
if (events) core.startup.addTask(StartupPhase.Configuration, "events-initialization", () => events.init());
|
|
2578
2578
|
core.startup.addTask(StartupPhase.Login, "bot-initialization", async (signal) => {
|
|
2579
2579
|
this.#logger.utils.initialization("Bot", "start");
|
|
2580
2580
|
await this.#init(signal);
|
|
2581
2581
|
this.#logger.utils.initialization("Bot", "end");
|
|
2582
2582
|
});
|
|
2583
2583
|
const registry = this.#commandRegistry;
|
|
2584
|
-
if (
|
|
2585
|
-
core.startup.addTask(StartupPhase.
|
|
2584
|
+
if (registry) core.startup.addTask(StartupPhase.Login, "command-deploy", (signal) => this.#deployCommands(registry, signal), COMMAND_DEPLOY_TIMEOUT_MS);
|
|
2585
|
+
core.startup.addTask(StartupPhase.Ready, "start-dispatch", () => {
|
|
2586
|
+
this.#inFlight.open();
|
|
2587
|
+
return Promise.resolve();
|
|
2588
|
+
});
|
|
2586
2589
|
}
|
|
2587
2590
|
#registerShutdownTasks(core) {
|
|
2588
2591
|
core.shutdown.addTask(ShutdownPhase.Unbind, "stop-dispatch", () => {
|
|
2589
|
-
this.#
|
|
2592
|
+
this.#inFlight.close();
|
|
2590
2593
|
return Promise.resolve();
|
|
2591
2594
|
}, UNBIND_TIMEOUT_MS);
|
|
2592
|
-
core.shutdown.addTask(ShutdownPhase.Drain, "drain-dispatch", () => this.#drain(), DRAIN_TASK_TIMEOUT_MS);
|
|
2595
|
+
core.shutdown.addTask(ShutdownPhase.Drain, "drain-dispatch", () => this.#inFlight.drain(DRAIN_WINDOW_MS), DRAIN_TASK_TIMEOUT_MS);
|
|
2593
2596
|
core.shutdown.addTask(ShutdownPhase.Logout, "stop-bot", async () => await this.#stop(), LOGOUT_TIMEOUT_MS);
|
|
2594
2597
|
}
|
|
2595
|
-
#stopAccepting() {
|
|
2596
|
-
this.#interactions?.stopAccepting();
|
|
2597
|
-
this.#events?.stopAccepting();
|
|
2598
|
-
}
|
|
2599
|
-
async #drain() {
|
|
2600
|
-
await Promise.allSettled([this.#interactions?.drain(DRAIN_WINDOW_MS), this.#events?.drain(DRAIN_WINDOW_MS)]);
|
|
2601
|
-
}
|
|
2602
2598
|
async #init(signal) {
|
|
2603
2599
|
if (this.#isInitialized) return;
|
|
2604
2600
|
this.#isInitialized = true;
|
|
2605
2601
|
signal.addEventListener("abort", () => this.#readyToDeploy.resolve(false), { once: true });
|
|
2606
2602
|
try {
|
|
2607
|
-
|
|
2608
|
-
if (this.#interactions) await this.#interactions.init();
|
|
2609
|
-
if (this.#events) await this.#events.init();
|
|
2610
|
-
await this.#login(token);
|
|
2603
|
+
await this.#login(this.botToken);
|
|
2611
2604
|
await this.#emojiInjector.init();
|
|
2612
2605
|
} catch (error) {
|
|
2613
2606
|
this.#readyToDeploy.resolve(false);
|
|
@@ -2629,8 +2622,7 @@ var Bot = class {
|
|
|
2629
2622
|
async #login(token) {
|
|
2630
2623
|
const ready = Promise.withResolvers();
|
|
2631
2624
|
this.#client.once(Events.ClientReady, () => ready.resolve());
|
|
2632
|
-
this.#client.login(token);
|
|
2633
|
-
await ready.promise;
|
|
2625
|
+
await Promise.all([this.#client.login(token), ready.promise]);
|
|
2634
2626
|
this.#logger.info(`Logged in as ${paint.sky.bold(this.#client.user?.username)}!`);
|
|
2635
2627
|
return this;
|
|
2636
2628
|
}
|
|
@@ -2646,7 +2638,7 @@ var Bot = class {
|
|
|
2646
2638
|
//#endregion
|
|
2647
2639
|
//#region src/version.ts
|
|
2648
2640
|
/** Package version */
|
|
2649
|
-
const version = "0.
|
|
2641
|
+
const version = "0.9.0-next.0";
|
|
2650
2642
|
|
|
2651
2643
|
//#endregion
|
|
2652
2644
|
//#region src/Seedcord.ts
|
|
@@ -2664,8 +2656,6 @@ var Seedcord = class extends ServerHost {
|
|
|
2664
2656
|
[HostVersion] = version;
|
|
2665
2657
|
#healthCheck;
|
|
2666
2658
|
#hmrManager;
|
|
2667
|
-
/** @see {@link Bus} */
|
|
2668
|
-
bus;
|
|
2669
2659
|
#subscribers;
|
|
2670
2660
|
/** @see {@link Bot} */
|
|
2671
2661
|
bot;
|
|
@@ -2675,24 +2665,20 @@ var Seedcord = class extends ServerHost {
|
|
|
2675
2665
|
get applicationId() {
|
|
2676
2666
|
return this.bot.applicationId;
|
|
2677
2667
|
}
|
|
2678
|
-
/** @see {@link IRateLimiter} */
|
|
2679
|
-
rateLimiter;
|
|
2680
2668
|
/**
|
|
2681
2669
|
* @param config - Bot configuration including paths and Discord client options
|
|
2682
2670
|
* @throws A **SeedcordError** when another `Seedcord` exists in this process and has not shut down.
|
|
2683
2671
|
* @throws A **SeedcordError** when the running Node or Bun is older than the range in `engines`.
|
|
2684
2672
|
*/
|
|
2685
2673
|
constructor(config) {
|
|
2686
|
-
super("gateway", new CoordinatedShutdown(config.lifecycle?.shutdownDeadline), new CoordinatedStartup());
|
|
2674
|
+
super("gateway", new CoordinatedShutdown(config.lifecycle?.shutdownDeadline), new CoordinatedStartup(), config.kv);
|
|
2687
2675
|
this.config = config;
|
|
2688
2676
|
bindBotColor(() => this.config.botColor);
|
|
2689
2677
|
this.#hmrManager = new HmrManager();
|
|
2690
2678
|
this.#hmrManager.init(connectDevChannel());
|
|
2691
|
-
this.bus = new Bus(this);
|
|
2692
2679
|
this.#subscribers = new SubscriberLoader(this.bus, config.subscribers.path);
|
|
2693
2680
|
this.bot = new Bot(this);
|
|
2694
2681
|
this.rest = this.bot.client.rest;
|
|
2695
|
-
this.rateLimiter = config.store ?? new MemoryRateLimiter();
|
|
2696
2682
|
this.#healthCheck = HealthCheck.fromOption(shutdownOf(this), config.healthCheck);
|
|
2697
2683
|
this.#registerStartupTasks();
|
|
2698
2684
|
}
|