@seedcord/gateway 0.1.0-next.4 → 0.1.0-next.5
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.cjs +209 -635
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +82 -353
- package/dist/index.mjs +211 -637
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -8
package/dist/index.mjs
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { installNodeDefaults } from "@seedcord/logger/node";
|
|
3
|
-
import { BaseReplySender, CommandMetadataKey, EventMetadataKey, GateNotice, GatedMetadataKey, HmrManager, InteractionMetadataKey, InteractionRouteKeys, InteractionRoutes, MiddlewareMetadataKey, MissingPermissions, NoticeCard, PAGE_MAX, PermissionNames,
|
|
3
|
+
import { BaseReplySender, CommandMetadataKey, EventMetadataKey, FaultThrottle, GateNotice, GatedMetadataKey, HmrManager, InteractionMetadataKey, InteractionRouteKeys, InteractionRoutes, MiddlewareMetadataKey, MissingPermissions, NoticeCard, PAGE_MAX, PermissionNames, PublishDefault, areRoutes, asError, decodeComponentRoute, deferFlags, dispatchedPayload, getDevChannel, outcomeFor, pageCursor, pickNotice, prefixOf, queuedMsFor, reportedWrite, routeIdOf, runHandlerGates, sendFlags, setBotColor, slowGateMonitor } from "@seedcord/core/internal";
|
|
4
4
|
import { SeedcordErrorCode } from "@seedcord/errors";
|
|
5
5
|
import { SeedcordError, SeedcordRangeError, SeedcordTypeError, validateDiscordToken } from "@seedcord/errors/internal";
|
|
6
|
-
import { BaseHandler, BuilderComponent, CustomId, DispatchContext, Fault, Notice, RepliableHandler as RepliableHandler$1, RowComponent, Silence, defineGate, paginate } from "@seedcord/core";
|
|
6
|
+
import { BaseHandler, BuilderComponent, Bus, CustomId, DispatchContext, Fault, Notice, RepliableHandler as RepliableHandler$1, RowComponent, Silence, Subscriber as Subscriber$1, WebhookLog as WebhookLog$1, defineGate, paginate } from "@seedcord/core";
|
|
7
7
|
import { Logger, LoggerChannelRegistry, paint } from "@seedcord/logger";
|
|
8
8
|
import { ActionRowBuilder, ButtonBuilder, ContextMenuCommandBuilder, SlashCommandBuilder, TextDisplayBuilder } from "@discordjs/builders";
|
|
9
9
|
import { buildSlashRoute, routeLeavesOf } from "@seedcord/utils/internal";
|
|
10
10
|
import { ApplicationCommandType, ButtonStyle, Client, Collection, ComponentType, DiscordAPIError, Events, Guild, GuildMember, Message, MessageFlags, PermissionFlagsBits, RESTJSONErrorCodes, Role, TextChannel, User, chatInputApplicationCommandMention } from "discord.js";
|
|
11
11
|
import * as crypto from "node:crypto";
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import { MessageFlags as MessageFlags$1, Routes, SeparatorSpacingSize } from "discord-api-types/v10";
|
|
15
|
-
import { filterCirculars, formatFilePath, hasKeys, stripAnsi, timestampFromSnowflake } from "@seedcord/utils";
|
|
16
|
-
import { DiscordAPIError as DiscordAPIError$1, REST } from "@discordjs/rest";
|
|
17
|
-
import { CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, settleWithin } from "@seedcord/core/node/internal";
|
|
12
|
+
import { Plugin as Plugin$1 } from "@seedcord/core/plugin";
|
|
13
|
+
import { CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader, settleWithin } from "@seedcord/core/node/internal";
|
|
18
14
|
import { MemoryRateLimiter } from "@seedcord/rate-limiter";
|
|
19
15
|
import { SeedcordBrand } from "@seedcord/types/internal";
|
|
20
|
-
import {
|
|
16
|
+
import { Envapter } from "envapt";
|
|
17
|
+
import chalk from "chalk";
|
|
21
18
|
import { Envapt } from "envapt/legacy";
|
|
22
19
|
import { resolve } from "node:path";
|
|
23
20
|
import { HmrModuleHandler } from "@seedcord/core/hmr";
|
|
21
|
+
import { formatFilePath, hasKeys } from "@seedcord/utils";
|
|
24
22
|
import { traverseDirectory } from "@seedcord/utils/node";
|
|
25
23
|
|
|
26
24
|
export * from "@seedcord/errors"
|
|
@@ -29,10 +27,10 @@ export * from "@seedcord/core"
|
|
|
29
27
|
|
|
30
28
|
export * from "@seedcord/logger"
|
|
31
29
|
|
|
32
|
-
export * from "@seedcord/utils"
|
|
33
|
-
|
|
34
30
|
export * from "@seedcord/rate-limiter"
|
|
35
31
|
|
|
32
|
+
export * from "@seedcord/utils"
|
|
33
|
+
|
|
36
34
|
export * from "@seedcord/event-emitter"
|
|
37
35
|
|
|
38
36
|
//#region src/logger-bootstrap.ts
|
|
@@ -147,7 +145,7 @@ let MiddlewareType = /* @__PURE__ */ function(MiddlewareType) {
|
|
|
147
145
|
* Interaction middleware cannot specify event filters.
|
|
148
146
|
*
|
|
149
147
|
* @param type - Middleware kind from {@link MiddlewareType}
|
|
150
|
-
* @param priority - Ordering value where lower runs earlier. {@default `0`}
|
|
148
|
+
* @param priority - Ordering value where lower runs earlier. {@default `0` }
|
|
151
149
|
* @param options - Additional registration options
|
|
152
150
|
*
|
|
153
151
|
* @decorator
|
|
@@ -335,7 +333,7 @@ const Emojis = emojiStorage;
|
|
|
335
333
|
*/
|
|
336
334
|
var EmojiInjector = class {
|
|
337
335
|
core;
|
|
338
|
-
logger = new Logger("Emojis");
|
|
336
|
+
logger = new Logger("Emojis", { channel: "bot" });
|
|
339
337
|
constructor(core) {
|
|
340
338
|
this.core = core;
|
|
341
339
|
}
|
|
@@ -410,7 +408,7 @@ const MULTI_GUILD = Symbol("seedcord:commandmentions:multi-guild");
|
|
|
410
408
|
*/
|
|
411
409
|
var CommandMentionInjector = class {
|
|
412
410
|
core;
|
|
413
|
-
logger = new Logger("CommandMentions");
|
|
411
|
+
logger = new Logger("CommandMentions", { channel: "bot" });
|
|
414
412
|
constructor(core) {
|
|
415
413
|
this.core = core;
|
|
416
414
|
}
|
|
@@ -568,7 +566,7 @@ function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
|
568
566
|
}
|
|
569
567
|
const me = target.guild.members.me;
|
|
570
568
|
if (!me) {
|
|
571
|
-
new Logger("checkBotPermissions").warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
|
|
569
|
+
new Logger("checkBotPermissions", { channel: "errors" }).warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
|
|
572
570
|
const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
|
|
573
571
|
throw new ((errors?.missingNotice) ?? MissingPermissions)(void 0, "The bot", names);
|
|
574
572
|
}
|
|
@@ -767,8 +765,11 @@ function seedState(interaction) {
|
|
|
767
765
|
*/
|
|
768
766
|
var ReplySender = class extends BaseReplySender {
|
|
769
767
|
interaction;
|
|
770
|
-
constructor(interaction, routeId) {
|
|
771
|
-
super(routeId, seedState(interaction)
|
|
768
|
+
constructor(interaction, routeId, bus) {
|
|
769
|
+
super(routeId, seedState(interaction), bus && {
|
|
770
|
+
bus,
|
|
771
|
+
interactionId: interaction.id
|
|
772
|
+
});
|
|
772
773
|
this.interaction = interaction;
|
|
773
774
|
}
|
|
774
775
|
async writeReply(response, opts) {
|
|
@@ -875,43 +876,9 @@ function slashRouteOf(interaction) {
|
|
|
875
876
|
return buildSlashRoute(interaction.commandName, subcommand, group);
|
|
876
877
|
}
|
|
877
878
|
|
|
878
|
-
//#endregion
|
|
879
|
-
//#region src/miscellaneous/FaultThrottle.ts
|
|
880
|
-
/**
|
|
881
|
-
* Drops duplicate fault reports inside a fixed window so a recurring fault (a database outage on a
|
|
882
|
-
* hot path) reports once per window instead of flooding the webhook. Keyed on a structural string, so
|
|
883
|
-
* the map stays small, and a stale entry is evicted on the next read of its key.
|
|
884
|
-
*
|
|
885
|
-
* @internal
|
|
886
|
-
*/
|
|
887
|
-
var FaultThrottle = class {
|
|
888
|
-
windowMs;
|
|
889
|
-
now;
|
|
890
|
-
lastReportedAt = /* @__PURE__ */ new Map();
|
|
891
|
-
constructor(windowMs = 6e4, now = () => Date.now()) {
|
|
892
|
-
this.windowMs = windowMs;
|
|
893
|
-
this.now = now;
|
|
894
|
-
}
|
|
895
|
-
/** Evicts the entry for this key if it has aged out, so the map never holds stale keys forever. */
|
|
896
|
-
shouldReport(key) {
|
|
897
|
-
const last = this.lastReportedAt.get(key);
|
|
898
|
-
if (last === void 0) return true;
|
|
899
|
-
if (this.now() - last < this.windowMs) return false;
|
|
900
|
-
this.lastReportedAt.delete(key);
|
|
901
|
-
return true;
|
|
902
|
-
}
|
|
903
|
-
markReported(key) {
|
|
904
|
-
this.lastReportedAt.set(key, this.now());
|
|
905
|
-
}
|
|
906
|
-
clear() {
|
|
907
|
-
this.lastReportedAt.clear();
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
|
|
911
879
|
//#endregion
|
|
912
880
|
//#region src/miscellaneous/extractErrorResponse.ts
|
|
913
|
-
const logger$2 = new Logger("ErrorsHandling");
|
|
914
|
-
const faultThrottle = new FaultThrottle();
|
|
881
|
+
const logger$2 = new Logger("ErrorsHandling", { channel: "errors" });
|
|
915
882
|
/**
|
|
916
883
|
* Processes an error into a user-facing reply and routes its report.
|
|
917
884
|
*
|
|
@@ -941,29 +908,30 @@ function extractErrorResponse(error, core, origin) {
|
|
|
941
908
|
response: override ? new override(uuid).render(ctx) : new Fault().render(ctx)
|
|
942
909
|
};
|
|
943
910
|
}
|
|
944
|
-
function withThrottle(origin, error, publish) {
|
|
911
|
+
function withThrottle(core, origin, error, publish) {
|
|
912
|
+
const throttle = FaultThrottle.for(core);
|
|
945
913
|
const key = faultKey(origin, error);
|
|
946
|
-
if (!
|
|
947
|
-
logger$2.
|
|
914
|
+
if (!throttle.shouldReport(key)) {
|
|
915
|
+
logger$2.debug(`throttled duplicate fault ${key}`);
|
|
948
916
|
return;
|
|
949
917
|
}
|
|
950
918
|
publish();
|
|
951
|
-
|
|
919
|
+
throttle.markReported(key);
|
|
952
920
|
}
|
|
953
921
|
function reportFault(denial, core, origin, uuid) {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
if (origin.interaction) core.bus
|
|
922
|
+
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
923
|
+
withThrottle(core, origin, denial, () => {
|
|
924
|
+
if (origin.interaction) core.bus[PublishDefault]("handledException", {
|
|
957
925
|
denial,
|
|
958
926
|
uuid,
|
|
959
927
|
source: buildInteractionSource(origin.interaction)
|
|
960
928
|
});
|
|
961
|
-
else if (origin.event) core.bus
|
|
929
|
+
else if (origin.event) core.bus[PublishDefault]("handledException", {
|
|
962
930
|
denial,
|
|
963
931
|
uuid,
|
|
964
932
|
source: buildEventSource(origin.event, origin)
|
|
965
933
|
});
|
|
966
|
-
else core.bus
|
|
934
|
+
else core.bus[PublishDefault]("unknownException", {
|
|
967
935
|
uuid,
|
|
968
936
|
error: denial,
|
|
969
937
|
...scalarActors(origin),
|
|
@@ -978,10 +946,10 @@ function causeLine(error) {
|
|
|
978
946
|
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
979
947
|
}
|
|
980
948
|
function reportRawFault(error, core, origin, uuid) {
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
core.bus
|
|
949
|
+
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
950
|
+
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
951
|
+
withThrottle(core, origin, error, () => {
|
|
952
|
+
core.bus[PublishDefault]("unknownException", {
|
|
985
953
|
uuid,
|
|
986
954
|
error,
|
|
987
955
|
...scalarActors(origin),
|
|
@@ -1149,7 +1117,7 @@ var BaseHandler$1 = class extends BaseHandler {
|
|
|
1149
1117
|
*/
|
|
1150
1118
|
var InteractionMiddleware = class extends BaseHandler$1 {
|
|
1151
1119
|
constructor(event, core) {
|
|
1152
|
-
super(event, core);
|
|
1120
|
+
super(event, core, void 0, "interactions");
|
|
1153
1121
|
}
|
|
1154
1122
|
};
|
|
1155
1123
|
|
|
@@ -1169,8 +1137,8 @@ var RepliableHandler = class extends RepliableHandler$1 {
|
|
|
1169
1137
|
getEvent() {
|
|
1170
1138
|
return this.event;
|
|
1171
1139
|
}
|
|
1172
|
-
buildSender(event,
|
|
1173
|
-
return new ReplySender(event, routeId);
|
|
1140
|
+
buildSender(event, core, routeId) {
|
|
1141
|
+
return new ReplySender(event, routeId, core.bus);
|
|
1174
1142
|
}
|
|
1175
1143
|
};
|
|
1176
1144
|
|
|
@@ -1325,7 +1293,7 @@ var ContextMenuHandler = class extends InteractionHandler {
|
|
|
1325
1293
|
*/
|
|
1326
1294
|
var AutocompleteHandler = class extends BaseHandler$1 {
|
|
1327
1295
|
constructor(event, core, dispatch) {
|
|
1328
|
-
super(event, core, dispatch);
|
|
1296
|
+
super(event, core, dispatch, "interactions");
|
|
1329
1297
|
}
|
|
1330
1298
|
decodedFocused;
|
|
1331
1299
|
/**
|
|
@@ -1376,7 +1344,11 @@ var AutocompleteHandler = class extends BaseHandler$1 {
|
|
|
1376
1344
|
}
|
|
1377
1345
|
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
1378
1346
|
async respond(choices) {
|
|
1379
|
-
|
|
1347
|
+
const routeId = this.dispatch?.routeId ?? "autocomplete";
|
|
1348
|
+
await reportedWrite({
|
|
1349
|
+
bus: this.core.bus,
|
|
1350
|
+
interactionId: this.event.id
|
|
1351
|
+
}, routeId, "respond", () => this.event.respond(choices));
|
|
1380
1352
|
}
|
|
1381
1353
|
/** The firing command route, for a field whose completion differs per registered command. */
|
|
1382
1354
|
get route() {
|
|
@@ -1569,7 +1541,7 @@ var SelectMenuHandler = class extends ComponentHandler {};
|
|
|
1569
1541
|
var EventHandler = class extends BaseHandler$1 {
|
|
1570
1542
|
firedEvent;
|
|
1571
1543
|
constructor(event, core, eventName) {
|
|
1572
|
-
super(event, core);
|
|
1544
|
+
super(event, core, void 0, "events");
|
|
1573
1545
|
this.firedEvent = eventName;
|
|
1574
1546
|
}
|
|
1575
1547
|
/**
|
|
@@ -1622,7 +1594,7 @@ var EventHandler = class extends BaseHandler$1 {
|
|
|
1622
1594
|
var EventMiddleware = class extends BaseHandler$1 {
|
|
1623
1595
|
firedEvent;
|
|
1624
1596
|
constructor(event, core, eventName) {
|
|
1625
|
-
super(event, core);
|
|
1597
|
+
super(event, core, void 0, "events");
|
|
1626
1598
|
this.firedEvent = eventName;
|
|
1627
1599
|
}
|
|
1628
1600
|
/**
|
|
@@ -1807,10 +1779,15 @@ var Paginator = class {
|
|
|
1807
1779
|
}
|
|
1808
1780
|
};
|
|
1809
1781
|
}
|
|
1810
|
-
/**
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1782
|
+
/**
|
|
1783
|
+
* Render page 0 and send it through the handler's sender, picking reply or followUp from its ack state.
|
|
1784
|
+
*
|
|
1785
|
+
* @param handler - The handler starting the paginator, normally `this`.
|
|
1786
|
+
*/
|
|
1787
|
+
async start(handler) {
|
|
1788
|
+
const interaction = handler.getEvent();
|
|
1789
|
+
const response = await this.page(contextOf(interaction, handler.core), 0);
|
|
1790
|
+
return handler.getSender().send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
1814
1791
|
}
|
|
1815
1792
|
/** Render a page as a {@link ReplyResponse}. To post it elsewhere, add `flags: MessageFlags.IsComponentsV2`. */
|
|
1816
1793
|
async page(ctx, n) {
|
|
@@ -1870,250 +1847,32 @@ var CursorSource = class {
|
|
|
1870
1847
|
};
|
|
1871
1848
|
|
|
1872
1849
|
//#endregion
|
|
1873
|
-
//#region src/subscribers/
|
|
1850
|
+
//#region src/subscribers/index.ts
|
|
1874
1851
|
/**
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
1877
|
-
* Provides type-safe access to subscribers data and the core framework instance.
|
|
1878
|
-
* Extend this class to create custom subscriber handlers.
|
|
1852
|
+
* Base class for a gateway subscriber, binding `this.core` to the gateway {@link Core}.
|
|
1879
1853
|
*
|
|
1880
|
-
* @typeParam KeyOfSubscribers - The
|
|
1854
|
+
* @typeParam KeyOfSubscribers - The subscription key this handler receives
|
|
1881
1855
|
*/
|
|
1882
|
-
var Subscriber = class {
|
|
1883
|
-
data;
|
|
1884
|
-
core;
|
|
1885
|
-
logger;
|
|
1886
|
-
/**
|
|
1887
|
-
* Instantiates the subscriber with event data and core.
|
|
1888
|
-
*
|
|
1889
|
-
* @param data - The subscriber event data
|
|
1890
|
-
* @param core - The core framework instance
|
|
1891
|
-
*/
|
|
1892
|
-
constructor(data, core) {
|
|
1893
|
-
this.data = data;
|
|
1894
|
-
this.core = core;
|
|
1895
|
-
this.logger = new Logger(this.constructor.name);
|
|
1896
|
-
}
|
|
1897
|
-
};
|
|
1898
|
-
|
|
1899
|
-
//#endregion
|
|
1900
|
-
//#region src/subscribers/decorators/Subscribe.ts
|
|
1901
|
-
/**
|
|
1902
|
-
* Registers a subscriber handler class with a specific subscriber event.
|
|
1903
|
-
*
|
|
1904
|
-
* Associates the decorated class with a subscriber event type for automatic
|
|
1905
|
-
* registration and execution when the subscriber is emitted.
|
|
1906
|
-
*
|
|
1907
|
-
* @typeParam TSubscriber - The subscriber event name to register for
|
|
1908
|
-
* @param subscriber - The subscriber event name to register for
|
|
1909
|
-
* @param options - Options to configure the subscriber handler registration.
|
|
1910
|
-
* @decorator
|
|
1911
|
-
* @example
|
|
1912
|
-
* ```typescript
|
|
1913
|
-
* \@Subscribe('userJoin')
|
|
1914
|
-
* class WelcomeHandler extends Subscriber<'userJoin'> {
|
|
1915
|
-
* async execute() {
|
|
1916
|
-
* // Handle user join event
|
|
1917
|
-
* }
|
|
1918
|
-
* }
|
|
1919
|
-
* ```
|
|
1920
|
-
* @example
|
|
1921
|
-
* ```ts
|
|
1922
|
-
* \@Subscribe('userJoin', { frequency: 'once' })
|
|
1923
|
-
* // or
|
|
1924
|
-
* \@Subscribe('userJoin')
|
|
1925
|
-
* ```
|
|
1926
|
-
*/
|
|
1927
|
-
function Subscribe(subscriber, options) {
|
|
1928
|
-
return function(constructor) {
|
|
1929
|
-
const meta = {
|
|
1930
|
-
subscriber,
|
|
1931
|
-
frequency: options?.frequency
|
|
1932
|
-
};
|
|
1933
|
-
Reflect.defineMetadata(SubscribeMetadataKey, meta, constructor);
|
|
1934
|
-
};
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
//#endregion
|
|
1938
|
-
//#region src/subscribers/decorators/WebhookUrl.ts
|
|
1856
|
+
var Subscriber = class extends Subscriber$1 {};
|
|
1939
1857
|
/**
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
* An unset variable disables the reporter with a boot warning. A set but malformed value throws
|
|
1943
|
-
* at boot.
|
|
1858
|
+
* Base class for a gateway subscriber that delivers its event to a Discord webhook, binding
|
|
1859
|
+
* `this.core` to the gateway {@link Core}.
|
|
1944
1860
|
*
|
|
1945
|
-
* @
|
|
1946
|
-
* @decorator
|
|
1947
|
-
* @example
|
|
1948
|
-
* ```ts
|
|
1949
|
-
* \@Subscribe('memberBanned')
|
|
1950
|
-
* \@WebhookUrl('BAN_LOG_WEBHOOK_URL')
|
|
1951
|
-
* class BanLog extends WebhookLog<'memberBanned'> {
|
|
1952
|
-
* report() {
|
|
1953
|
-
* return { components: [new BanCard(this.data).component] };
|
|
1954
|
-
* }
|
|
1955
|
-
* }
|
|
1956
|
-
* ```
|
|
1957
|
-
*/
|
|
1958
|
-
function WebhookUrl(envKey) {
|
|
1959
|
-
return function(constructor) {
|
|
1960
|
-
Reflect.defineMetadata(WebhookUrlMetadataKey, envKey, constructor);
|
|
1961
|
-
};
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
//#endregion
|
|
1965
|
-
//#region src/miscellaneous/flagsFor.ts
|
|
1966
|
-
/** @internal */
|
|
1967
|
-
function flagsFor(ephemeral) {
|
|
1968
|
-
let flags = MessageFlags$1.IsComponentsV2;
|
|
1969
|
-
if (ephemeral) flags |= MessageFlags$1.Ephemeral;
|
|
1970
|
-
return flags;
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
//#endregion
|
|
1974
|
-
//#region src/subscribers/bases/webhookHelpers.ts
|
|
1975
|
-
const DISCORD_WEBHOOK_REGEX = new RegExp(String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`);
|
|
1976
|
-
function isDiscordWebhookUrl(value) {
|
|
1977
|
-
return URL.canParse(value) && DISCORD_WEBHOOK_REGEX.test(value);
|
|
1978
|
-
}
|
|
1979
|
-
function jsonAttachment(name, description, data) {
|
|
1980
|
-
const content = filterCirculars(data);
|
|
1981
|
-
return {
|
|
1982
|
-
name,
|
|
1983
|
-
description,
|
|
1984
|
-
data: Buffer.from(JSON.stringify(content, void 0, 2), "utf8")
|
|
1985
|
-
};
|
|
1986
|
-
}
|
|
1987
|
-
function neutralizeFences(text) {
|
|
1988
|
-
return text.replaceAll("```", "```");
|
|
1989
|
-
}
|
|
1990
|
-
/** An error's stack with its direct cause appended (one level), ANSI-stripped for the webhook payload. */
|
|
1991
|
-
function errorReport(error) {
|
|
1992
|
-
let report = error.stack ?? `${error.name}: ${error.message}`;
|
|
1993
|
-
if (Error.isError(error.cause)) {
|
|
1994
|
-
const cause = error.cause.stack ?? `${error.cause.name}: ${error.cause.message}`;
|
|
1995
|
-
report += `\n\nCaused by:\n${cause}`;
|
|
1996
|
-
}
|
|
1997
|
-
const stripped = neutralizeFences(stripAnsi(report));
|
|
1998
|
-
return stripped.length > 1800 ? `${stripped.slice(0, 1799)}…` : stripped;
|
|
1999
|
-
}
|
|
2000
|
-
var WebhookSeparator = class extends BuilderComponent {
|
|
2001
|
-
constructor() {
|
|
2002
|
-
super("separator");
|
|
2003
|
-
this.instance.setSpacing(SeparatorSpacingSize.Small).setDivider(true);
|
|
2004
|
-
}
|
|
2005
|
-
};
|
|
2006
|
-
|
|
2007
|
-
//#endregion
|
|
2008
|
-
//#region src/subscribers/bases/WebhookSender.ts
|
|
2009
|
-
const HTTP_NOT_FOUND = 404;
|
|
2010
|
-
const HTTP_UNAUTHORIZED = 401;
|
|
2011
|
-
/**
|
|
2012
|
-
* Sends component messages to a Discord webhook through `@discordjs/rest`.
|
|
2013
|
-
*
|
|
2014
|
-
* @throws A `SeedcordError` when the url does not carry a webhook id and token.
|
|
1861
|
+
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
2015
1862
|
*/
|
|
2016
|
-
var
|
|
2017
|
-
rest = new REST();
|
|
2018
|
-
route;
|
|
2019
|
-
constructor(url) {
|
|
2020
|
-
const match = /\/webhooks\/(\d+)\/([\w$-]+)$/.exec(url);
|
|
2021
|
-
if (!match?.[1] || !match[2]) throw new SeedcordError(SeedcordErrorCode.ConfigWebhookUrlInvalid, ["The url"]);
|
|
2022
|
-
this.route = Routes.webhook(match[1], match[2]);
|
|
2023
|
-
}
|
|
2024
|
-
/**
|
|
2025
|
-
* Fetches the webhook object, sending no message. Returns `missing` on a 404 or 401 from
|
|
2026
|
-
* Discord and `unreachable` on any other failure.
|
|
2027
|
-
*/
|
|
2028
|
-
async verify() {
|
|
2029
|
-
try {
|
|
2030
|
-
await this.rest.get(this.route, { auth: false });
|
|
2031
|
-
return "ok";
|
|
2032
|
-
} catch (error) {
|
|
2033
|
-
return error instanceof DiscordAPIError$1 && (error.status === HTTP_NOT_FOUND || error.status === HTTP_UNAUTHORIZED) ? "missing" : "unreachable";
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
async send(options) {
|
|
2037
|
-
const { flags, username, components, files } = options;
|
|
2038
|
-
await this.rest.post(this.route, {
|
|
2039
|
-
auth: false,
|
|
2040
|
-
query: new URLSearchParams({
|
|
2041
|
-
with_components: "true",
|
|
2042
|
-
wait: "true"
|
|
2043
|
-
}),
|
|
2044
|
-
body: {
|
|
2045
|
-
flags,
|
|
2046
|
-
username,
|
|
2047
|
-
components: components.map((component) => component.toJSON()),
|
|
2048
|
-
...files?.length && { attachments: files.map((file, id) => ({
|
|
2049
|
-
id,
|
|
2050
|
-
filename: file.name,
|
|
2051
|
-
description: file.description
|
|
2052
|
-
})) }
|
|
2053
|
-
},
|
|
2054
|
-
...files?.length && { files: files.map((file) => ({
|
|
2055
|
-
name: file.name,
|
|
2056
|
-
data: file.data
|
|
2057
|
-
})) }
|
|
2058
|
-
});
|
|
2059
|
-
}
|
|
2060
|
-
};
|
|
1863
|
+
var WebhookLog = class extends WebhookLog$1 {};
|
|
2061
1864
|
|
|
2062
1865
|
//#endregion
|
|
2063
|
-
//#region src/
|
|
1866
|
+
//#region src/plugin.ts
|
|
2064
1867
|
/**
|
|
2065
|
-
* Base class for
|
|
1868
|
+
* Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
|
|
2066
1869
|
*
|
|
2067
|
-
*
|
|
2068
|
-
*
|
|
2069
|
-
* the reporter is skipped at registration with a boot warning.
|
|
1870
|
+
* A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
|
|
1871
|
+
* `this.core` carries the shared members only.
|
|
2070
1872
|
*
|
|
2071
|
-
* @typeParam
|
|
1873
|
+
* @typeParam Opts - The transport, runtime, and capabilities the plugin declares.
|
|
2072
1874
|
*/
|
|
2073
|
-
var
|
|
2074
|
-
static senders = /* @__PURE__ */ new Map();
|
|
2075
|
-
async execute() {
|
|
2076
|
-
const url = WebhookLog.urlOf(WebhookLog.envKeyOf(this.constructor));
|
|
2077
|
-
if (url === null) {
|
|
2078
|
-
this.logger.warn(`${chalk.bold(this.constructor.name)} has no webhook url set, this reporter is disabled`);
|
|
2079
|
-
return;
|
|
2080
|
-
}
|
|
2081
|
-
const { username, components, files } = await this.report();
|
|
2082
|
-
try {
|
|
2083
|
-
await WebhookLog.senderFor(url).send({
|
|
2084
|
-
flags: flagsFor(false),
|
|
2085
|
-
username: username ?? this.constructor.name,
|
|
2086
|
-
components,
|
|
2087
|
-
files
|
|
2088
|
-
});
|
|
2089
|
-
} catch (error) {
|
|
2090
|
-
this.logger.error("Failed to send webhook report", error);
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
/** @internal */
|
|
2094
|
-
static senderFor(url) {
|
|
2095
|
-
let sender = WebhookLog.senders.get(url);
|
|
2096
|
-
if (!sender) {
|
|
2097
|
-
sender = new WebhookSender(url);
|
|
2098
|
-
WebhookLog.senders.set(url, sender);
|
|
2099
|
-
}
|
|
2100
|
-
return sender;
|
|
2101
|
-
}
|
|
2102
|
-
/** @internal */
|
|
2103
|
-
static envKeyOf(ctor) {
|
|
2104
|
-
const envKey = Reflect.getMetadata(WebhookUrlMetadataKey, ctor);
|
|
2105
|
-
if (!envKey) throw new SeedcordError(SeedcordErrorCode.DecoratorWebhookUrlMissing, [ctor.name]);
|
|
2106
|
-
return envKey;
|
|
2107
|
-
}
|
|
2108
|
-
/** @internal Returns null when the env var is unset, throws when set but malformed. */
|
|
2109
|
-
static urlOf(envKey) {
|
|
2110
|
-
if (!Envapter.has(envKey)) return null;
|
|
2111
|
-
const raw = Envapter.getRequired(envKey, Converters.String).trim();
|
|
2112
|
-
if (raw === "") return null;
|
|
2113
|
-
if (!isDiscordWebhookUrl(raw)) throw new SeedcordError(SeedcordErrorCode.ConfigWebhookUrlInvalid, [envKey]);
|
|
2114
|
-
return raw;
|
|
2115
|
-
}
|
|
2116
|
-
};
|
|
1875
|
+
var Plugin = class extends Plugin$1 {};
|
|
2117
1876
|
|
|
2118
1877
|
//#endregion
|
|
2119
1878
|
//#region src/bot/utilities/miscellaneous/contextMenuLeaves.ts
|
|
@@ -2169,7 +1928,7 @@ function slashRouteLeaves(commands) {
|
|
|
2169
1928
|
var CommandRegistry = class {
|
|
2170
1929
|
core;
|
|
2171
1930
|
onDeployed;
|
|
2172
|
-
logger = new Logger("Commands");
|
|
1931
|
+
logger = new Logger("Commands", { channel: "commands" });
|
|
2173
1932
|
isInitialised = false;
|
|
2174
1933
|
globalCommands = [];
|
|
2175
1934
|
guildCommands = new Collection();
|
|
@@ -2256,7 +2015,7 @@ var CommandRegistry = class {
|
|
|
2256
2015
|
this.registerCommand(exported, rel);
|
|
2257
2016
|
this.hmrHandler?.trackHandler(fullPath, exported);
|
|
2258
2017
|
}
|
|
2259
|
-
}
|
|
2018
|
+
});
|
|
2260
2019
|
}
|
|
2261
2020
|
isCommandClass(obj) {
|
|
2262
2021
|
if (typeof obj !== "function") return false;
|
|
@@ -2418,7 +2177,7 @@ function eventGateContext(eventName, args, core) {
|
|
|
2418
2177
|
|
|
2419
2178
|
//#endregion
|
|
2420
2179
|
//#region src/bot/handleEventFault.ts
|
|
2421
|
-
const logger$1 = new Logger("EventBoundary");
|
|
2180
|
+
const logger$1 = new Logger("EventBoundary", { channel: "errors" });
|
|
2422
2181
|
/**
|
|
2423
2182
|
* The event controller boundary. Reports a throw from an event handler and never replies, since a
|
|
2424
2183
|
* generic event has no reply target. A {@link Silence} or a non-reporting {@link Notice} stops quietly, a
|
|
@@ -2431,15 +2190,15 @@ function handleEventFault(caught, eventName, handlerName, args, core) {
|
|
|
2431
2190
|
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger$1.debug(`Silence: ${caught.reason}`);
|
|
2432
2191
|
return;
|
|
2433
2192
|
}
|
|
2434
|
-
|
|
2193
|
+
const error = asError(caught);
|
|
2435
2194
|
const ignore = new Set(core.config.errors?.ignoreEventApiCodes ?? []);
|
|
2436
|
-
if (
|
|
2437
|
-
logger$1.debug(`swallowed api code ${
|
|
2195
|
+
if (error instanceof DiscordAPIError && ignore.has(error.code)) {
|
|
2196
|
+
logger$1.debug(`swallowed api code ${error.code}`);
|
|
2438
2197
|
return;
|
|
2439
2198
|
}
|
|
2440
|
-
if (
|
|
2199
|
+
if (error instanceof Notice && !error.report) return;
|
|
2441
2200
|
const actor = deriveEventActor(args);
|
|
2442
|
-
extractErrorResponse(
|
|
2201
|
+
extractErrorResponse(error, core, {
|
|
2443
2202
|
event: {
|
|
2444
2203
|
name: eventName,
|
|
2445
2204
|
handler: handlerName,
|
|
@@ -2454,15 +2213,15 @@ function handleEventFault(caught, eventName, handlerName, args, core) {
|
|
|
2454
2213
|
//#endregion
|
|
2455
2214
|
//#region src/bot/controllers/EventDispatcher.ts
|
|
2456
2215
|
/**
|
|
2457
|
-
* Scans event handler directories,
|
|
2458
|
-
*
|
|
2459
|
-
*
|
|
2216
|
+
* Scans event handler directories, attaches one client listener per registered event, and runs the
|
|
2217
|
+
* handlers for each fire. Interactions route through InteractionDispatcher. Multiple handlers can
|
|
2218
|
+
* point to one event.
|
|
2460
2219
|
*
|
|
2461
2220
|
* @internal
|
|
2462
2221
|
*/
|
|
2463
2222
|
var EventDispatcher = class {
|
|
2464
2223
|
core;
|
|
2465
|
-
logger = new Logger("Events");
|
|
2224
|
+
logger = new Logger("Events", { channel: "events" });
|
|
2466
2225
|
isInitialized = false;
|
|
2467
2226
|
eventMap = /* @__PURE__ */ new Map();
|
|
2468
2227
|
middlewares = [];
|
|
@@ -2533,7 +2292,7 @@ var EventDispatcher = class {
|
|
|
2533
2292
|
this.registerHandler(val, relativePath);
|
|
2534
2293
|
this.hmrHandler?.trackHandler(fullPath, val);
|
|
2535
2294
|
}
|
|
2536
|
-
}
|
|
2295
|
+
});
|
|
2537
2296
|
}
|
|
2538
2297
|
async loadMiddlewares(dir) {
|
|
2539
2298
|
await traverseDirectory(dir, (fullPath, relativePath, imported) => {
|
|
@@ -2542,7 +2301,7 @@ var EventDispatcher = class {
|
|
|
2542
2301
|
this.registerMiddleware(val, relativePath);
|
|
2543
2302
|
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
2544
2303
|
}
|
|
2545
|
-
}
|
|
2304
|
+
});
|
|
2546
2305
|
}
|
|
2547
2306
|
/** @internal For use in dev mode */
|
|
2548
2307
|
async onHmr(event) {
|
|
@@ -2639,10 +2398,14 @@ var EventDispatcher = class {
|
|
|
2639
2398
|
this.logger.debug(`Attaching ${paint.mint.bold(eventName)} to the client with ${paint.mute(handlerEntries?.length ?? 0)} handler(s)`);
|
|
2640
2399
|
this.core.bot.client.on(eventName, (...args) => {
|
|
2641
2400
|
if (this.draining) return;
|
|
2642
|
-
this.core.
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2401
|
+
this.core.bus[PublishDefault]("anyEvent", {
|
|
2402
|
+
name: eventName,
|
|
2403
|
+
args
|
|
2404
|
+
});
|
|
2405
|
+
const run = this.processEvent(eventName, args).catch((caught) => {
|
|
2406
|
+
const error = asError(caught);
|
|
2407
|
+
this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
2408
|
+
this.core.bus[PublishDefault]("unhandledEventError", { error });
|
|
2646
2409
|
});
|
|
2647
2410
|
this.inFlight.add(run);
|
|
2648
2411
|
run.finally(() => this.inFlight.delete(run));
|
|
@@ -2718,7 +2481,7 @@ const HARMLESS_API_CODES = new Set([
|
|
|
2718
2481
|
|
|
2719
2482
|
//#endregion
|
|
2720
2483
|
//#region src/bot/handleInteractionFault.ts
|
|
2721
|
-
const logger = new Logger("InteractionBoundary");
|
|
2484
|
+
const logger = new Logger("InteractionBoundary", { channel: "errors" });
|
|
2722
2485
|
/**
|
|
2723
2486
|
* The interaction controller boundary. Sorts a throw from any handler-lifecycle phase into the right
|
|
2724
2487
|
* user reply plus the right bus event. When a handler is built its sender is passed in, so the boundary
|
|
@@ -2727,33 +2490,45 @@ const logger = new Logger("InteractionBoundary");
|
|
|
2727
2490
|
*
|
|
2728
2491
|
* @internal
|
|
2729
2492
|
*/
|
|
2730
|
-
async function handleInteractionFault(caught, interaction, core, sender) {
|
|
2493
|
+
async function handleInteractionFault(caught, interaction, core, routeId, sender) {
|
|
2731
2494
|
if (caught instanceof Silence) {
|
|
2732
2495
|
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger.debug(`Silence: ${caught.reason}`);
|
|
2733
2496
|
return;
|
|
2734
2497
|
}
|
|
2735
|
-
|
|
2498
|
+
const error = asError(caught);
|
|
2736
2499
|
const ignore = new Set(core.config.errors?.ignoreApiCodes ?? []);
|
|
2737
|
-
if (
|
|
2738
|
-
logger.debug(`swallowed api code ${
|
|
2500
|
+
if (error instanceof DiscordAPIError && ignore.has(error.code)) {
|
|
2501
|
+
logger.debug(`swallowed api code ${error.code}`);
|
|
2739
2502
|
return;
|
|
2740
2503
|
}
|
|
2741
2504
|
if (interaction.isAutocomplete()) {
|
|
2742
|
-
extractErrorResponse(
|
|
2505
|
+
extractErrorResponse(error, core, {
|
|
2743
2506
|
guild: interaction.guild,
|
|
2744
2507
|
user: interaction.user,
|
|
2745
2508
|
metadata: interaction,
|
|
2746
2509
|
route: slashRouteOf(interaction)
|
|
2747
2510
|
});
|
|
2511
|
+
await sendEmptyChoices(interaction, core, routeId);
|
|
2748
2512
|
return;
|
|
2749
2513
|
}
|
|
2750
|
-
const { response } = extractErrorResponse(
|
|
2514
|
+
const { response } = extractErrorResponse(error, core, {
|
|
2751
2515
|
interaction,
|
|
2752
2516
|
guild: interaction.guild,
|
|
2753
2517
|
user: interaction.user,
|
|
2754
2518
|
metadata: interaction
|
|
2755
2519
|
});
|
|
2756
|
-
await sendGuarded(sender ?? new ReplySender(interaction,
|
|
2520
|
+
await sendGuarded(sender ?? new ReplySender(interaction, routeId, core.bus), response, error instanceof Notice ? error.ephemeral : true);
|
|
2521
|
+
}
|
|
2522
|
+
async function sendEmptyChoices(interaction, core, routeId) {
|
|
2523
|
+
const telemetry = {
|
|
2524
|
+
bus: core.bus,
|
|
2525
|
+
interactionId: interaction.id
|
|
2526
|
+
};
|
|
2527
|
+
try {
|
|
2528
|
+
await reportedWrite(telemetry, routeId, "respond", () => interaction.respond([]));
|
|
2529
|
+
} catch (error) {
|
|
2530
|
+
logger.debug(`autocomplete empty-choices send failed: ${String(error)}`);
|
|
2531
|
+
}
|
|
2757
2532
|
}
|
|
2758
2533
|
async function sendGuarded(sender, response, ephemeral) {
|
|
2759
2534
|
try {
|
|
@@ -2777,7 +2552,7 @@ async function sendGuarded(sender, response, ephemeral) {
|
|
|
2777
2552
|
*/
|
|
2778
2553
|
var InteractionDispatcher = class {
|
|
2779
2554
|
core;
|
|
2780
|
-
logger = new Logger("Interactions");
|
|
2555
|
+
logger = new Logger("Interactions", { channel: "interactions" });
|
|
2781
2556
|
isInitialized = false;
|
|
2782
2557
|
slashMap = /* @__PURE__ */ new Map();
|
|
2783
2558
|
buttonMap = /* @__PURE__ */ new Map();
|
|
@@ -2790,6 +2565,7 @@ var InteractionDispatcher = class {
|
|
|
2790
2565
|
messageContextMenuMap = /* @__PURE__ */ new Map();
|
|
2791
2566
|
userContextMenuMap = /* @__PURE__ */ new Map();
|
|
2792
2567
|
autocompleteMap = /* @__PURE__ */ new Map();
|
|
2568
|
+
handlerFiles = /* @__PURE__ */ new Map();
|
|
2793
2569
|
keysToIgnore = /* @__PURE__ */ new Set();
|
|
2794
2570
|
middlewares = [];
|
|
2795
2571
|
inFlight = /* @__PURE__ */ new Set();
|
|
@@ -2908,7 +2684,7 @@ var InteractionDispatcher = class {
|
|
|
2908
2684
|
this.registerHandler(val, relativePath);
|
|
2909
2685
|
this.hmrHandler?.trackHandler(fullPath, val);
|
|
2910
2686
|
}
|
|
2911
|
-
}
|
|
2687
|
+
});
|
|
2912
2688
|
}
|
|
2913
2689
|
async loadMiddlewares(dir) {
|
|
2914
2690
|
await traverseDirectory(dir, (fullPath, relativePath, imported) => {
|
|
@@ -2917,7 +2693,7 @@ var InteractionDispatcher = class {
|
|
|
2917
2693
|
this.registerMiddleware(val, relativePath);
|
|
2918
2694
|
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
2919
2695
|
}
|
|
2920
|
-
}
|
|
2696
|
+
});
|
|
2921
2697
|
}
|
|
2922
2698
|
registerMiddleware(middlewareCtor, relativePath) {
|
|
2923
2699
|
const metadata = Reflect.getMetadata(MiddlewareMetadataKey, middlewareCtor);
|
|
@@ -2947,6 +2723,7 @@ var InteractionDispatcher = class {
|
|
|
2947
2723
|
return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(MiddlewareMetadataKey, obj);
|
|
2948
2724
|
}
|
|
2949
2725
|
registerHandler(handlerClass, relativePath) {
|
|
2726
|
+
const from = formatFilePath(relativePath);
|
|
2950
2727
|
const writes = [];
|
|
2951
2728
|
for (const [routeType, map] of this.routeTypes) {
|
|
2952
2729
|
const meta = Reflect.getMetadata(InteractionRouteKeys[routeType], handlerClass);
|
|
@@ -2954,16 +2731,16 @@ var InteractionDispatcher = class {
|
|
|
2954
2731
|
for (const route of meta) {
|
|
2955
2732
|
const existing = map.get(route);
|
|
2956
2733
|
if (existing && existing !== handlerClass) throw new SeedcordError(SeedcordErrorCode.InteractionDuplicateRoute, [
|
|
2957
|
-
route
|
|
2958
|
-
existing.name
|
|
2959
|
-
handlerClass.name
|
|
2734
|
+
`${routeType}:${route}`,
|
|
2735
|
+
`${existing.name} (${this.handlerFiles.get(existing) ?? "unknown file"})`,
|
|
2736
|
+
`${handlerClass.name} (${from})`
|
|
2960
2737
|
]);
|
|
2961
2738
|
writes.push([map, route]);
|
|
2962
2739
|
}
|
|
2963
2740
|
}
|
|
2964
2741
|
if (writes.length === 0) return;
|
|
2965
2742
|
for (const [map, route] of writes) map.set(route, handlerClass);
|
|
2966
|
-
|
|
2743
|
+
this.handlerFiles.set(handlerClass, from);
|
|
2967
2744
|
if (this.loading) this.loadedHandlers.push({
|
|
2968
2745
|
name: handlerClass.name,
|
|
2969
2746
|
from
|
|
@@ -2975,6 +2752,7 @@ var InteractionDispatcher = class {
|
|
|
2975
2752
|
await this.hmrHandler?.handle(event);
|
|
2976
2753
|
}
|
|
2977
2754
|
unregisterHandler(handlerClass, artifacts) {
|
|
2755
|
+
this.handlerFiles.delete(handlerClass);
|
|
2978
2756
|
if (artifacts) {
|
|
2979
2757
|
for (const { routeType, routes } of artifacts) {
|
|
2980
2758
|
const map = this.routeTypes.find(([type]) => type === routeType)?.[1];
|
|
@@ -2995,10 +2773,11 @@ var InteractionDispatcher = class {
|
|
|
2995
2773
|
attachToClient() {
|
|
2996
2774
|
this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
|
|
2997
2775
|
if (this.draining) return;
|
|
2998
|
-
this.core.
|
|
2999
|
-
const run = this.handleInteraction(interaction).catch((
|
|
3000
|
-
|
|
3001
|
-
this.
|
|
2776
|
+
this.core.bus[PublishDefault]("anyInteraction", { interaction });
|
|
2777
|
+
const run = this.handleInteraction(interaction).catch((caught) => {
|
|
2778
|
+
const error = asError(caught);
|
|
2779
|
+
this.logger.error(`[${paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
2780
|
+
this.core.bus[PublishDefault]("unhandledInteractionError", { error });
|
|
3002
2781
|
});
|
|
3003
2782
|
this.inFlight.add(run);
|
|
3004
2783
|
run.finally(() => this.inFlight.delete(run));
|
|
@@ -3010,37 +2789,75 @@ var InteractionDispatcher = class {
|
|
|
3010
2789
|
drain(timeoutMs) {
|
|
3011
2790
|
return settleWithin(Promise.allSettled(this.inFlight), timeoutMs);
|
|
3012
2791
|
}
|
|
3013
|
-
async handleCustomIdInteraction(interaction,
|
|
2792
|
+
async handleCustomIdInteraction(interaction, kind, getMap) {
|
|
3014
2793
|
if ([...this.keysToIgnore].some((matcher) => matcher.owns(interaction.customId))) return;
|
|
3015
2794
|
const prefix = prefixOf(interaction.customId);
|
|
3016
|
-
if (!prefix)
|
|
3017
|
-
await this.processInteraction(interaction, () => prefix, (key) => getMap().get(key));
|
|
2795
|
+
if (!prefix) this.logger.warn(`${paint.sky.bold(kind)} has invalid customId: ${paint.mute(interaction.customId)}`);
|
|
2796
|
+
await this.processInteraction(interaction, kind, () => prefix, (key) => getMap().get(key));
|
|
3018
2797
|
}
|
|
3019
|
-
async processInteraction(interaction, extractKey, getHandler, fallback = UnhandledRepliable) {
|
|
2798
|
+
async processInteraction(interaction, kind, extractKey, getHandler, fallback = UnhandledRepliable) {
|
|
3020
2799
|
const key = extractKey(interaction);
|
|
2800
|
+
const startedAt = performance.now();
|
|
2801
|
+
const queuedMs = queuedMsFor(interaction.id);
|
|
2802
|
+
const matched = getHandler(key);
|
|
2803
|
+
let routeId = `${kind}:${key || "unrouted"}`;
|
|
2804
|
+
let reported = false;
|
|
2805
|
+
const report = (outcome) => {
|
|
2806
|
+
if (reported) return;
|
|
2807
|
+
reported = true;
|
|
2808
|
+
this.core.bus[PublishDefault]("interactionDispatched", dispatchedPayload({
|
|
2809
|
+
routeId,
|
|
2810
|
+
interactionId: interaction.id,
|
|
2811
|
+
kind,
|
|
2812
|
+
outcome,
|
|
2813
|
+
startedAt,
|
|
2814
|
+
fallback: !matched,
|
|
2815
|
+
queuedMs
|
|
2816
|
+
}));
|
|
2817
|
+
};
|
|
3021
2818
|
let sender;
|
|
3022
2819
|
try {
|
|
3023
|
-
if (!interaction.isAutocomplete())
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
}
|
|
3029
|
-
this.logger.debug(`Processing ${paint.mint.bold(key)} with ${paint.mute(HandlerCtor.name)}`);
|
|
3030
|
-
const dispatch = new DispatchContext(routeIdOf(HandlerCtor));
|
|
3031
|
-
const handler = new HandlerCtor(interaction, this.core, dispatch);
|
|
2820
|
+
if (!interaction.isAutocomplete()) await this.runMiddlewares(interaction);
|
|
2821
|
+
const HandlerCtor = matched ?? fallback;
|
|
2822
|
+
const dispatch = new DispatchContext(routeIdOf(HandlerCtor) ?? routeId);
|
|
2823
|
+
routeId = dispatch.routeId ?? routeId;
|
|
2824
|
+
const handler = this.buildHandler(HandlerCtor, interaction, dispatch, key, !matched);
|
|
3032
2825
|
if (handler instanceof RepliableHandler) sender = handler.getSender();
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
} finally {
|
|
3038
|
-
monitor?.report(dispatch.routeId);
|
|
3039
|
-
}
|
|
2826
|
+
const refusal = interaction.isAutocomplete() ? null : await this.gateRefusal(HandlerCtor, interaction, dispatch.routeId);
|
|
2827
|
+
if (refusal) {
|
|
2828
|
+
await this.answer(refusal.caught, interaction, routeId, sender, report);
|
|
2829
|
+
return;
|
|
3040
2830
|
}
|
|
3041
2831
|
await handler.execute();
|
|
2832
|
+
report("handled");
|
|
3042
2833
|
} catch (caught) {
|
|
3043
|
-
await
|
|
2834
|
+
await this.answer(caught, interaction, routeId, sender, report);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
async answer(caught, interaction, routeId, sender, report) {
|
|
2838
|
+
try {
|
|
2839
|
+
await handleInteractionFault(caught, interaction, this.core, routeId, sender);
|
|
2840
|
+
} finally {
|
|
2841
|
+
report(outcomeFor(caught));
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
buildHandler(HandlerCtor, interaction, dispatch, key, isFallback) {
|
|
2845
|
+
if (isFallback) this.logger.warn(`No handler found for key ${paint.sky.bold(key)}. Falling back to ${HandlerCtor.name}.`);
|
|
2846
|
+
this.logger.debug(`Processing ${paint.mint.bold(key)} with ${paint.mute(HandlerCtor.name)}`);
|
|
2847
|
+
return new HandlerCtor(interaction, this.core, dispatch);
|
|
2848
|
+
}
|
|
2849
|
+
async runMiddlewares(interaction) {
|
|
2850
|
+
for (const { ctor } of this.middlewares) await new ctor(interaction, this.core).execute();
|
|
2851
|
+
}
|
|
2852
|
+
async gateRefusal(HandlerCtor, interaction, routeId) {
|
|
2853
|
+
const monitor = slowGateMonitor();
|
|
2854
|
+
try {
|
|
2855
|
+
await runHandlerGates(HandlerCtor, interactionGateContext(interaction, this.core), routeId ?? void 0, monitor?.observe);
|
|
2856
|
+
return null;
|
|
2857
|
+
} catch (caught) {
|
|
2858
|
+
return { caught };
|
|
2859
|
+
} finally {
|
|
2860
|
+
monitor?.report(routeId);
|
|
3044
2861
|
}
|
|
3045
2862
|
}
|
|
3046
2863
|
async handleInteraction(interaction) {
|
|
@@ -3085,47 +2902,41 @@ var InteractionDispatcher = class {
|
|
|
3085
2902
|
}
|
|
3086
2903
|
async handleSlashCommand(interaction) {
|
|
3087
2904
|
const route = slashRouteOf(interaction);
|
|
3088
|
-
await this.processInteraction(interaction, () => route, (key) => this.slashMap.get(key));
|
|
2905
|
+
await this.processInteraction(interaction, InteractionRoutes.Slash, () => route, (key) => this.slashMap.get(key));
|
|
3089
2906
|
}
|
|
3090
2907
|
async handleButton(interaction) {
|
|
3091
|
-
await this.handleCustomIdInteraction(interaction, () => this.buttonMap
|
|
2908
|
+
await this.handleCustomIdInteraction(interaction, InteractionRoutes.Button, () => this.buttonMap);
|
|
3092
2909
|
}
|
|
3093
2910
|
async handleModal(interaction) {
|
|
3094
|
-
await this.handleCustomIdInteraction(interaction, () => this.modalMap
|
|
2911
|
+
await this.handleCustomIdInteraction(interaction, InteractionRoutes.Modal, () => this.modalMap);
|
|
3095
2912
|
}
|
|
3096
2913
|
async handleStringSelectMenu(interaction) {
|
|
3097
|
-
await this.handleCustomIdInteraction(interaction, () => this.stringSelectMap
|
|
2914
|
+
await this.handleCustomIdInteraction(interaction, InteractionRoutes.StringMenu, () => this.stringSelectMap);
|
|
3098
2915
|
}
|
|
3099
2916
|
async handleUserSelectMenu(interaction) {
|
|
3100
|
-
await this.handleCustomIdInteraction(interaction, () => this.userSelectMap
|
|
2917
|
+
await this.handleCustomIdInteraction(interaction, InteractionRoutes.UserMenu, () => this.userSelectMap);
|
|
3101
2918
|
}
|
|
3102
2919
|
async handleRoleSelectMenu(interaction) {
|
|
3103
|
-
await this.handleCustomIdInteraction(interaction, () => this.roleSelectMap
|
|
2920
|
+
await this.handleCustomIdInteraction(interaction, InteractionRoutes.RoleMenu, () => this.roleSelectMap);
|
|
3104
2921
|
}
|
|
3105
2922
|
async handleChannelSelectMenu(interaction) {
|
|
3106
|
-
await this.handleCustomIdInteraction(interaction, () => this.channelSelectMap
|
|
2923
|
+
await this.handleCustomIdInteraction(interaction, InteractionRoutes.ChannelMenu, () => this.channelSelectMap);
|
|
3107
2924
|
}
|
|
3108
2925
|
async handleMentionableSelectMenu(interaction) {
|
|
3109
|
-
await this.handleCustomIdInteraction(interaction, () => this.mentionableSelectMap
|
|
2926
|
+
await this.handleCustomIdInteraction(interaction, InteractionRoutes.MentionableMenu, () => this.mentionableSelectMap);
|
|
3110
2927
|
}
|
|
3111
2928
|
async handleMessageContextMenu(interaction) {
|
|
3112
|
-
await this.processInteraction(interaction, () => interaction.commandName, (key) => this.messageContextMenuMap.get(key));
|
|
2929
|
+
await this.processInteraction(interaction, InteractionRoutes.MessageContextMenu, () => interaction.commandName, (key) => this.messageContextMenuMap.get(key));
|
|
3113
2930
|
}
|
|
3114
2931
|
async handleUserContextMenu(interaction) {
|
|
3115
|
-
await this.processInteraction(interaction, () => interaction.commandName, (key) => this.userContextMenuMap.get(key));
|
|
2932
|
+
await this.processInteraction(interaction, InteractionRoutes.UserContextMenu, () => interaction.commandName, (key) => this.userContextMenuMap.get(key));
|
|
3116
2933
|
}
|
|
3117
2934
|
async handleAutocomplete(interaction) {
|
|
3118
2935
|
const route = slashRouteOf(interaction);
|
|
3119
|
-
await this.processInteraction(interaction, () => route, (key) => this.autocompleteMap.get(key), UnhandledAutocomplete);
|
|
2936
|
+
await this.processInteraction(interaction, InteractionRoutes.Autocomplete, () => route, (key) => this.autocompleteMap.get(key), UnhandledAutocomplete);
|
|
3120
2937
|
}
|
|
3121
2938
|
};
|
|
3122
2939
|
|
|
3123
|
-
//#endregion
|
|
3124
|
-
//#region \0@oxc-project+runtime@0.135.0/helpers/esm/decorateMetadata.js
|
|
3125
|
-
function __decorateMetadata(k, v) {
|
|
3126
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
3127
|
-
}
|
|
3128
|
-
|
|
3129
2940
|
//#endregion
|
|
3130
2941
|
//#region \0@oxc-project+runtime@0.135.0/helpers/esm/decorate.js
|
|
3131
2942
|
function __decorate(decorators, target, key, desc) {
|
|
@@ -3144,9 +2955,9 @@ const LOGOUT_TIMEOUT_MS = 2e3;
|
|
|
3144
2955
|
/**
|
|
3145
2956
|
* The Discord client and its controllers. Access it through `core.bot`.
|
|
3146
2957
|
*/
|
|
3147
|
-
var Bot = class
|
|
2958
|
+
var Bot = class {
|
|
3148
2959
|
/** @internal */
|
|
3149
|
-
logger = new Logger("Bot");
|
|
2960
|
+
logger = new Logger("Bot", { channel: "bot" });
|
|
3150
2961
|
isInitialized = false;
|
|
3151
2962
|
_client;
|
|
3152
2963
|
interactions;
|
|
@@ -3163,7 +2974,6 @@ var Bot = class extends TypedEventEmitter {
|
|
|
3163
2974
|
}
|
|
3164
2975
|
/** @internal */
|
|
3165
2976
|
constructor(core) {
|
|
3166
|
-
super();
|
|
3167
2977
|
this._client = new Client(core.config.bot.clientOptions);
|
|
3168
2978
|
if (core.config.bot.interactions.path) this.interactions = new InteractionDispatcher(core);
|
|
3169
2979
|
if (core.config.bot.events.path) this.events = new EventDispatcher(core);
|
|
@@ -3213,9 +3023,10 @@ var Bot = class extends TypedEventEmitter {
|
|
|
3213
3023
|
await this.logout();
|
|
3214
3024
|
}
|
|
3215
3025
|
async login(token) {
|
|
3216
|
-
|
|
3026
|
+
const ready = Promise.withResolvers();
|
|
3027
|
+
this._client.once(Events.ClientReady, () => ready.resolve());
|
|
3217
3028
|
this._client.login(token);
|
|
3218
|
-
await
|
|
3029
|
+
await ready.promise;
|
|
3219
3030
|
this.logger.info(`Logged in as ${chalk.bold.magenta(this._client.user?.username)}!`);
|
|
3220
3031
|
return this;
|
|
3221
3032
|
}
|
|
@@ -3226,244 +3037,13 @@ var Bot = class extends TypedEventEmitter {
|
|
|
3226
3037
|
get client() {
|
|
3227
3038
|
return this._client;
|
|
3228
3039
|
}
|
|
3229
|
-
/**
|
|
3230
|
-
* The overloads enforce the key/args correlation at compile time only. At runtime this forwards
|
|
3231
|
-
* straight to the underlying TypedEventEmitter.
|
|
3232
|
-
*
|
|
3233
|
-
* @internal
|
|
3234
|
-
*/
|
|
3235
|
-
emit(event, ...args) {
|
|
3236
|
-
return super.emit(event, ...args);
|
|
3237
|
-
}
|
|
3238
|
-
emitSafe(event, ...args) {
|
|
3239
|
-
return super.emitSafe(event, ...args);
|
|
3240
|
-
}
|
|
3241
|
-
onListenerError(error, event) {
|
|
3242
|
-
this.logger.error(`listener for ${String(event)} threw`, error);
|
|
3243
|
-
}
|
|
3244
|
-
};
|
|
3245
|
-
__decorate([Envapt("DISCORD_BOT_TOKEN", { converter: (raw) => validateDiscordToken(raw) }), __decorateMetadata("design:type", String)], Bot.prototype, "botToken", void 0);
|
|
3246
|
-
|
|
3247
|
-
//#endregion
|
|
3248
|
-
//#region src/subscribers/default/HandledException.ts
|
|
3249
|
-
let HandledException = class HandledException extends WebhookLog {
|
|
3250
|
-
report() {
|
|
3251
|
-
return {
|
|
3252
|
-
username: "Handled Exception",
|
|
3253
|
-
components: [new HandledExceptionContainer(this.data).component],
|
|
3254
|
-
files: [jsonAttachment("source.json", "The raw source the fault came from", this.data.source.raw)]
|
|
3255
|
-
};
|
|
3256
|
-
}
|
|
3257
|
-
};
|
|
3258
|
-
HandledException = __decorate([Subscribe("handledException"), WebhookUrl("HANDLED_EXCEPTION_WEBHOOK_URL")], HandledException);
|
|
3259
|
-
var HandledExceptionContainer = class extends BuilderComponent {
|
|
3260
|
-
constructor(data) {
|
|
3261
|
-
super("container");
|
|
3262
|
-
const { denial, uuid, source } = data;
|
|
3263
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(faultSummary(denial, source))).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${neutralizeFences(causeStack(denial))}\`\`\``)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Source")).addFileComponents((file) => file.setURL("attachment://source.json"));
|
|
3264
|
-
}
|
|
3265
|
-
};
|
|
3266
|
-
function faultSummary(denial, source) {
|
|
3267
|
-
const head = stripAnsi(`### A handled fault was reported: \`${denial.name}\`\n**Message:** ${denial.message}\n`);
|
|
3268
|
-
if (source.kind === "event") return `${head}**Event:** ${source.eventName}\n**Handler:** ${source.handler}\n**User ID:** \`${source.userId ?? "n/a"}\`\n**Guild ID:** \`${source.guildId ?? "n/a"}\`\n**Channel ID:** \`${source.channelId ?? "n/a"}\``;
|
|
3269
|
-
return `${head}**Kind:** ${source.interactionKind}\n**Command:** ${source.command ?? "n/a"}\n**CustomId:** ${source.customId ?? "n/a"}\n**User ID:** \`${source.userId}\`\n**Guild ID:** \`${source.guildId ?? "n/a"}\`\n**Channel ID:** \`${source.channelId ?? "n/a"}\`\n**Interaction ID:** \`${source.interactionId}\``;
|
|
3270
|
-
}
|
|
3271
|
-
function causeStack(denial) {
|
|
3272
|
-
const { cause } = denial;
|
|
3273
|
-
if (Error.isError(cause)) return stripAnsi(cause.stack ?? cause.message);
|
|
3274
|
-
if (typeof cause === "string") return stripAnsi(cause);
|
|
3275
|
-
if (typeof cause === "bigint" || typeof cause === "symbol" || typeof cause === "function") return cause.toString();
|
|
3276
|
-
if (cause !== void 0) try {
|
|
3277
|
-
return JSON.stringify(cause);
|
|
3278
|
-
} catch {
|
|
3279
|
-
return "[unserializable cause]";
|
|
3280
|
-
}
|
|
3281
|
-
return stripAnsi(denial.stack ?? "No cause recorded.");
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
//#endregion
|
|
3285
|
-
//#region src/subscribers/default/UnknownException.ts
|
|
3286
|
-
let UnknownException = class UnknownException extends WebhookLog {
|
|
3287
|
-
report() {
|
|
3288
|
-
const { metadata } = this.data;
|
|
3289
|
-
return {
|
|
3290
|
-
username: "Unknown Exception",
|
|
3291
|
-
components: [new UnhandledErrorContainer(this.data).component],
|
|
3292
|
-
files: metadata ? [jsonAttachment("metadata.json", "Metadata associated with the error", metadata)] : void 0
|
|
3293
|
-
};
|
|
3294
|
-
}
|
|
3295
|
-
};
|
|
3296
|
-
UnknownException = __decorate([Subscribe("unknownException"), WebhookUrl("UNKNOWN_EXCEPTION_WEBHOOK_URL")], UnknownException);
|
|
3297
|
-
var UnhandledErrorContainer = class extends BuilderComponent {
|
|
3298
|
-
constructor(data) {
|
|
3299
|
-
super("container");
|
|
3300
|
-
const { uuid, error, guild, user, metadata } = data;
|
|
3301
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(`### An unknown exception was thrown\n**Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`\n**Guild Name:** ${guild?.name ?? "Not used in a guild"}\n**User ID:** \`${user?.id ?? "Missing user info"}\`\n**Username:** ${user?.username ?? "Missing user info"}\n`)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${errorReport(error)}\`\`\``));
|
|
3302
|
-
this.addTimestampsIfAvailable(error);
|
|
3303
|
-
this.addMetadataIfAvailable(metadata);
|
|
3304
|
-
}
|
|
3305
|
-
addTimestampsIfAvailable(error) {
|
|
3306
|
-
if (!(error instanceof DiscordAPIError$1)) return;
|
|
3307
|
-
const now = Date.now();
|
|
3308
|
-
const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
|
|
3309
|
-
if (!snowflake) return void 0;
|
|
3310
|
-
const interactionTs = timestampFromSnowflake(snowflake);
|
|
3311
|
-
const diff = now - interactionTs;
|
|
3312
|
-
const seconds = Math.floor(diff / 1e3);
|
|
3313
|
-
const millis = diff % 1e3;
|
|
3314
|
-
this.instance.addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### Timestamps\n- **\`Interaction sent\` :** ${new Date(interactionTs).toISOString()} (${interactionTs})\n- **\`Error logged \` :** ${new Date(now).toISOString()} (${now})\n- **\`Offset \` :** ${seconds}s ${millis}ms`));
|
|
3315
|
-
}
|
|
3316
|
-
addMetadataIfAvailable(metadata) {
|
|
3317
|
-
if (!metadata) return;
|
|
3318
|
-
this.instance.addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Metadata")).addFileComponents((file) => file.setURL("attachment://metadata.json"));
|
|
3319
|
-
}
|
|
3320
|
-
};
|
|
3321
|
-
|
|
3322
|
-
//#endregion
|
|
3323
|
-
//#region src/subscribers/Bus.ts
|
|
3324
|
-
/**
|
|
3325
|
-
* Registers and dispatches subscribers. Subscribers load from the configured directories and run
|
|
3326
|
-
* on a programmatic or framework-event publish. Accessed via `core.bus`. Do not construct it directly.
|
|
3327
|
-
*/
|
|
3328
|
-
var Bus = class extends TypedEventEmitter {
|
|
3329
|
-
core;
|
|
3330
|
-
logger = new Logger("Subscribers");
|
|
3331
|
-
isInitialized = false;
|
|
3332
|
-
subscribersMap = /* @__PURE__ */ new Map();
|
|
3333
|
-
executedOnceHandlers = /* @__PURE__ */ new Set();
|
|
3334
|
-
webhookProbes = /* @__PURE__ */ new Map();
|
|
3335
|
-
hmrHandler;
|
|
3336
|
-
constructor(core) {
|
|
3337
|
-
super();
|
|
3338
|
-
this.core = core;
|
|
3339
|
-
if (this.core.config.subscribers.path) {
|
|
3340
|
-
if (!Envapter.isDevelopment && !Envapter.isTest) return;
|
|
3341
|
-
this.hmrHandler = new HmrModuleHandler({
|
|
3342
|
-
handlersDir: this.core.config.subscribers.path,
|
|
3343
|
-
isHandler: this.isSubscriber.bind(this),
|
|
3344
|
-
registerHandler: this.registerSubscriber.bind(this),
|
|
3345
|
-
unregisterHandler: this.unregisterSubscriber.bind(this),
|
|
3346
|
-
getArtifacts: this.getArtifacts.bind(this),
|
|
3347
|
-
logger: this.logger
|
|
3348
|
-
});
|
|
3349
|
-
}
|
|
3350
|
-
}
|
|
3351
|
-
getArtifacts(ctor) {
|
|
3352
|
-
return [Reflect.getMetadata(SubscribeMetadataKey, ctor).subscriber];
|
|
3353
|
-
}
|
|
3354
|
-
/** @internal */
|
|
3355
|
-
async init() {
|
|
3356
|
-
if (this.isInitialized) return;
|
|
3357
|
-
this.isInitialized = true;
|
|
3358
|
-
this.registerSubscriber(UnknownException);
|
|
3359
|
-
this.registerSubscriber(HandledException);
|
|
3360
|
-
const subscribersDir = this.core.config.subscribers.path;
|
|
3361
|
-
if (subscribersDir) {
|
|
3362
|
-
this.logger.info(chalk.bold(subscribersDir));
|
|
3363
|
-
await this.loadSubscribers(subscribersDir);
|
|
3364
|
-
const totalSubscribers = [...this.subscribersMap.values()].reduce((acc, handlers) => acc + handlers.length, 0);
|
|
3365
|
-
this.logger.utils.list([`${chalk.bold.magenta(totalSubscribers)} subscribers`], chalk.bold.green("Loaded"));
|
|
3366
|
-
}
|
|
3367
|
-
if (!Envapter.isTest) await this.verifyWebhooks();
|
|
3368
|
-
}
|
|
3369
|
-
/** @internal Exposed for tests. */
|
|
3370
|
-
async verifyWebhooks() {
|
|
3371
|
-
const missing = [];
|
|
3372
|
-
await Promise.all([...this.webhookProbes].map(async ([url, envKeys]) => {
|
|
3373
|
-
const result = await WebhookLog.senderFor(url).verify();
|
|
3374
|
-
if (result === "missing") missing.push(...envKeys);
|
|
3375
|
-
else if (result === "unreachable") this.logger.warn(`could not verify webhook at ${paint.sky.bold(envKeys.join(", "))}`);
|
|
3376
|
-
}));
|
|
3377
|
-
if (missing.length > 0) throw new SeedcordError(SeedcordErrorCode.ConfigWebhookNotFound, [missing.join(", ")]);
|
|
3378
|
-
}
|
|
3379
|
-
/** @internal */
|
|
3380
|
-
async onHmr(event) {
|
|
3381
|
-
if (this.hmrHandler) await this.hmrHandler.handle(event);
|
|
3382
|
-
}
|
|
3383
|
-
async loadSubscribers(dir) {
|
|
3384
|
-
await traverseDirectory(dir, (fullPath, relativePath, imported) => {
|
|
3385
|
-
for (const exportName of Object.keys(imported)) {
|
|
3386
|
-
const val = imported[exportName];
|
|
3387
|
-
if (this.isSubscriber(val)) {
|
|
3388
|
-
this.registerSubscriber(val);
|
|
3389
|
-
this.hmrHandler?.trackHandler(fullPath, val);
|
|
3390
|
-
this.logger.utils.registration(val.name, relativePath);
|
|
3391
|
-
}
|
|
3392
|
-
}
|
|
3393
|
-
}, this.logger);
|
|
3394
|
-
}
|
|
3395
|
-
registerSubscriber(handler) {
|
|
3396
|
-
if (handler.prototype instanceof WebhookLog) {
|
|
3397
|
-
const envKey = WebhookLog.envKeyOf(handler);
|
|
3398
|
-
const url = WebhookLog.urlOf(envKey);
|
|
3399
|
-
if (url === null) {
|
|
3400
|
-
this.logger.warn(`${chalk.bold(handler.name)} disabled, ${paint.sky.bold(envKey)} is not set`);
|
|
3401
|
-
return;
|
|
3402
|
-
}
|
|
3403
|
-
const envKeys = this.webhookProbes.get(url) ?? [];
|
|
3404
|
-
if (!envKeys.includes(envKey)) envKeys.push(envKey);
|
|
3405
|
-
this.webhookProbes.set(url, envKeys);
|
|
3406
|
-
}
|
|
3407
|
-
const options = Reflect.getMetadata(SubscribeMetadataKey, handler);
|
|
3408
|
-
let handlers = this.subscribersMap.get(options.subscriber);
|
|
3409
|
-
if (!handlers) {
|
|
3410
|
-
handlers = [];
|
|
3411
|
-
this.subscribersMap.set(options.subscriber, handlers);
|
|
3412
|
-
}
|
|
3413
|
-
handlers.push({
|
|
3414
|
-
ctor: handler,
|
|
3415
|
-
frequency: options.frequency ?? "on"
|
|
3416
|
-
});
|
|
3417
|
-
}
|
|
3418
|
-
unregisterSubscriber(handler, artifacts) {
|
|
3419
|
-
const subscribers = artifacts ?? [...this.subscribersMap.keys()];
|
|
3420
|
-
for (const subscriber of subscribers) {
|
|
3421
|
-
const handlers = this.subscribersMap.get(subscriber);
|
|
3422
|
-
if (!handlers) continue;
|
|
3423
|
-
const index = handlers.findIndex((entry) => entry.ctor === handler);
|
|
3424
|
-
if (index !== -1) handlers.splice(index, 1);
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
isSubscriber(obj) {
|
|
3428
|
-
if (typeof obj !== "function") return false;
|
|
3429
|
-
return obj.prototype instanceof Subscriber && Reflect.hasMetadata(SubscribeMetadataKey, obj);
|
|
3430
|
-
}
|
|
3431
|
-
/**
|
|
3432
|
-
* Publishes an event to its subscribers and native listeners.
|
|
3433
|
-
*
|
|
3434
|
-
* Fire-and-forget, so subscriber handlers run asynchronously and this returns before they
|
|
3435
|
-
* complete, and callers must not assume side effects have landed. Errors thrown by a subscriber
|
|
3436
|
-
* are caught and logged, never surfaced here. A `'once'` subscriber is marked as executed when
|
|
3437
|
-
* it starts (even if it throws), so it never runs twice.
|
|
3438
|
-
*
|
|
3439
|
-
* @param event - The subscription key to publish
|
|
3440
|
-
* @param data - Payload passed to each subscriber
|
|
3441
|
-
* @returns Whether the underlying emitter had native listeners for the event
|
|
3442
|
-
*/
|
|
3443
|
-
publish(event, data) {
|
|
3444
|
-
this.processSubscriber(event, data);
|
|
3445
|
-
return super.emit(event, ...[data]);
|
|
3446
|
-
}
|
|
3447
|
-
async processSubscriber(subscriberName, data) {
|
|
3448
|
-
const handlers = this.subscribersMap.get(subscriberName);
|
|
3449
|
-
if (!handlers) return;
|
|
3450
|
-
for (const entry of handlers) {
|
|
3451
|
-
if (entry.frequency === "once" && this.executedOnceHandlers.has(entry.ctor)) continue;
|
|
3452
|
-
try {
|
|
3453
|
-
if (entry.frequency === "once") this.executedOnceHandlers.add(entry.ctor);
|
|
3454
|
-
const Ctor = entry.ctor;
|
|
3455
|
-
await new Ctor(data, this.core).execute();
|
|
3456
|
-
} catch (err) {
|
|
3457
|
-
this.logger.error(`Error in subscriber ${String(subscriberName)} handler ${entry.ctor.name}:`, err);
|
|
3458
|
-
}
|
|
3459
|
-
}
|
|
3460
|
-
}
|
|
3461
3040
|
};
|
|
3041
|
+
__decorate([Envapt("DISCORD_BOT_TOKEN", { converter: (raw) => validateDiscordToken(raw) })], Bot.prototype, "botToken", void 0);
|
|
3462
3042
|
|
|
3463
3043
|
//#endregion
|
|
3464
3044
|
//#region src/version.ts
|
|
3465
3045
|
/** Package version */
|
|
3466
|
-
const version = "0.1.0-next.
|
|
3046
|
+
const version = "0.1.0-next.5";
|
|
3467
3047
|
|
|
3468
3048
|
//#endregion
|
|
3469
3049
|
//#region src/Seedcord.ts
|
|
@@ -3481,12 +3061,9 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3481
3061
|
version = version;
|
|
3482
3062
|
healthCheck;
|
|
3483
3063
|
hmrManager;
|
|
3484
|
-
/** @see {@link CoordinatedShutdown} */
|
|
3485
|
-
shutdown;
|
|
3486
|
-
/** @see {@link CoordinatedStartup} */
|
|
3487
|
-
startup;
|
|
3488
3064
|
/** @see {@link Bus} */
|
|
3489
3065
|
bus;
|
|
3066
|
+
subscribers;
|
|
3490
3067
|
/** @see {@link Bot} */
|
|
3491
3068
|
bot;
|
|
3492
3069
|
/** @see {@link IRateLimiter} */
|
|
@@ -3496,17 +3073,14 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3496
3073
|
* @throws A **SeedcordError** When attempting to create multiple instances (singleton)
|
|
3497
3074
|
*/
|
|
3498
3075
|
constructor(config) {
|
|
3499
|
-
|
|
3500
|
-
const startup = new CoordinatedStartup();
|
|
3501
|
-
super(shutdown, startup);
|
|
3076
|
+
super(new CoordinatedShutdown(), new CoordinatedStartup());
|
|
3502
3077
|
this.config = config;
|
|
3503
3078
|
installNodeDefaults(config.logger);
|
|
3504
3079
|
setBotColor(config.botColor);
|
|
3505
|
-
this.shutdown = shutdown;
|
|
3506
|
-
this.startup = startup;
|
|
3507
3080
|
this.hmrManager = new HmrManager();
|
|
3508
3081
|
this.hmrManager.init();
|
|
3509
3082
|
this.bus = new Bus(this);
|
|
3083
|
+
this.subscribers = new SubscriberLoader(this.bus, config.subscribers.path);
|
|
3510
3084
|
this.bot = new Bot(this);
|
|
3511
3085
|
this.rateLimiter = config.store ?? new MemoryRateLimiter();
|
|
3512
3086
|
this.healthCheck = HealthCheck.fromOption(this.shutdown, config.healthCheck);
|
|
@@ -3529,18 +3103,18 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3529
3103
|
}
|
|
3530
3104
|
registerStartupTasks() {
|
|
3531
3105
|
if (Envapter.isDevelopment || Envapter.isTest) this.registerHmrAwareModules();
|
|
3532
|
-
this.startup.addTask(StartupPhase.Configuration, "
|
|
3106
|
+
this.startup.addTask(StartupPhase.Configuration, "bus-initialization", async () => {
|
|
3533
3107
|
this.bus.logger.utils.initialization("Subscribers", "start");
|
|
3534
|
-
await this.
|
|
3108
|
+
await this.subscribers.init();
|
|
3535
3109
|
this.bus.logger.utils.initialization("Subscribers", "end");
|
|
3536
3110
|
});
|
|
3537
|
-
this.startup.addTask(StartupPhase.Login, "
|
|
3111
|
+
this.startup.addTask(StartupPhase.Login, "bot-initialization", async () => {
|
|
3538
3112
|
this.bot.logger.utils.initialization("Bot", "start");
|
|
3539
3113
|
await this.bot.init();
|
|
3540
3114
|
this.bot.logger.utils.initialization("Bot", "end");
|
|
3541
3115
|
});
|
|
3542
3116
|
const { healthCheck } = this;
|
|
3543
|
-
if (healthCheck) this.startup.addTask(StartupPhase.Ready, "
|
|
3117
|
+
if (healthCheck) this.startup.addTask(StartupPhase.Ready, "health-check", async () => {
|
|
3544
3118
|
healthCheck.logger.utils.initialization("HealthCheck", "start");
|
|
3545
3119
|
await healthCheck.init();
|
|
3546
3120
|
healthCheck.logger.utils.initialization("HealthCheck", "end");
|
|
@@ -3562,9 +3136,9 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3562
3136
|
return this;
|
|
3563
3137
|
}
|
|
3564
3138
|
registerHmrAwareModules() {
|
|
3565
|
-
this.startup.addTask(StartupPhase.Configuration, "
|
|
3139
|
+
this.startup.addTask(StartupPhase.Configuration, "hmr-registration", async () => {
|
|
3566
3140
|
this.hmrManager.register(this.bot);
|
|
3567
|
-
this.hmrManager.register(this.
|
|
3141
|
+
this.hmrManager.register(this.subscribers);
|
|
3568
3142
|
for (const plugin of this.plugins) this.hmrManager.register(plugin);
|
|
3569
3143
|
await Promise.resolve();
|
|
3570
3144
|
});
|
|
@@ -3572,5 +3146,5 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3572
3146
|
};
|
|
3573
3147
|
|
|
3574
3148
|
//#endregion
|
|
3575
|
-
export { ArraySource, AutocompleteHandler, ButtonHandler, CommandMentions, ContextMenuHandler, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, IgnoreBots, InteractionHandler, InteractionMiddleware, Middleware, MiddlewareType, ModalHandler, Nsfw, Paginator, RegisterEvent, RepliableHandler, Seedcord, SelectMenuHandler, SlashHandler,
|
|
3149
|
+
export { ArraySource, AutocompleteHandler, ButtonHandler, CommandMentions, ContextMenuHandler, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, IgnoreBots, InteractionHandler, InteractionMiddleware, Middleware, MiddlewareType, ModalHandler, Nsfw, Paginator, Plugin, RegisterEvent, RepliableHandler, Seedcord, SelectMenuHandler, SlashHandler, Subscriber, WebhookLog, checkBotPermissions, checkPermissions, fetchGuildMember, fetchManyGuildMembers, fetchManyUsers, fetchRole, fetchText, fetchUser, getBotRole, getConfirmation, hasPermsToAssign, updateMemberRoles, version };
|
|
3576
3150
|
//# sourceMappingURL=index.mjs.map
|