@seedcord/core 0.10.0 → 0.11.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/README.md +27 -9
- package/dist/CoordinatedShutdown-H5YjZn8A.d.mts +78 -0
- package/dist/CoordinatedStartup-CGnJrqcl.mjs +168 -0
- package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +1 -0
- package/dist/{HmrModuleHandler-DOAtah3j.mjs → HmrModuleHandler-8ZC-UuZh.mjs} +9 -22
- package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +1 -0
- package/dist/PluginHost-B8kxLOeb.mjs +245 -0
- package/dist/PluginHost-B8kxLOeb.mjs.map +1 -0
- package/dist/{Plugin-Dhx_ivqj.d.mts → PluginHost-DB4j8unu.d.mts} +110 -17
- package/dist/PluginLifecycle-AhQ46NIr.mjs +158 -0
- package/dist/PluginLifecycle-AhQ46NIr.mjs.map +1 -0
- package/dist/ServerHost-CtmAhEXn.mjs +168 -0
- package/dist/ServerHost-CtmAhEXn.mjs.map +1 -0
- package/dist/ServerHost-DYeh0KTk.d.mts +40 -0
- package/dist/bindViteEnv-BgFewlZ0.d.mts +11 -0
- package/dist/bindViteEnv-DWq5rftJ.mjs +88 -0
- package/dist/bindViteEnv-DWq5rftJ.mjs.map +1 -0
- package/dist/env-internal.index.d.mts +2 -0
- package/dist/env-internal.index.mjs +3 -0
- package/dist/hmr.index.d.mts +0 -1
- package/dist/hmr.index.mjs +1 -1
- package/dist/{cursor-BTU5bGoo.d.mts → index-Du71Xx69.d.mts} +253 -6
- package/dist/index.d.mts +19 -84
- package/dist/index.mjs +17 -37
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +186 -22
- package/dist/internal.index.mjs +714 -19
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +25 -70
- package/dist/node-internal.index.mjs +83 -297
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +3 -2
- package/dist/node.index.mjs +53 -200
- package/dist/node.index.mjs.map +1 -1
- package/dist/{phases-D_zQBXga.d.mts → phases-FTM69J3a.d.mts} +4 -4
- package/dist/plugin.index.d.mts +2 -2
- package/dist/plugin.index.mjs +2 -2
- package/dist/publishDefault-D3dAyzZ9.mjs +7 -0
- package/dist/publishDefault-D3dAyzZ9.mjs.map +1 -0
- package/dist/subscribers-Ce3SWy23.mjs +1913 -0
- package/dist/subscribers-Ce3SWy23.mjs.map +1 -0
- package/package.json +24 -17
- package/dist/Bus-BxPa-Uvp.mjs +0 -866
- package/dist/Bus-BxPa-Uvp.mjs.map +0 -1
- package/dist/HmrModuleHandler-DOAtah3j.mjs.map +0 -1
- package/dist/Pluggable-CwKbLui3.d.mts +0 -162
- package/dist/Pluggable-afy2H0Dr.mjs +0 -339
- package/dist/Pluggable-afy2H0Dr.mjs.map +0 -1
- package/dist/Plugin-CmNW5n6a.mjs +0 -122
- package/dist/Plugin-CmNW5n6a.mjs.map +0 -1
- package/dist/contextMenuLeaves-COzDYa6c.d.mts +0 -17
- package/dist/cursor-CgNBnJfu.mjs +0 -802
- package/dist/cursor-CgNBnJfu.mjs.map +0 -1
- package/dist/devChannel-CQU7hq7Q.mjs +0 -14
- package/dist/devChannel-CQU7hq7Q.mjs.map +0 -1
- package/dist/index-B_-EjyvS.d.mts +0 -135
- package/dist/publishDefault-BuSZPbjT.mjs +0 -14
- package/dist/publishDefault-BuSZPbjT.mjs.map +0 -1
- package/dist/slashRouteLeaves-k1hJnXDO.mjs +0 -31
- package/dist/slashRouteLeaves-k1hJnXDO.mjs.map +0 -1
- package/dist/subscribers-xGy7a7xR.mjs +0 -167
- package/dist/subscribers-xGy7a7xR.mjs.map +0 -1
- package/dist/types-BG0BimVr.d.mts +0 -13
package/dist/Bus-BxPa-Uvp.mjs
DELETED
|
@@ -1,866 +0,0 @@
|
|
|
1
|
-
import { t as PublishDefault } from "./publishDefault-BuSZPbjT.mjs";
|
|
2
|
-
import "reflect-metadata";
|
|
3
|
-
import { ActionRowBuilder, ButtonBuilder, ChannelSelectMenuBuilder, CheckboxBuilder, CheckboxGroupBuilder, CheckboxGroupOptionBuilder, ContainerBuilder, ContextMenuCommandBuilder, EmbedBuilder, FileBuilder, FileUploadBuilder, LabelBuilder, MediaGalleryBuilder, MentionableSelectMenuBuilder, ModalBuilder, RadioGroupBuilder, RadioGroupOptionBuilder, RoleSelectMenuBuilder, SectionBuilder, SeparatorBuilder, SlashCommandBuilder, SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextDisplayBuilder, TextInputBuilder, UserSelectMenuBuilder } from "@discordjs/builders";
|
|
4
|
-
import { ApplicationCommandType, InteractionContextType, MessageFlags, Routes, SeparatorSpacingSize } from "discord-api-types/v10";
|
|
5
|
-
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
6
|
-
import { SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
|
|
7
|
-
import { Logger } from "@seedcord/logger";
|
|
8
|
-
import { filterCirculars, stripAnsi, timestampFromSnowflake } from "@seedcord/utils";
|
|
9
|
-
import { TypedEventEmitter } from "@seedcord/event-emitter";
|
|
10
|
-
import { Converters, Envapter } from "envapt";
|
|
11
|
-
import { DiscordAPIError, REST } from "@discordjs/rest";
|
|
12
|
-
|
|
13
|
-
//#region src/components/botColorHolder.ts
|
|
14
|
-
const DEFAULT_COLOR = "Default";
|
|
15
|
-
let readBotColor = () => void 0;
|
|
16
|
-
function bindBotColor(read) {
|
|
17
|
-
readBotColor = read;
|
|
18
|
-
}
|
|
19
|
-
function getBotColor() {
|
|
20
|
-
return readBotColor() ?? DEFAULT_COLOR;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/components/builderTypes.ts
|
|
25
|
-
/**
|
|
26
|
-
* Available Discord.js builder classes for use with BuilderComponent for commands, embeds, modals, etc.
|
|
27
|
-
*
|
|
28
|
-
* @internal
|
|
29
|
-
*/
|
|
30
|
-
const BuilderTypes = {
|
|
31
|
-
command: SlashCommandBuilder,
|
|
32
|
-
context_menu: ContextMenuCommandBuilder,
|
|
33
|
-
subcommand: SlashCommandSubcommandBuilder,
|
|
34
|
-
group: SlashCommandSubcommandGroupBuilder,
|
|
35
|
-
embed: EmbedBuilder,
|
|
36
|
-
modal: ModalBuilder,
|
|
37
|
-
label: LabelBuilder,
|
|
38
|
-
text_input: TextInputBuilder,
|
|
39
|
-
file_upload: FileUploadBuilder,
|
|
40
|
-
checkbox: CheckboxBuilder,
|
|
41
|
-
checkbox_group: CheckboxGroupBuilder,
|
|
42
|
-
checkbox_group_option: CheckboxGroupOptionBuilder,
|
|
43
|
-
radio_group: RadioGroupBuilder,
|
|
44
|
-
radio_group_option: RadioGroupOptionBuilder,
|
|
45
|
-
button: ButtonBuilder,
|
|
46
|
-
menu_string: StringSelectMenuBuilder,
|
|
47
|
-
menu_option_string: StringSelectMenuOptionBuilder,
|
|
48
|
-
menu_user: UserSelectMenuBuilder,
|
|
49
|
-
menu_channel: ChannelSelectMenuBuilder,
|
|
50
|
-
menu_mentionable: MentionableSelectMenuBuilder,
|
|
51
|
-
menu_role: RoleSelectMenuBuilder,
|
|
52
|
-
container: ContainerBuilder,
|
|
53
|
-
text_display: TextDisplayBuilder,
|
|
54
|
-
file: FileBuilder,
|
|
55
|
-
media: MediaGalleryBuilder,
|
|
56
|
-
section: SectionBuilder,
|
|
57
|
-
separator: SeparatorBuilder
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Available Discord.js action row classes for use with RowComponent for Select Menus and Buttons
|
|
61
|
-
*
|
|
62
|
-
* @internal
|
|
63
|
-
*/
|
|
64
|
-
const RowTypes = {
|
|
65
|
-
button: ActionRowBuilder,
|
|
66
|
-
menu_string: ActionRowBuilder,
|
|
67
|
-
menu_user: ActionRowBuilder,
|
|
68
|
-
menu_channel: ActionRowBuilder,
|
|
69
|
-
menu_mentionable: ActionRowBuilder,
|
|
70
|
-
menu_role: ActionRowBuilder
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
//#endregion
|
|
74
|
-
//#region src/components/colors.ts
|
|
75
|
-
const Colors = {
|
|
76
|
-
Aqua: 1752220,
|
|
77
|
-
Blue: 3447003,
|
|
78
|
-
Blurple: 5793266,
|
|
79
|
-
DarkAqua: 1146986,
|
|
80
|
-
DarkBlue: 2123412,
|
|
81
|
-
DarkButNotBlack: 2895667,
|
|
82
|
-
DarkerGrey: 8359053,
|
|
83
|
-
DarkGold: 12745742,
|
|
84
|
-
DarkGreen: 2067276,
|
|
85
|
-
DarkGrey: 9936031,
|
|
86
|
-
DarkNavy: 2899536,
|
|
87
|
-
DarkOrange: 11027200,
|
|
88
|
-
DarkPurple: 7419530,
|
|
89
|
-
DarkRed: 10038562,
|
|
90
|
-
DarkVividPink: 11342935,
|
|
91
|
-
Default: 0,
|
|
92
|
-
Fuchsia: 15418782,
|
|
93
|
-
Gold: 15844367,
|
|
94
|
-
Green: 5763719,
|
|
95
|
-
Grey: 9807270,
|
|
96
|
-
Greyple: 10070709,
|
|
97
|
-
LightGrey: 12370112,
|
|
98
|
-
LuminousVividPink: 15277667,
|
|
99
|
-
Navy: 3426654,
|
|
100
|
-
NotQuiteBlack: 2303786,
|
|
101
|
-
Orange: 15105570,
|
|
102
|
-
Purple: 10181046,
|
|
103
|
-
Red: 15548997,
|
|
104
|
-
White: 16777215,
|
|
105
|
-
Yellow: 16705372
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/components/resolveColor.ts
|
|
110
|
-
const MAX_COLOR = 16777215;
|
|
111
|
-
const RED_SHIFT = 16;
|
|
112
|
-
const GREEN_SHIFT = 8;
|
|
113
|
-
const palette = Colors;
|
|
114
|
-
/**
|
|
115
|
-
* Resolves a {@link BotColor} to the integer Discord expects.
|
|
116
|
-
*
|
|
117
|
-
* Ported from discord.js (Apache-2.0), `resolveColor` in src/util/Util.js.
|
|
118
|
-
*/
|
|
119
|
-
function resolveColor(color) {
|
|
120
|
-
let resolved;
|
|
121
|
-
if (typeof color === "string") {
|
|
122
|
-
if (color === "Random") return Math.floor(Math.random() * 16777216);
|
|
123
|
-
if (color === "Default") return 0;
|
|
124
|
-
if (/^#?[\da-f]{6}$/i.test(color)) return Number.parseInt(color.replace("#", ""), 16);
|
|
125
|
-
resolved = palette[color];
|
|
126
|
-
} else if (typeof color === "number") resolved = color;
|
|
127
|
-
else resolved = (color[0] << RED_SHIFT) + (color[1] << GREEN_SHIFT) + color[2];
|
|
128
|
-
if (resolved === void 0 || !Number.isSafeInteger(resolved)) throw new SeedcordTypeError(SeedcordErrorCode.ColorUnresolvable, [JSON.stringify(color)]);
|
|
129
|
-
if (resolved < 0 || resolved > MAX_COLOR) throw new SeedcordRangeError(SeedcordErrorCode.ColorOutOfRange);
|
|
130
|
-
return resolved;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
//#endregion
|
|
134
|
-
//#region src/components/Component.ts
|
|
135
|
-
/** @internal */
|
|
136
|
-
var BaseComponent = class {
|
|
137
|
-
_component;
|
|
138
|
-
constructor(ComponentClass) {
|
|
139
|
-
this._component = new ComponentClass();
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* The wrapped builder, for calling Discord.js methods like setTitle() and setDescription() inside a subclass.
|
|
143
|
-
*
|
|
144
|
-
* @example this.instance.setTitle('My Modal')
|
|
145
|
-
*/
|
|
146
|
-
get instance() {
|
|
147
|
-
return this._component;
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
/**
|
|
151
|
-
* Base class for Discord.js builder components
|
|
152
|
-
*
|
|
153
|
-
* Wraps Discord.js builders (SlashCommandBuilder, EmbedBuilder, etc.) with
|
|
154
|
-
* Seedcord-specific defaults and helper methods.
|
|
155
|
-
*
|
|
156
|
-
* @typeParam BuilderKey - The type of Discord.js builder being wrapped
|
|
157
|
-
*/
|
|
158
|
-
var BuilderComponent = class extends BaseComponent {
|
|
159
|
-
type;
|
|
160
|
-
colorApplied = false;
|
|
161
|
-
constructor(type) {
|
|
162
|
-
const ComponentClass = BuilderTypes[type];
|
|
163
|
-
super(ComponentClass);
|
|
164
|
-
this.type = type;
|
|
165
|
-
if (this.instance instanceof SlashCommandBuilder || this.instance instanceof ContextMenuCommandBuilder) this.instance.setContexts(InteractionContextType.Guild);
|
|
166
|
-
}
|
|
167
|
-
get component() {
|
|
168
|
-
this.applyBotColor();
|
|
169
|
-
return this.instance;
|
|
170
|
-
}
|
|
171
|
-
applyBotColor() {
|
|
172
|
-
if (this.colorApplied) return;
|
|
173
|
-
this.colorApplied = true;
|
|
174
|
-
const color = getBotColor();
|
|
175
|
-
if (this.instance instanceof EmbedBuilder) {
|
|
176
|
-
if (this.instance.data.color === void 0) this.instance.setColor(resolveColor(color));
|
|
177
|
-
} else if (this.instance instanceof ContainerBuilder) {
|
|
178
|
-
const accent = this.instance.data.accent_color;
|
|
179
|
-
if (accent === null || accent === void 0) this.instance.setAccentColor(color === "Default" ? void 0 : resolveColor(color));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
const BUILDER_COMPONENT = Symbol.for("seedcord:core:builder-component");
|
|
184
|
-
Object.defineProperty(BuilderComponent.prototype, BUILDER_COMPONENT, { value: true });
|
|
185
|
-
/** @internal */
|
|
186
|
-
function isBuilderComponentClass(value) {
|
|
187
|
-
if (typeof value !== "function") return false;
|
|
188
|
-
const proto = value.prototype;
|
|
189
|
-
return typeof proto === "object" && proto !== null && BUILDER_COMPONENT in proto;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Base class for Discord action row components
|
|
193
|
-
*
|
|
194
|
-
* Wraps Discord.js action row builder with Seedcord-specific defaults and helper methods.
|
|
195
|
-
*
|
|
196
|
-
* @typeParam RowKey - The Discord.js action row type being wrapped
|
|
197
|
-
*/
|
|
198
|
-
var RowComponent = class extends BaseComponent {
|
|
199
|
-
type;
|
|
200
|
-
constructor(type) {
|
|
201
|
-
const ComponentClass = RowTypes[type];
|
|
202
|
-
super(ComponentClass);
|
|
203
|
-
this.type = type;
|
|
204
|
-
}
|
|
205
|
-
get component() {
|
|
206
|
-
return this.instance;
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
//#endregion
|
|
211
|
-
//#region src/commands/contextMenuLeaves.ts
|
|
212
|
-
/**
|
|
213
|
-
* dedupes names across builders, since the same builder repeats once per guild.
|
|
214
|
-
* a user and a message command can share a name.
|
|
215
|
-
*
|
|
216
|
-
* @internal
|
|
217
|
-
*/
|
|
218
|
-
function contextMenuLeaves(commands) {
|
|
219
|
-
const user = /* @__PURE__ */ new Set();
|
|
220
|
-
const message = /* @__PURE__ */ new Set();
|
|
221
|
-
for (const command of commands) {
|
|
222
|
-
if (!(command instanceof ContextMenuCommandBuilder)) continue;
|
|
223
|
-
if (command.type === ApplicationCommandType.User) user.add(command.name);
|
|
224
|
-
else message.add(command.name);
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
user,
|
|
228
|
-
message
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
//#endregion
|
|
233
|
-
//#region src/miscellaneous/guarded.ts
|
|
234
|
-
function accessorStore() {
|
|
235
|
-
return Object.create(null);
|
|
236
|
-
}
|
|
237
|
-
function clearStore(store) {
|
|
238
|
-
for (const key of Object.keys(store)) Reflect.deleteProperty(store, key);
|
|
239
|
-
}
|
|
240
|
-
function guardedAccessor(accessor, storage) {
|
|
241
|
-
return new Proxy(storage, { get(target, key, receiver) {
|
|
242
|
-
if (typeof key !== "string" || Object.hasOwn(target, key)) return Reflect.get(target, key, receiver);
|
|
243
|
-
throw new SeedcordError(SeedcordErrorCode.CoreAccessorUnresolved, [accessor, key]);
|
|
244
|
-
} });
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
//#endregion
|
|
248
|
-
//#region src/metadataKeys.ts
|
|
249
|
-
/**
|
|
250
|
-
* Every interaction kind seedcord routes.
|
|
251
|
-
*/
|
|
252
|
-
let InteractionKind = /* @__PURE__ */ function(InteractionKind) {
|
|
253
|
-
InteractionKind["Slash"] = "slash";
|
|
254
|
-
InteractionKind["Button"] = "button";
|
|
255
|
-
InteractionKind["Modal"] = "modal";
|
|
256
|
-
InteractionKind["StringMenu"] = "stringMenu";
|
|
257
|
-
InteractionKind["UserMenu"] = "userMenu";
|
|
258
|
-
InteractionKind["RoleMenu"] = "roleMenu";
|
|
259
|
-
InteractionKind["ChannelMenu"] = "channelMenu";
|
|
260
|
-
InteractionKind["MentionableMenu"] = "mentionableMenu";
|
|
261
|
-
InteractionKind["MessageContextMenu"] = "messageContextMenu";
|
|
262
|
-
InteractionKind["UserContextMenu"] = "userContextMenu";
|
|
263
|
-
InteractionKind["Autocomplete"] = "autocomplete";
|
|
264
|
-
return InteractionKind;
|
|
265
|
-
}({});
|
|
266
|
-
const CommandMetadataKey = Symbol.for("seedcord:command:metadata");
|
|
267
|
-
const InteractionMetadataKey = Symbol.for("seedcord:interaction:metadata");
|
|
268
|
-
const InteractionMiddlewareMetadataKey = Symbol.for("seedcord:middleware:interaction:metadata");
|
|
269
|
-
const EventMiddlewareMetadataKey = Symbol.for("seedcord:middleware:event:metadata");
|
|
270
|
-
const SubscribeMetadataKey = Symbol.for("seedcord:subscribe:metadata");
|
|
271
|
-
const EventMetadataKey = Symbol.for("seedcord:event:metadata");
|
|
272
|
-
const GatedMetadataKey = Symbol.for("seedcord:gated:metadata");
|
|
273
|
-
const WebhookUrlMetadataKey = Symbol.for("seedcord:webhook-url:metadata");
|
|
274
|
-
const InteractionRouteKeys = {
|
|
275
|
-
["slash"]: Symbol.for("seedcord:interaction:slash"),
|
|
276
|
-
["button"]: Symbol.for("seedcord:interaction:button"),
|
|
277
|
-
["modal"]: Symbol.for("seedcord:interaction:modal"),
|
|
278
|
-
["stringMenu"]: Symbol.for("seedcord:interaction:string-menu"),
|
|
279
|
-
["userMenu"]: Symbol.for("seedcord:interaction:user-menu"),
|
|
280
|
-
["roleMenu"]: Symbol.for("seedcord:interaction:role-menu"),
|
|
281
|
-
["channelMenu"]: Symbol.for("seedcord:interaction:channel-menu"),
|
|
282
|
-
["mentionableMenu"]: Symbol.for("seedcord:interaction:mentionable-menu"),
|
|
283
|
-
["messageContextMenu"]: Symbol.for("seedcord:interaction:message-context-menu"),
|
|
284
|
-
["userContextMenu"]: Symbol.for("seedcord:interaction:user-context-menu"),
|
|
285
|
-
["autocomplete"]: Symbol.for("seedcord:interaction:autocomplete")
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
//#endregion
|
|
289
|
-
//#region src/reply/flags.ts
|
|
290
|
-
function sendFlags(opts) {
|
|
291
|
-
let flags = MessageFlags.IsComponentsV2;
|
|
292
|
-
if (opts?.ephemeral ?? true) flags |= MessageFlags.Ephemeral;
|
|
293
|
-
if (opts?.silent) flags |= MessageFlags.SuppressNotifications;
|
|
294
|
-
return flags;
|
|
295
|
-
}
|
|
296
|
-
function deferFlags(opts) {
|
|
297
|
-
return opts?.ephemeral ?? true ? MessageFlags.Ephemeral : 0;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
//#endregion
|
|
301
|
-
//#region src/subscribers/ReportThrottle.ts
|
|
302
|
-
var ReportThrottle = class ReportThrottle {
|
|
303
|
-
windowMs;
|
|
304
|
-
now;
|
|
305
|
-
static perCore = /* @__PURE__ */ new WeakMap();
|
|
306
|
-
static for(core) {
|
|
307
|
-
let throttle = ReportThrottle.perCore.get(core);
|
|
308
|
-
if (!throttle) {
|
|
309
|
-
throttle = new ReportThrottle();
|
|
310
|
-
ReportThrottle.perCore.set(core, throttle);
|
|
311
|
-
}
|
|
312
|
-
return throttle;
|
|
313
|
-
}
|
|
314
|
-
windows = /* @__PURE__ */ new Map();
|
|
315
|
-
constructor(windowMs = 6e4, now = () => Date.now()) {
|
|
316
|
-
this.windowMs = windowMs;
|
|
317
|
-
this.now = now;
|
|
318
|
-
}
|
|
319
|
-
take(key) {
|
|
320
|
-
const window = this.windows.get(key);
|
|
321
|
-
if (window && this.now() - window.lastSentAt < this.windowMs) {
|
|
322
|
-
window.suppressedSinceSend += 1;
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
const suppressed = window?.suppressedSinceSend ?? 0;
|
|
326
|
-
this.windows.set(key, {
|
|
327
|
-
lastSentAt: this.now(),
|
|
328
|
-
suppressedSinceSend: 0
|
|
329
|
-
});
|
|
330
|
-
return suppressed;
|
|
331
|
-
}
|
|
332
|
-
restore(key, suppressed) {
|
|
333
|
-
const window = this.windows.get(key);
|
|
334
|
-
if (window) window.suppressedSinceSend += suppressed + 1;
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
//#endregion
|
|
339
|
-
//#region src/subscribers/Subscriber.ts
|
|
340
|
-
/**
|
|
341
|
-
* Abstract base class for handling application subscribers.
|
|
342
|
-
*
|
|
343
|
-
* Provides type-safe access to subscribers data and the core framework instance.
|
|
344
|
-
* Extend this class to create custom subscriber handlers.
|
|
345
|
-
*
|
|
346
|
-
* @typeParam KeyOfSubscribers - The specific subscriber type this handler processes
|
|
347
|
-
* @typeParam TCore - The transport's Core
|
|
348
|
-
*/
|
|
349
|
-
var Subscriber = class {
|
|
350
|
-
data;
|
|
351
|
-
core;
|
|
352
|
-
logger;
|
|
353
|
-
/**
|
|
354
|
-
* Instantiates the subscriber with event data and core.
|
|
355
|
-
*
|
|
356
|
-
* @param data - The subscriber event data
|
|
357
|
-
* @param core - The core framework instance
|
|
358
|
-
*/
|
|
359
|
-
constructor(data, core) {
|
|
360
|
-
this.data = data;
|
|
361
|
-
this.core = core;
|
|
362
|
-
this.logger = new Logger(this.constructor.name, { channel: "subscribers" });
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
//#endregion
|
|
367
|
-
//#region src/subscribers/bases/webhookHelpers.ts
|
|
368
|
-
const DISCORD_WEBHOOK_REGEX = new RegExp(String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`);
|
|
369
|
-
function isDiscordWebhookUrl(value) {
|
|
370
|
-
return URL.canParse(value) && DISCORD_WEBHOOK_REGEX.test(value);
|
|
371
|
-
}
|
|
372
|
-
function jsonAttachment(name, description, data) {
|
|
373
|
-
const content = filterCirculars(data);
|
|
374
|
-
return {
|
|
375
|
-
name,
|
|
376
|
-
description,
|
|
377
|
-
data: new TextEncoder().encode(JSON.stringify(content, void 0, 2))
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
function breakBackticks(text) {
|
|
381
|
-
return text.replaceAll("```", "```");
|
|
382
|
-
}
|
|
383
|
-
const REPORT_BUDGET = 1800;
|
|
384
|
-
function body(value) {
|
|
385
|
-
const raw = Error.isError(value) ? value.stack ?? `${value.name}: ${value.message}` : String(value);
|
|
386
|
-
return breakBackticks(stripAnsi(raw));
|
|
387
|
-
}
|
|
388
|
-
function clamp(text, budget) {
|
|
389
|
-
if (text.length <= budget) return text;
|
|
390
|
-
return budget > 0 ? `${text.slice(0, budget - 1)}…` : "";
|
|
391
|
-
}
|
|
392
|
-
function omittedLine(count) {
|
|
393
|
-
return `\n\nand ${count} more failure${count === 1 ? "" : "s"}`;
|
|
394
|
-
}
|
|
395
|
-
function memberReport(members, budget) {
|
|
396
|
-
const room = Math.max(budget - omittedLine(members.length).length, 0);
|
|
397
|
-
const share = Math.floor(room / members.length);
|
|
398
|
-
let report = "";
|
|
399
|
-
let shown = 0;
|
|
400
|
-
for (const [index, member] of members.entries()) {
|
|
401
|
-
const heading = `\n\nFailure ${index + 1} of ${members.length}:\n`;
|
|
402
|
-
const entry = heading + clamp(body(member), Math.max(share - heading.length, 0));
|
|
403
|
-
if (report.length + entry.length > room) break;
|
|
404
|
-
report += entry;
|
|
405
|
-
shown++;
|
|
406
|
-
}
|
|
407
|
-
const hidden = members.length - shown;
|
|
408
|
-
return hidden > 0 ? report + omittedLine(hidden) : report;
|
|
409
|
-
}
|
|
410
|
-
function errorReport(error) {
|
|
411
|
-
let head = body(error);
|
|
412
|
-
if (Error.isError(error.cause)) head += `\n\nCaused by:\n${body(error.cause)}`;
|
|
413
|
-
const members = error instanceof AggregateError ? error.errors : [];
|
|
414
|
-
if (members.length === 0) return clamp(head, REPORT_BUDGET);
|
|
415
|
-
const report = clamp(head, REPORT_BUDGET - omittedLine(members.length).length);
|
|
416
|
-
return report + memberReport(members, REPORT_BUDGET - report.length);
|
|
417
|
-
}
|
|
418
|
-
var WebhookSeparator = class extends BuilderComponent {
|
|
419
|
-
constructor() {
|
|
420
|
-
super("separator");
|
|
421
|
-
this.instance.setSpacing(SeparatorSpacingSize.Small).setDivider(true);
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
//#endregion
|
|
426
|
-
//#region src/subscribers/bases/WebhookSender.ts
|
|
427
|
-
const HTTP_NOT_FOUND = 404;
|
|
428
|
-
const HTTP_UNAUTHORIZED = 401;
|
|
429
|
-
var WebhookSender = class {
|
|
430
|
-
rest = new REST();
|
|
431
|
-
route;
|
|
432
|
-
constructor(url) {
|
|
433
|
-
const match = /\/webhooks\/(\d+)\/([\w$-]+)$/.exec(url);
|
|
434
|
-
if (!match?.[1] || !match[2]) throw new SeedcordTypeError(SeedcordErrorCode.ConfigWebhookUrlInvalid, ["The url"]);
|
|
435
|
-
this.route = Routes.webhook(match[1], match[2]);
|
|
436
|
-
}
|
|
437
|
-
async verify() {
|
|
438
|
-
try {
|
|
439
|
-
await this.rest.get(this.route, { auth: false });
|
|
440
|
-
return "ok";
|
|
441
|
-
} catch (error) {
|
|
442
|
-
return error instanceof DiscordAPIError && (error.status === HTTP_NOT_FOUND || error.status === HTTP_UNAUTHORIZED) ? "missing" : "unreachable";
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
async send(options) {
|
|
446
|
-
const { flags, username, avatarUrl, components, files } = options;
|
|
447
|
-
await this.rest.post(this.route, {
|
|
448
|
-
auth: false,
|
|
449
|
-
query: new URLSearchParams({
|
|
450
|
-
with_components: "true",
|
|
451
|
-
wait: "true"
|
|
452
|
-
}),
|
|
453
|
-
body: {
|
|
454
|
-
flags,
|
|
455
|
-
username,
|
|
456
|
-
...avatarUrl && { avatar_url: avatarUrl },
|
|
457
|
-
components: components.map((component) => component.toJSON()),
|
|
458
|
-
...files?.length && { attachments: files.map((file, id) => ({
|
|
459
|
-
id,
|
|
460
|
-
filename: file.name,
|
|
461
|
-
description: file.description
|
|
462
|
-
})) }
|
|
463
|
-
},
|
|
464
|
-
...files?.length && { files: files.map((file) => ({
|
|
465
|
-
name: file.name,
|
|
466
|
-
data: file.data
|
|
467
|
-
})) }
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
//#endregion
|
|
473
|
-
//#region src/subscribers/bases/WebhookLog.ts
|
|
474
|
-
/**
|
|
475
|
-
* Base class for subscribers that deliver their event to a Discord webhook.
|
|
476
|
-
*
|
|
477
|
-
* Declare the url's environment variable with `@WebhookUrl` and implement {@link report}. The base
|
|
478
|
-
* resolves the url, validates it, reuses one sender per url, and sends. Registration skips a reporter
|
|
479
|
-
* whose variable is unset and warns at boot.
|
|
480
|
-
*
|
|
481
|
-
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
482
|
-
* @typeParam TCore - The transport's Core
|
|
483
|
-
*/
|
|
484
|
-
var WebhookLog = class WebhookLog extends Subscriber {
|
|
485
|
-
static senders = /* @__PURE__ */ new Map();
|
|
486
|
-
/**
|
|
487
|
-
* Events sharing this key collapse into one card per minute. Return `null` to send every event.
|
|
488
|
-
*/
|
|
489
|
-
throttleKey() {
|
|
490
|
-
return null;
|
|
491
|
-
}
|
|
492
|
-
async execute() {
|
|
493
|
-
const envKey = WebhookLog.envKeyOf(this.constructor);
|
|
494
|
-
const url = WebhookLog.urlOf(envKey);
|
|
495
|
-
if (url === null) throw new SeedcordError(SeedcordErrorCode.ConfigMissingEnv, [envKey]);
|
|
496
|
-
const key = this.throttleKey();
|
|
497
|
-
const throttle = ReportThrottle.for(this.core);
|
|
498
|
-
const suppressed = key === null ? 0 : throttle.take(key);
|
|
499
|
-
if (suppressed === null) {
|
|
500
|
-
this.logger.debug(`counted a repeat into the next ${paint.sky.bold(this.constructor.name)} card`);
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
try {
|
|
504
|
-
const { username, avatarUrl, components, files } = await this.report(suppressed);
|
|
505
|
-
await WebhookLog.senderFor(url).send({
|
|
506
|
-
flags: sendFlags({ ephemeral: false }),
|
|
507
|
-
username: username ?? this.constructor.name,
|
|
508
|
-
avatarUrl,
|
|
509
|
-
components,
|
|
510
|
-
files
|
|
511
|
-
});
|
|
512
|
-
} catch (error) {
|
|
513
|
-
if (key !== null) throttle.restore(key, suppressed);
|
|
514
|
-
this.logger.error("Webhook report failed", error);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
/** @internal */
|
|
518
|
-
static senderFor(url) {
|
|
519
|
-
let sender = WebhookLog.senders.get(url);
|
|
520
|
-
if (!sender) {
|
|
521
|
-
sender = new WebhookSender(url);
|
|
522
|
-
WebhookLog.senders.set(url, sender);
|
|
523
|
-
}
|
|
524
|
-
return sender;
|
|
525
|
-
}
|
|
526
|
-
/** @internal */
|
|
527
|
-
static envKeyOf(ctor) {
|
|
528
|
-
const envKey = Reflect.getMetadata(WebhookUrlMetadataKey, ctor);
|
|
529
|
-
if (!envKey) throw new SeedcordError(SeedcordErrorCode.DecoratorWebhookUrlMissing, [ctor.name]);
|
|
530
|
-
return envKey;
|
|
531
|
-
}
|
|
532
|
-
/** @internal Returns null when the env var is unset, throws when set but malformed. */
|
|
533
|
-
static urlOf(envKey) {
|
|
534
|
-
if (!Envapter.has(envKey)) return null;
|
|
535
|
-
const raw = Envapter.getRequired(envKey, Converters.String).trim();
|
|
536
|
-
if (raw === "") return null;
|
|
537
|
-
if (!isDiscordWebhookUrl(raw)) throw new SeedcordTypeError(SeedcordErrorCode.ConfigWebhookUrlInvalid, [envKey]);
|
|
538
|
-
return raw;
|
|
539
|
-
}
|
|
540
|
-
};
|
|
541
|
-
|
|
542
|
-
//#endregion
|
|
543
|
-
//#region src/subscribers/decorators/Subscribe.ts
|
|
544
|
-
/**
|
|
545
|
-
* Registers a subscriber handler class with a specific subscriber event.
|
|
546
|
-
*
|
|
547
|
-
* Associates the decorated class with a subscriber event type for automatic
|
|
548
|
-
* registration and execution when the subscriber is emitted.
|
|
549
|
-
*
|
|
550
|
-
* @typeParam TSubscriber - The subscriber event name to register for
|
|
551
|
-
* @param subscriber - The subscriber event name to register for
|
|
552
|
-
* @param options - Options to configure the subscriber handler registration.
|
|
553
|
-
* @decorator
|
|
554
|
-
* @example
|
|
555
|
-
* ```typescript
|
|
556
|
-
* \@Subscribe('userJoin')
|
|
557
|
-
* class WelcomeHandler extends Subscriber<'userJoin'> {
|
|
558
|
-
* async execute() {
|
|
559
|
-
* }
|
|
560
|
-
* }
|
|
561
|
-
* ```
|
|
562
|
-
* @example
|
|
563
|
-
* ```ts
|
|
564
|
-
* \@Subscribe('userJoin', { frequency: 'once' })
|
|
565
|
-
* // or
|
|
566
|
-
* \@Subscribe('userJoin')
|
|
567
|
-
* ```
|
|
568
|
-
*/
|
|
569
|
-
function Subscribe(subscriber, options) {
|
|
570
|
-
return function(constructor) {
|
|
571
|
-
const meta = {
|
|
572
|
-
subscriber,
|
|
573
|
-
frequency: options?.frequency
|
|
574
|
-
};
|
|
575
|
-
Reflect.defineMetadata(SubscribeMetadataKey, meta, constructor);
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
//#endregion
|
|
580
|
-
//#region src/subscribers/decorators/WebhookUrl.ts
|
|
581
|
-
/**
|
|
582
|
-
* Declares the environment variable a {@link WebhookLog} reporter reads its webhook url from.
|
|
583
|
-
*
|
|
584
|
-
* An unset variable disables the reporter with a boot warning. A set but malformed value throws
|
|
585
|
-
* at boot.
|
|
586
|
-
*
|
|
587
|
-
* @param envKey - The environment variable name holding the webhook url
|
|
588
|
-
* @decorator
|
|
589
|
-
* @example
|
|
590
|
-
* ```ts
|
|
591
|
-
* \@Subscribe('memberBanned')
|
|
592
|
-
* \@WebhookUrl('BAN_LOG_WEBHOOK_URL')
|
|
593
|
-
* class BanLog extends WebhookLog<'memberBanned'> {
|
|
594
|
-
* report() {
|
|
595
|
-
* return { components: [new BanCard(this.data).component] };
|
|
596
|
-
* }
|
|
597
|
-
* }
|
|
598
|
-
* ```
|
|
599
|
-
*/
|
|
600
|
-
function WebhookUrl(envKey) {
|
|
601
|
-
return function(constructor) {
|
|
602
|
-
Reflect.defineMetadata(WebhookUrlMetadataKey, envKey, constructor);
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
//#endregion
|
|
607
|
-
//#region \0@oxc-project+runtime@0.152.0/helpers/esm/decorate.js
|
|
608
|
-
function __decorate(decorators, target, key, desc) {
|
|
609
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
610
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
611
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
612
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
//#endregion
|
|
616
|
-
//#region src/subscribers/default/HandledException.ts
|
|
617
|
-
let HandledException = class HandledException extends WebhookLog {
|
|
618
|
-
throttleKey() {
|
|
619
|
-
return `handledException:${this.data.origin}:${this.data.denial.name}`;
|
|
620
|
-
}
|
|
621
|
-
report(suppressed) {
|
|
622
|
-
return {
|
|
623
|
-
username: "Handled Exception",
|
|
624
|
-
components: [new HandledExceptionContainer(this.data, suppressed).component],
|
|
625
|
-
files: [jsonAttachment("source.json", "The raw source the fault came from", this.data.source.raw)]
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
};
|
|
629
|
-
HandledException = __decorate([Subscribe("handledException"), WebhookUrl("HANDLED_EXCEPTION_WEBHOOK_URL")], HandledException);
|
|
630
|
-
var HandledExceptionContainer = class extends BuilderComponent {
|
|
631
|
-
constructor(data, suppressed) {
|
|
632
|
-
super("container");
|
|
633
|
-
const { denial, uuid, source } = data;
|
|
634
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(faultSummary(denial, source))).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${breakBackticks(causeStack(denial))}\`\`\``)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Source")).addFileComponents((file) => file.setURL("attachment://source.json"));
|
|
635
|
-
this.addSuppressedCount(suppressed);
|
|
636
|
-
}
|
|
637
|
-
addSuppressedCount(suppressed) {
|
|
638
|
-
if (suppressed === 0) return;
|
|
639
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(`-# ${suppressed} more of these since the last report`));
|
|
640
|
-
}
|
|
641
|
-
};
|
|
642
|
-
function faultSummary(denial, source) {
|
|
643
|
-
const head = stripAnsi(`### A handled fault was reported: \`${denial.name}\`\n**Message:** ${denial.message}\n`);
|
|
644
|
-
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"}\``;
|
|
645
|
-
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}\``;
|
|
646
|
-
}
|
|
647
|
-
function causeStack(denial) {
|
|
648
|
-
const { cause } = denial;
|
|
649
|
-
if (Error.isError(cause)) return stripAnsi(cause.stack ?? cause.message);
|
|
650
|
-
if (typeof cause === "string") return stripAnsi(cause);
|
|
651
|
-
if (typeof cause === "bigint" || typeof cause === "symbol" || typeof cause === "function") return cause.toString();
|
|
652
|
-
if (cause !== void 0) try {
|
|
653
|
-
return JSON.stringify(cause);
|
|
654
|
-
} catch {
|
|
655
|
-
return "[unserializable cause]";
|
|
656
|
-
}
|
|
657
|
-
return stripAnsi(denial.stack ?? "No cause recorded.");
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
//#endregion
|
|
661
|
-
//#region src/subscribers/default/UnknownException.ts
|
|
662
|
-
let UnknownException = class UnknownException extends WebhookLog {
|
|
663
|
-
throttleKey() {
|
|
664
|
-
return `unknownException:${this.data.origin}:${nameOf(this.data.error)}`;
|
|
665
|
-
}
|
|
666
|
-
report(suppressed) {
|
|
667
|
-
const { metadata } = this.data;
|
|
668
|
-
return {
|
|
669
|
-
username: "Unknown Exception",
|
|
670
|
-
components: [new UnhandledErrorContainer(this.data, suppressed).component],
|
|
671
|
-
files: metadata ? [jsonAttachment("metadata.json", "Metadata associated with the error", metadata)] : void 0
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
};
|
|
675
|
-
UnknownException = __decorate([Subscribe("unknownException"), WebhookUrl("UNKNOWN_EXCEPTION_WEBHOOK_URL")], UnknownException);
|
|
676
|
-
function nameOf(error) {
|
|
677
|
-
return error.name === "Error" ? error.constructor.name : error.name;
|
|
678
|
-
}
|
|
679
|
-
var UnhandledErrorContainer = class extends BuilderComponent {
|
|
680
|
-
constructor(data, suppressed) {
|
|
681
|
-
super("container");
|
|
682
|
-
const { uuid, error, guild, user, metadata } = data;
|
|
683
|
-
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)}\`\`\``));
|
|
684
|
-
this.addTimestampsIfAvailable(error);
|
|
685
|
-
this.addMetadataIfAvailable(metadata);
|
|
686
|
-
this.addSuppressedCount(suppressed);
|
|
687
|
-
}
|
|
688
|
-
addSuppressedCount(suppressed) {
|
|
689
|
-
if (suppressed === 0) return;
|
|
690
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(`-# ${suppressed} more of these since the last report`));
|
|
691
|
-
}
|
|
692
|
-
addTimestampsIfAvailable(error) {
|
|
693
|
-
if (!(error instanceof DiscordAPIError)) return;
|
|
694
|
-
const now = Date.now();
|
|
695
|
-
const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
|
|
696
|
-
if (!snowflake) return void 0;
|
|
697
|
-
const interactionTs = timestampFromSnowflake(snowflake);
|
|
698
|
-
const diff = now - interactionTs;
|
|
699
|
-
const seconds = Math.floor(diff / 1e3);
|
|
700
|
-
const millis = diff % 1e3;
|
|
701
|
-
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`));
|
|
702
|
-
}
|
|
703
|
-
addMetadataIfAvailable(metadata) {
|
|
704
|
-
if (!metadata) return;
|
|
705
|
-
this.instance.addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Metadata")).addFileComponents((file) => file.setURL("attachment://metadata.json"));
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
|
|
709
|
-
//#endregion
|
|
710
|
-
//#region src/subscribers/slots.ts
|
|
711
|
-
const RegisterDefaults = Symbol("seedcord:bus:register-defaults");
|
|
712
|
-
const VerifyWebhooks = Symbol("seedcord:bus:verify-webhooks");
|
|
713
|
-
const RegisteredCount = Symbol("seedcord:bus:registered-count");
|
|
714
|
-
const RegisterSubscriber = Symbol("seedcord:bus:register");
|
|
715
|
-
const UnregisterSubscriber = Symbol("seedcord:bus:unregister");
|
|
716
|
-
|
|
717
|
-
//#endregion
|
|
718
|
-
//#region src/subscribers/Bus.ts
|
|
719
|
-
const loggerSlot = Symbol("seedcord:bus:logger");
|
|
720
|
-
/**
|
|
721
|
-
* Registers and dispatches subscribers. Subscribers run on a programmatic or framework publish.
|
|
722
|
-
* Accessed via `core.bus`. Do not construct it directly.
|
|
723
|
-
*/
|
|
724
|
-
var Bus = class extends TypedEventEmitter {
|
|
725
|
-
core;
|
|
726
|
-
logger = new Logger("Subscribers", { channel: "subscribers" });
|
|
727
|
-
/** @internal */
|
|
728
|
-
[loggerSlot] = this.logger;
|
|
729
|
-
subscribersMap = /* @__PURE__ */ new Map();
|
|
730
|
-
executedOnce = /* @__PURE__ */ new Set();
|
|
731
|
-
webhookProbes = /* @__PURE__ */ new Map();
|
|
732
|
-
constructor(core) {
|
|
733
|
-
super();
|
|
734
|
-
this.core = core;
|
|
735
|
-
}
|
|
736
|
-
/** @internal keep this inside start(). a bad webhook url thrown outside it never resets the singleton guard */
|
|
737
|
-
[RegisterDefaults]() {
|
|
738
|
-
this[RegisterSubscriber](registrationFor(UnknownException));
|
|
739
|
-
this[RegisterSubscriber](registrationFor(HandledException));
|
|
740
|
-
}
|
|
741
|
-
/** @internal */
|
|
742
|
-
[RegisterSubscriber](registration) {
|
|
743
|
-
if (!this.probeWebhook(registration.ctor)) return;
|
|
744
|
-
for (const key of registration.keys) {
|
|
745
|
-
let registrations = this.subscribersMap.get(key);
|
|
746
|
-
if (!registrations) {
|
|
747
|
-
registrations = [];
|
|
748
|
-
this.subscribersMap.set(key, registrations);
|
|
749
|
-
}
|
|
750
|
-
registrations.push(registration);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
/** @internal */
|
|
754
|
-
[UnregisterSubscriber](ctor, keys) {
|
|
755
|
-
for (const key of keys ?? [...this.subscribersMap.keys()]) {
|
|
756
|
-
const registrations = this.subscribersMap.get(key);
|
|
757
|
-
if (!registrations) continue;
|
|
758
|
-
const index = registrations.findIndex((entry) => entry.ctor === ctor);
|
|
759
|
-
if (index !== -1) registrations.splice(index, 1);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
/** @internal */
|
|
763
|
-
get [RegisteredCount]() {
|
|
764
|
-
return [...this.subscribersMap.values()].reduce((total, entries) => total + entries.length, 0);
|
|
765
|
-
}
|
|
766
|
-
/** @internal registration already dropped every reporter with no url set */
|
|
767
|
-
async [VerifyWebhooks]() {
|
|
768
|
-
const missing = [];
|
|
769
|
-
await Promise.all([...this.webhookProbes].map(async ([url, envKeys]) => {
|
|
770
|
-
const result = await WebhookLog.senderFor(url).verify();
|
|
771
|
-
if (result === "missing") missing.push(...envKeys);
|
|
772
|
-
else if (result === "unreachable") this.logger.warn(`could not verify webhook at ${envKeys.map((key) => paint.amber.bold(key)).join(", ")}`);
|
|
773
|
-
}));
|
|
774
|
-
if (missing.length > 0) throw new SeedcordError(SeedcordErrorCode.ConfigWebhookNotFound, [missing.join(", ")]);
|
|
775
|
-
}
|
|
776
|
-
probeWebhook(ctor) {
|
|
777
|
-
if (!(ctor.prototype instanceof WebhookLog)) return true;
|
|
778
|
-
const envKey = WebhookLog.envKeyOf(ctor);
|
|
779
|
-
const url = WebhookLog.urlOf(envKey);
|
|
780
|
-
if (url === null) {
|
|
781
|
-
this.logger.warn(`${paint.sky.bold(ctor.name)} disabled, ${paint.amber.bold(envKey)} is not set`);
|
|
782
|
-
return false;
|
|
783
|
-
}
|
|
784
|
-
const envKeys = this.webhookProbes.get(url) ?? [];
|
|
785
|
-
if (!envKeys.includes(envKey)) envKeys.push(envKey);
|
|
786
|
-
this.webhookProbes.set(url, envKeys);
|
|
787
|
-
return true;
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Publishes an event to its subscribers and native listeners.
|
|
791
|
-
*
|
|
792
|
-
* Fire-and-forget. Subscriber handlers run asynchronously and this returns before they finish.
|
|
793
|
-
* Errors thrown by a subscriber or by an `on()` listener are caught and logged, never surfaced
|
|
794
|
-
* here. One throwing listener does not stop the others. Subscribers on one key run concurrently
|
|
795
|
-
* and carry no ordering guarantee. A `'once'` subscriber is marked as executed when it starts,
|
|
796
|
-
* even if it throws. It never runs twice.
|
|
797
|
-
*
|
|
798
|
-
* The framework's own keys are excluded. Subscribe to those and listen with `on`. The framework
|
|
799
|
-
* is their only publisher.
|
|
800
|
-
*
|
|
801
|
-
* @param event - The subscription key to publish
|
|
802
|
-
* @param data - Payload passed to each subscriber
|
|
803
|
-
* @returns Whether the underlying emitter had native listeners for the event
|
|
804
|
-
*/
|
|
805
|
-
publish(event, data) {
|
|
806
|
-
return this.dispatchKey(event, data);
|
|
807
|
-
}
|
|
808
|
-
/**
|
|
809
|
-
* @deprecated Always throws. Call {@link Bus.publish} to reach subscribers and listeners together.
|
|
810
|
-
* @throws A **SeedcordTypeError** with `CoreBusEmitUnavailable`. `emit` reaches only the `on()` listeners.
|
|
811
|
-
*/
|
|
812
|
-
emit(event, ..._args) {
|
|
813
|
-
throw new SeedcordTypeError(SeedcordErrorCode.CoreBusEmitUnavailable, [String(event)]);
|
|
814
|
-
}
|
|
815
|
-
/** @internal the framework's own publish path, keyed by a symbol no public entry exports */
|
|
816
|
-
[PublishDefault](event, data) {
|
|
817
|
-
return this.dispatchKey(event, data);
|
|
818
|
-
}
|
|
819
|
-
dispatchKey(event, data) {
|
|
820
|
-
this.processSubscriber(event, data);
|
|
821
|
-
return this.emitSafe(event, ...[data]);
|
|
822
|
-
}
|
|
823
|
-
onListenerError(error, event) {
|
|
824
|
-
this.logger.error(`listener for ${String(event)} threw`, error);
|
|
825
|
-
}
|
|
826
|
-
async processSubscriber(subscriberName, data) {
|
|
827
|
-
const registrations = this.subscribersMap.get(subscriberName);
|
|
828
|
-
if (!registrations) return;
|
|
829
|
-
const pending = registrations.reduce((acc, entry) => {
|
|
830
|
-
if (entry.frequency === "once") {
|
|
831
|
-
if (this.executedOnce.has(entry)) return acc;
|
|
832
|
-
this.executedOnce.add(entry);
|
|
833
|
-
}
|
|
834
|
-
acc.push(this.runSubscriber(entry, subscriberName, data));
|
|
835
|
-
return acc;
|
|
836
|
-
}, []);
|
|
837
|
-
await Promise.allSettled(pending);
|
|
838
|
-
}
|
|
839
|
-
async runSubscriber(entry, subscriberName, data) {
|
|
840
|
-
let name = "<unresolved>";
|
|
841
|
-
try {
|
|
842
|
-
const Ctor = entry.ctor;
|
|
843
|
-
name = Ctor.name;
|
|
844
|
-
await new Ctor(data, this.core).execute();
|
|
845
|
-
} catch (err) {
|
|
846
|
-
this.logger.error(`Error in subscriber ${paint.mute(String(subscriberName))} handler ${paint.sky.bold(name)}:`, err);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
};
|
|
850
|
-
/** @internal */
|
|
851
|
-
function registrationFor(ctor) {
|
|
852
|
-
const meta = Reflect.getMetadata(SubscribeMetadataKey, ctor);
|
|
853
|
-
return {
|
|
854
|
-
keys: [meta.subscriber],
|
|
855
|
-
frequency: meta.frequency ?? "on",
|
|
856
|
-
ctor
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
/** @internal */
|
|
860
|
-
function busLoggerOf(bus) {
|
|
861
|
-
return bus[loggerSlot];
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
//#endregion
|
|
865
|
-
export { BuilderComponent as A, InteractionRouteKeys as C, clearStore as D, accessorStore as E, isBuilderComponentClass as M, bindBotColor as N, guardedAccessor as O, InteractionMiddlewareMetadataKey as S, WebhookUrlMetadataKey as T, EventMetadataKey as _, RegisterSubscriber as a, InteractionKind as b, VerifyWebhooks as c, WebhookLog as d, Subscriber as f, CommandMetadataKey as g, sendFlags as h, RegisterDefaults as i, RowComponent as j, contextMenuLeaves as k, WebhookUrl as l, deferFlags as m, busLoggerOf as n, RegisteredCount as o, ReportThrottle as p, registrationFor as r, UnregisterSubscriber as s, Bus as t, Subscribe as u, EventMiddlewareMetadataKey as v, SubscribeMetadataKey as w, InteractionMetadataKey as x, GatedMetadataKey as y };
|
|
866
|
-
//# sourceMappingURL=Bus-BxPa-Uvp.mjs.map
|