@seedcord/core 0.1.4 → 0.2.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -5
- package/dist/{subscribers-nEpbJj3Z.mjs → Bus-DcuUtOEL.mjs} +401 -31
- package/dist/Bus-DcuUtOEL.mjs.map +1 -0
- package/dist/{HmrModuleHandler-KxRP8lqJ.mjs → HmrModuleHandler-9AUfKXRl.mjs} +2 -2
- package/dist/{HmrModuleHandler-KxRP8lqJ.mjs.map → HmrModuleHandler-9AUfKXRl.mjs.map} +1 -1
- package/dist/{Plugin-CDAV7P9l.mjs → Plugin-Bkeqcjy6.mjs} +12 -3
- package/dist/Plugin-Bkeqcjy6.mjs.map +1 -0
- package/dist/{Plugin-qRc7Ch4g.d.mts → Plugin-Cl5P7-PO.d.mts} +19 -7
- package/dist/{cursor-D207VrRo.mjs → cursor-B07ArwDz.mjs} +4 -4
- package/dist/cursor-B07ArwDz.mjs.map +1 -0
- package/dist/{cursor-C77xH5QT.d.mts → cursor-__0_F4rW.d.mts} +10 -3
- package/dist/hmr.index.mjs +1 -1
- package/dist/{index-C0k7lRDi.d.mts → index-TYnQ-ztr.d.mts} +15 -4
- package/dist/index.d.mts +24 -11
- package/dist/index.mjs +26 -6
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +13 -18
- package/dist/internal.index.mjs +16 -48
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +6 -5
- package/dist/node-internal.index.mjs +54 -49
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/plugin.index.d.mts +1 -1
- package/dist/plugin.index.mjs +1 -1
- package/dist/subscribers-DuHg-bGT.mjs +3 -0
- package/package.json +25 -34
- package/dist/HmrModuleHandler-CxGOo1LT.cjs +0 -182
- package/dist/HmrModuleHandler-CxGOo1LT.cjs.map +0 -1
- package/dist/Plugin-CDAV7P9l.mjs.map +0 -1
- package/dist/Plugin-DVNh1kvX.cjs +0 -95
- package/dist/Plugin-DVNh1kvX.cjs.map +0 -1
- package/dist/cursor-D207VrRo.mjs.map +0 -1
- package/dist/cursor-DFgJ70_B.cjs +0 -1339
- package/dist/cursor-DFgJ70_B.cjs.map +0 -1
- package/dist/devChannel-Cdnmz6hd.cjs +0 -25
- package/dist/devChannel-Cdnmz6hd.cjs.map +0 -1
- package/dist/hmr.index.cjs +0 -4
- package/dist/hmr.index.d.cts +0 -1
- package/dist/index.cjs +0 -889
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1
- package/dist/internal.index.cjs +0 -717
- package/dist/internal.index.cjs.map +0 -1
- package/dist/internal.index.d.cts +0 -1
- package/dist/node-internal.index.cjs +0 -799
- package/dist/node-internal.index.cjs.map +0 -1
- package/dist/node-internal.index.d.cts +0 -1
- package/dist/phases-8qiiLKjS.cjs +0 -42
- package/dist/phases-8qiiLKjS.cjs.map +0 -1
- package/dist/plugin.index.cjs +0 -4
- package/dist/plugin.index.d.cts +0 -1
- package/dist/publishDefault-BCrMG9T8.mjs +0 -273
- package/dist/publishDefault-BCrMG9T8.mjs.map +0 -1
- package/dist/publishDefault-D52iT8mF.cjs +0 -368
- package/dist/publishDefault-D52iT8mF.cjs.map +0 -1
- package/dist/slashRouteLeaves-HVl6Ymr0.cjs +0 -28
- package/dist/slashRouteLeaves-HVl6Ymr0.cjs.map +0 -1
- package/dist/subscribers-6x4xw39Z.cjs +0 -531
- package/dist/subscribers-6x4xw39Z.cjs.map +0 -1
- package/dist/subscribers-nEpbJj3Z.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,12 +1,42 @@
|
|
|
1
|
-
<
|
|
1
|
+
<div align="center">
|
|
2
2
|
<picture>
|
|
3
3
|
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.seedcord.org/assets/wordmark-dark.webp" />
|
|
4
4
|
<img src="https://cdn.seedcord.org/assets/wordmark-light.webp" alt="seedcord" width="440" />
|
|
5
5
|
</picture>
|
|
6
|
-
</
|
|
6
|
+
</div>
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
<div align="center">
|
|
9
|
+
<h3>The whole Discord bot, wired and typed</h3>
|
|
10
|
+
<a href="https://seedcord.org">Website</a> ·
|
|
11
|
+
<a href="https://guide.seedcord.org">Guide</a> ·
|
|
12
|
+
<a href="https://docs.seedcord.org">Reference</a> ·
|
|
13
|
+
<a href="https://discord.gg/DzFxY58WXf">Discord</a>
|
|
14
|
+
</div>
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
<br />
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
<div align="center">
|
|
19
|
+
|
|
20
|
+
[](https://www.npmjs.com/package/@seedcord/core) [](https://nodejs.org) [](LICENSE)
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
## About
|
|
25
|
+
|
|
26
|
+
`@seedcord/core` is the foundational code both seedcord transports share. Command and context-menu injection, the interaction route decorators, the handler bases, gates, the startup and shutdown phases, and the plugin base all live here.
|
|
27
|
+
|
|
28
|
+
Nothing in it opens a connection to Discord. `@seedcord/gateway` and `@seedcord/http` each bind their own transport into these pieces.
|
|
29
|
+
|
|
30
|
+
Until v1.0.0, minor versions can break.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Your transport already re-exports all of this.
|
|
35
|
+
|
|
36
|
+
Install it directly when you are writing a plugin against the framework:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
pnpm add @seedcord/core
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`@seedcord/core/plugin` holds the base a plugin extends.
|
|
@@ -1,15 +1,313 @@
|
|
|
1
|
-
import { h as WebhookUrlMetadataKey, i as BuilderComponent, m as SubscribeMetadataKey, r as sendFlags, t as PublishDefault } from "./publishDefault-BCrMG9T8.mjs";
|
|
2
1
|
import "reflect-metadata";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
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";
|
|
3
|
+
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
4
|
+
import { Logger } from "@seedcord/logger";
|
|
5
|
+
import { ApplicationCommandType, InteractionContextType, MessageFlags, Routes, SeparatorSpacingSize } from "discord-api-types/v10";
|
|
6
|
+
import { SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
|
|
7
7
|
import { filterCirculars, stripAnsi, timestampFromSnowflake } from "@seedcord/utils";
|
|
8
8
|
import { TypedEventEmitter } from "@seedcord/event-emitter";
|
|
9
|
-
import chalk from "chalk";
|
|
10
9
|
import { Converters, Envapter } from "envapt";
|
|
11
10
|
import { DiscordAPIError, REST } from "@discordjs/rest";
|
|
12
11
|
|
|
12
|
+
//#region src/commands/contextMenuLeaves.ts
|
|
13
|
+
/**
|
|
14
|
+
* dedupes names across builders, since the same builder repeats once per guild.
|
|
15
|
+
* a user and a message command can share a name.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
function contextMenuLeaves(commands) {
|
|
20
|
+
const user = /* @__PURE__ */ new Set();
|
|
21
|
+
const message = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const command of commands) {
|
|
23
|
+
if (!(command instanceof ContextMenuCommandBuilder)) continue;
|
|
24
|
+
if (command.type === ApplicationCommandType.User) user.add(command.name);
|
|
25
|
+
else message.add(command.name);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
user,
|
|
29
|
+
message
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/metadataKeys.ts
|
|
35
|
+
let InteractionRoutes = /* @__PURE__ */ function(InteractionRoutes) {
|
|
36
|
+
InteractionRoutes["Slash"] = "slash";
|
|
37
|
+
InteractionRoutes["Button"] = "button";
|
|
38
|
+
InteractionRoutes["Modal"] = "modal";
|
|
39
|
+
InteractionRoutes["StringMenu"] = "stringMenu";
|
|
40
|
+
InteractionRoutes["UserMenu"] = "userMenu";
|
|
41
|
+
InteractionRoutes["RoleMenu"] = "roleMenu";
|
|
42
|
+
InteractionRoutes["ChannelMenu"] = "channelMenu";
|
|
43
|
+
InteractionRoutes["MentionableMenu"] = "mentionableMenu";
|
|
44
|
+
InteractionRoutes["MessageContextMenu"] = "messageContextMenu";
|
|
45
|
+
InteractionRoutes["UserContextMenu"] = "userContextMenu";
|
|
46
|
+
InteractionRoutes["Autocomplete"] = "autocomplete";
|
|
47
|
+
return InteractionRoutes;
|
|
48
|
+
}({});
|
|
49
|
+
const InteractionMetadataKey = Symbol("seedcord:interaction:metadata");
|
|
50
|
+
const InteractionRouteKeys = {
|
|
51
|
+
["slash"]: Symbol("seedcord:interaction:slash"),
|
|
52
|
+
["button"]: Symbol("seedcord:interaction:button"),
|
|
53
|
+
["modal"]: Symbol("seedcord:interaction:modal"),
|
|
54
|
+
["stringMenu"]: Symbol("seedcord:interaction:stringMenu"),
|
|
55
|
+
["userMenu"]: Symbol("seedcord:interaction:userMenu"),
|
|
56
|
+
["roleMenu"]: Symbol("seedcord:interaction:roleMenu"),
|
|
57
|
+
["channelMenu"]: Symbol("seedcord:interaction:channelMenu"),
|
|
58
|
+
["mentionableMenu"]: Symbol("seedcord:interaction:mentionableMenu"),
|
|
59
|
+
["messageContextMenu"]: Symbol("seedcord:interaction:messageContextMenu"),
|
|
60
|
+
["userContextMenu"]: Symbol("seedcord:interaction:userContextMenu"),
|
|
61
|
+
["autocomplete"]: Symbol("seedcord:interaction:autocomplete")
|
|
62
|
+
};
|
|
63
|
+
const MiddlewareMetadataKey = Symbol("seedcord:middleware:metadata");
|
|
64
|
+
const EventMetadataKey = Symbol("seedcord:event:metadata");
|
|
65
|
+
const CommandMetadataKey = Symbol("seedcord:command:metadata");
|
|
66
|
+
const GatedMetadataKey = Symbol("seedcord:gated:metadata");
|
|
67
|
+
const SubscribeMetadataKey = Symbol("seedcord:subscribe:metadata");
|
|
68
|
+
const WebhookUrlMetadataKey = Symbol("seedcord:webhookUrl:metadata");
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/components/botColorHolder.ts
|
|
72
|
+
const DEFAULT_COLOR = "Default";
|
|
73
|
+
let current = DEFAULT_COLOR;
|
|
74
|
+
function setBotColor(color) {
|
|
75
|
+
current = color ?? DEFAULT_COLOR;
|
|
76
|
+
}
|
|
77
|
+
function getBotColor() {
|
|
78
|
+
return current;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/components/builderTypes.ts
|
|
83
|
+
/**
|
|
84
|
+
* Available Discord.js builder classes for use with BuilderComponent for commands, embeds, modals, etc.
|
|
85
|
+
*
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
const BuilderTypes = {
|
|
89
|
+
command: SlashCommandBuilder,
|
|
90
|
+
context_menu: ContextMenuCommandBuilder,
|
|
91
|
+
subcommand: SlashCommandSubcommandBuilder,
|
|
92
|
+
group: SlashCommandSubcommandGroupBuilder,
|
|
93
|
+
embed: EmbedBuilder,
|
|
94
|
+
modal: ModalBuilder,
|
|
95
|
+
label: LabelBuilder,
|
|
96
|
+
text_input: TextInputBuilder,
|
|
97
|
+
file_upload: FileUploadBuilder,
|
|
98
|
+
checkbox: CheckboxBuilder,
|
|
99
|
+
checkbox_group: CheckboxGroupBuilder,
|
|
100
|
+
checkbox_group_option: CheckboxGroupOptionBuilder,
|
|
101
|
+
radio_group: RadioGroupBuilder,
|
|
102
|
+
radio_group_option: RadioGroupOptionBuilder,
|
|
103
|
+
button: ButtonBuilder,
|
|
104
|
+
menu_string: StringSelectMenuBuilder,
|
|
105
|
+
menu_option_string: StringSelectMenuOptionBuilder,
|
|
106
|
+
menu_user: UserSelectMenuBuilder,
|
|
107
|
+
menu_channel: ChannelSelectMenuBuilder,
|
|
108
|
+
menu_mentionable: MentionableSelectMenuBuilder,
|
|
109
|
+
menu_role: RoleSelectMenuBuilder,
|
|
110
|
+
container: ContainerBuilder,
|
|
111
|
+
text_display: TextDisplayBuilder,
|
|
112
|
+
file: FileBuilder,
|
|
113
|
+
media: MediaGalleryBuilder,
|
|
114
|
+
section: SectionBuilder,
|
|
115
|
+
separator: SeparatorBuilder
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Available Discord.js action row classes for use with RowComponent for Select Menus and Buttons
|
|
119
|
+
*
|
|
120
|
+
* @internal
|
|
121
|
+
*/
|
|
122
|
+
const RowTypes = {
|
|
123
|
+
button: ActionRowBuilder,
|
|
124
|
+
menu_string: ActionRowBuilder,
|
|
125
|
+
menu_user: ActionRowBuilder,
|
|
126
|
+
menu_channel: ActionRowBuilder,
|
|
127
|
+
menu_mentionable: ActionRowBuilder,
|
|
128
|
+
menu_role: ActionRowBuilder
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/components/colors.ts
|
|
133
|
+
const Colors = {
|
|
134
|
+
Aqua: 1752220,
|
|
135
|
+
Blue: 3447003,
|
|
136
|
+
Blurple: 5793266,
|
|
137
|
+
DarkAqua: 1146986,
|
|
138
|
+
DarkBlue: 2123412,
|
|
139
|
+
DarkButNotBlack: 2895667,
|
|
140
|
+
DarkerGrey: 8359053,
|
|
141
|
+
DarkGold: 12745742,
|
|
142
|
+
DarkGreen: 2067276,
|
|
143
|
+
DarkGrey: 9936031,
|
|
144
|
+
DarkNavy: 2899536,
|
|
145
|
+
DarkOrange: 11027200,
|
|
146
|
+
DarkPurple: 7419530,
|
|
147
|
+
DarkRed: 10038562,
|
|
148
|
+
DarkVividPink: 11342935,
|
|
149
|
+
Default: 0,
|
|
150
|
+
Fuchsia: 15418782,
|
|
151
|
+
Gold: 15844367,
|
|
152
|
+
Green: 5763719,
|
|
153
|
+
Grey: 9807270,
|
|
154
|
+
Greyple: 10070709,
|
|
155
|
+
LightGrey: 12370112,
|
|
156
|
+
LuminousVividPink: 15277667,
|
|
157
|
+
Navy: 3426654,
|
|
158
|
+
NotQuiteBlack: 2303786,
|
|
159
|
+
Orange: 15105570,
|
|
160
|
+
Purple: 10181046,
|
|
161
|
+
Red: 15548997,
|
|
162
|
+
White: 16777215,
|
|
163
|
+
Yellow: 16705372
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/components/resolveColor.ts
|
|
168
|
+
const MAX_COLOR = 16777215;
|
|
169
|
+
const RED_SHIFT = 16;
|
|
170
|
+
const GREEN_SHIFT = 8;
|
|
171
|
+
const palette = Colors;
|
|
172
|
+
/**
|
|
173
|
+
* Resolves a {@link BotColor} to the integer Discord expects.
|
|
174
|
+
*
|
|
175
|
+
* Ported from discord.js (Apache-2.0), `resolveColor` in src/util/Util.js.
|
|
176
|
+
*/
|
|
177
|
+
function resolveColor(color) {
|
|
178
|
+
let resolved;
|
|
179
|
+
if (typeof color === "string") {
|
|
180
|
+
if (color === "Random") return Math.floor(Math.random() * 16777216);
|
|
181
|
+
if (color === "Default") return 0;
|
|
182
|
+
if (/^#?[\da-f]{6}$/i.test(color)) return Number.parseInt(color.replace("#", ""), 16);
|
|
183
|
+
resolved = palette[color];
|
|
184
|
+
} else if (typeof color === "number") resolved = color;
|
|
185
|
+
else resolved = (color[0] << RED_SHIFT) + (color[1] << GREEN_SHIFT) + color[2];
|
|
186
|
+
if (resolved === void 0 || !Number.isSafeInteger(resolved)) throw new SeedcordTypeError(SeedcordErrorCode.ColorUnresolvable, [JSON.stringify(color)]);
|
|
187
|
+
if (resolved < 0 || resolved > MAX_COLOR) throw new SeedcordRangeError(SeedcordErrorCode.ColorOutOfRange);
|
|
188
|
+
return resolved;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/components/Component.ts
|
|
193
|
+
/** @internal */
|
|
194
|
+
var BaseComponent = class {
|
|
195
|
+
_component;
|
|
196
|
+
constructor(ComponentClass) {
|
|
197
|
+
this._component = new ComponentClass();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The wrapped builder, for calling Discord.js methods like setTitle() and setDescription() inside a subclass.
|
|
201
|
+
*
|
|
202
|
+
* @example this.instance.setTitle('My Modal')
|
|
203
|
+
*/
|
|
204
|
+
get instance() {
|
|
205
|
+
return this._component;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Base class for Discord.js builder components
|
|
210
|
+
*
|
|
211
|
+
* Wraps Discord.js builders (SlashCommandBuilder, EmbedBuilder, etc.) with
|
|
212
|
+
* Seedcord-specific defaults and helper methods.
|
|
213
|
+
*
|
|
214
|
+
* @typeParam BuilderKey - The type of Discord.js builder being wrapped
|
|
215
|
+
*/
|
|
216
|
+
var BuilderComponent = class extends BaseComponent {
|
|
217
|
+
type;
|
|
218
|
+
colorApplied = false;
|
|
219
|
+
constructor(type) {
|
|
220
|
+
const ComponentClass = BuilderTypes[type];
|
|
221
|
+
super(ComponentClass);
|
|
222
|
+
this.type = type;
|
|
223
|
+
if (this.instance instanceof SlashCommandBuilder || this.instance instanceof ContextMenuCommandBuilder) this.instance.setContexts(InteractionContextType.Guild);
|
|
224
|
+
}
|
|
225
|
+
get component() {
|
|
226
|
+
this.applyBotColor();
|
|
227
|
+
return this.instance;
|
|
228
|
+
}
|
|
229
|
+
applyBotColor() {
|
|
230
|
+
if (this.colorApplied) return;
|
|
231
|
+
this.colorApplied = true;
|
|
232
|
+
const color = getBotColor();
|
|
233
|
+
if (this.instance instanceof EmbedBuilder) {
|
|
234
|
+
if (this.instance.data.color === void 0) this.instance.setColor(resolveColor(color));
|
|
235
|
+
} else if (this.instance instanceof ContainerBuilder) {
|
|
236
|
+
const accent = this.instance.data.accent_color;
|
|
237
|
+
if (accent === null || accent === void 0) this.instance.setAccentColor(color === "Default" ? void 0 : resolveColor(color));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Base class for Discord action row components
|
|
243
|
+
*
|
|
244
|
+
* Wraps Discord.js action row builder with Seedcord-specific defaults and helper methods.
|
|
245
|
+
*
|
|
246
|
+
* @typeParam RowKey - The Discord.js action row type being wrapped
|
|
247
|
+
*/
|
|
248
|
+
var RowComponent = class extends BaseComponent {
|
|
249
|
+
type;
|
|
250
|
+
constructor(type) {
|
|
251
|
+
const ComponentClass = RowTypes[type];
|
|
252
|
+
super(ComponentClass);
|
|
253
|
+
this.type = type;
|
|
254
|
+
}
|
|
255
|
+
get component() {
|
|
256
|
+
return this.instance;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/reply/flags.ts
|
|
262
|
+
function sendFlags(opts) {
|
|
263
|
+
let flags = MessageFlags.IsComponentsV2;
|
|
264
|
+
if (opts?.ephemeral ?? true) flags |= MessageFlags.Ephemeral;
|
|
265
|
+
if (opts?.silent) flags |= MessageFlags.SuppressNotifications;
|
|
266
|
+
return flags;
|
|
267
|
+
}
|
|
268
|
+
function deferFlags(opts) {
|
|
269
|
+
return opts?.ephemeral ?? true ? MessageFlags.Ephemeral : 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region src/subscribers/ReportThrottle.ts
|
|
274
|
+
var ReportThrottle = class ReportThrottle {
|
|
275
|
+
windowMs;
|
|
276
|
+
now;
|
|
277
|
+
static perCore = /* @__PURE__ */ new WeakMap();
|
|
278
|
+
static for(core) {
|
|
279
|
+
let throttle = ReportThrottle.perCore.get(core);
|
|
280
|
+
if (!throttle) {
|
|
281
|
+
throttle = new ReportThrottle();
|
|
282
|
+
ReportThrottle.perCore.set(core, throttle);
|
|
283
|
+
}
|
|
284
|
+
return throttle;
|
|
285
|
+
}
|
|
286
|
+
windows = /* @__PURE__ */ new Map();
|
|
287
|
+
constructor(windowMs = 6e4, now = () => Date.now()) {
|
|
288
|
+
this.windowMs = windowMs;
|
|
289
|
+
this.now = now;
|
|
290
|
+
}
|
|
291
|
+
take(key) {
|
|
292
|
+
const window = this.windows.get(key);
|
|
293
|
+
if (window && this.now() - window.lastSentAt < this.windowMs) {
|
|
294
|
+
window.suppressedSinceSend += 1;
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
const suppressed = window?.suppressedSinceSend ?? 0;
|
|
298
|
+
this.windows.set(key, {
|
|
299
|
+
lastSentAt: this.now(),
|
|
300
|
+
suppressedSinceSend: 0
|
|
301
|
+
});
|
|
302
|
+
return suppressed;
|
|
303
|
+
}
|
|
304
|
+
restore(key, suppressed) {
|
|
305
|
+
const window = this.windows.get(key);
|
|
306
|
+
if (window) window.suppressedSinceSend += suppressed + 1;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
//#endregion
|
|
13
311
|
//#region src/subscribers/Subscriber.ts
|
|
14
312
|
/**
|
|
15
313
|
* Abstract base class for handling application subscribers.
|
|
@@ -51,17 +349,43 @@ function jsonAttachment(name, description, data) {
|
|
|
51
349
|
data: new TextEncoder().encode(JSON.stringify(content, void 0, 2))
|
|
52
350
|
};
|
|
53
351
|
}
|
|
54
|
-
function
|
|
352
|
+
function breakBackticks(text) {
|
|
55
353
|
return text.replaceAll("```", "```");
|
|
56
354
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
355
|
+
const REPORT_BUDGET = 1800;
|
|
356
|
+
function body(value) {
|
|
357
|
+
const raw = Error.isError(value) ? value.stack ?? `${value.name}: ${value.message}` : String(value);
|
|
358
|
+
return breakBackticks(stripAnsi(raw));
|
|
359
|
+
}
|
|
360
|
+
function clamp(text, budget) {
|
|
361
|
+
if (text.length <= budget) return text;
|
|
362
|
+
return budget > 0 ? `${text.slice(0, budget - 1)}…` : "";
|
|
363
|
+
}
|
|
364
|
+
function omittedLine(count) {
|
|
365
|
+
return `\n\nand ${count} more failure${count === 1 ? "" : "s"}`;
|
|
366
|
+
}
|
|
367
|
+
function memberReport(members, budget) {
|
|
368
|
+
const room = Math.max(budget - omittedLine(members.length).length, 0);
|
|
369
|
+
const share = Math.floor(room / members.length);
|
|
370
|
+
let report = "";
|
|
371
|
+
let shown = 0;
|
|
372
|
+
for (const [index, member] of members.entries()) {
|
|
373
|
+
const heading = `\n\nFailure ${index + 1} of ${members.length}:\n`;
|
|
374
|
+
const entry = heading + clamp(body(member), Math.max(share - heading.length, 0));
|
|
375
|
+
if (report.length + entry.length > room) break;
|
|
376
|
+
report += entry;
|
|
377
|
+
shown++;
|
|
62
378
|
}
|
|
63
|
-
const
|
|
64
|
-
return
|
|
379
|
+
const hidden = members.length - shown;
|
|
380
|
+
return hidden > 0 ? report + omittedLine(hidden) : report;
|
|
381
|
+
}
|
|
382
|
+
function errorReport(error) {
|
|
383
|
+
let head = body(error);
|
|
384
|
+
if (Error.isError(error.cause)) head += `\n\nCaused by:\n${body(error.cause)}`;
|
|
385
|
+
const members = error instanceof AggregateError ? error.errors : [];
|
|
386
|
+
if (members.length === 0) return clamp(head, REPORT_BUDGET);
|
|
387
|
+
const report = clamp(head, REPORT_BUDGET - omittedLine(members.length).length);
|
|
388
|
+
return report + memberReport(members, REPORT_BUDGET - report.length);
|
|
65
389
|
}
|
|
66
390
|
var WebhookSeparator = class extends BuilderComponent {
|
|
67
391
|
constructor() {
|
|
@@ -91,7 +415,7 @@ var WebhookSender = class {
|
|
|
91
415
|
}
|
|
92
416
|
}
|
|
93
417
|
async send(options) {
|
|
94
|
-
const { flags, username, components, files } = options;
|
|
418
|
+
const { flags, username, avatarUrl, components, files } = options;
|
|
95
419
|
await this.rest.post(this.route, {
|
|
96
420
|
auth: false,
|
|
97
421
|
query: new URLSearchParams({
|
|
@@ -101,6 +425,7 @@ var WebhookSender = class {
|
|
|
101
425
|
body: {
|
|
102
426
|
flags,
|
|
103
427
|
username,
|
|
428
|
+
...avatarUrl && { avatar_url: avatarUrl },
|
|
104
429
|
components: components.map((component) => component.toJSON()),
|
|
105
430
|
...files?.length && { attachments: files.map((file, id) => ({
|
|
106
431
|
id,
|
|
@@ -130,22 +455,37 @@ var WebhookSender = class {
|
|
|
130
455
|
*/
|
|
131
456
|
var WebhookLog = class WebhookLog extends Subscriber {
|
|
132
457
|
static senders = /* @__PURE__ */ new Map();
|
|
458
|
+
/**
|
|
459
|
+
* Events sharing this key collapse into one card per minute. Return `null` to send every event.
|
|
460
|
+
*/
|
|
461
|
+
throttleKey() {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
133
464
|
async execute() {
|
|
134
465
|
const url = WebhookLog.urlOf(WebhookLog.envKeyOf(this.constructor));
|
|
135
466
|
if (url === null) {
|
|
136
|
-
this.logger.warn(`${
|
|
467
|
+
this.logger.warn(`${paint.sky.bold(this.constructor.name)} has no webhook url set, this reporter is disabled`);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const key = this.throttleKey();
|
|
471
|
+
const throttle = ReportThrottle.for(this.core);
|
|
472
|
+
const suppressed = key === null ? 0 : throttle.take(key);
|
|
473
|
+
if (suppressed === null) {
|
|
474
|
+
this.logger.debug(`counted a repeat into the next ${paint.sky.bold(this.constructor.name)} card`);
|
|
137
475
|
return;
|
|
138
476
|
}
|
|
139
|
-
const { username, components, files } = await this.report();
|
|
140
477
|
try {
|
|
478
|
+
const { username, avatarUrl, components, files } = await this.report(suppressed);
|
|
141
479
|
await WebhookLog.senderFor(url).send({
|
|
142
480
|
flags: sendFlags({ ephemeral: false }),
|
|
143
481
|
username: username ?? this.constructor.name,
|
|
482
|
+
avatarUrl,
|
|
144
483
|
components,
|
|
145
484
|
files
|
|
146
485
|
});
|
|
147
486
|
} catch (error) {
|
|
148
|
-
|
|
487
|
+
if (key !== null) throttle.restore(key, suppressed);
|
|
488
|
+
this.logger.error("Webhook report failed", error);
|
|
149
489
|
}
|
|
150
490
|
}
|
|
151
491
|
/** @internal */
|
|
@@ -249,20 +589,28 @@ function __decorate(decorators, target, key, desc) {
|
|
|
249
589
|
//#endregion
|
|
250
590
|
//#region src/subscribers/default/HandledException.ts
|
|
251
591
|
let HandledException = class HandledException extends WebhookLog {
|
|
252
|
-
|
|
592
|
+
throttleKey() {
|
|
593
|
+
return `handledException:${this.data.routeId}:${this.data.denial.name}`;
|
|
594
|
+
}
|
|
595
|
+
report(suppressed) {
|
|
253
596
|
return {
|
|
254
597
|
username: "Handled Exception",
|
|
255
|
-
components: [new HandledExceptionContainer(this.data).component],
|
|
598
|
+
components: [new HandledExceptionContainer(this.data, suppressed).component],
|
|
256
599
|
files: [jsonAttachment("source.json", "The raw source the fault came from", this.data.source.raw)]
|
|
257
600
|
};
|
|
258
601
|
}
|
|
259
602
|
};
|
|
260
603
|
HandledException = __decorate([Subscribe("handledException"), WebhookUrl("HANDLED_EXCEPTION_WEBHOOK_URL")], HandledException);
|
|
261
604
|
var HandledExceptionContainer = class extends BuilderComponent {
|
|
262
|
-
constructor(data) {
|
|
605
|
+
constructor(data, suppressed) {
|
|
263
606
|
super("container");
|
|
264
607
|
const { denial, uuid, source } = data;
|
|
265
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(faultSummary(denial, source))).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${
|
|
608
|
+
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"));
|
|
609
|
+
this.addSuppressedCount(suppressed);
|
|
610
|
+
}
|
|
611
|
+
addSuppressedCount(suppressed) {
|
|
612
|
+
if (suppressed === 0) return;
|
|
613
|
+
this.instance.addTextDisplayComponents((text) => text.setContent(`-# ${suppressed} more of these since the last report`));
|
|
266
614
|
}
|
|
267
615
|
};
|
|
268
616
|
function faultSummary(denial, source) {
|
|
@@ -286,23 +634,34 @@ function causeStack(denial) {
|
|
|
286
634
|
//#endregion
|
|
287
635
|
//#region src/subscribers/default/UnknownException.ts
|
|
288
636
|
let UnknownException = class UnknownException extends WebhookLog {
|
|
289
|
-
|
|
637
|
+
throttleKey() {
|
|
638
|
+
return `unknownException:${this.data.routeId}:${nameOf(this.data.error)}`;
|
|
639
|
+
}
|
|
640
|
+
report(suppressed) {
|
|
290
641
|
const { metadata } = this.data;
|
|
291
642
|
return {
|
|
292
643
|
username: "Unknown Exception",
|
|
293
|
-
components: [new UnhandledErrorContainer(this.data).component],
|
|
644
|
+
components: [new UnhandledErrorContainer(this.data, suppressed).component],
|
|
294
645
|
files: metadata ? [jsonAttachment("metadata.json", "Metadata associated with the error", metadata)] : void 0
|
|
295
646
|
};
|
|
296
647
|
}
|
|
297
648
|
};
|
|
298
649
|
UnknownException = __decorate([Subscribe("unknownException"), WebhookUrl("UNKNOWN_EXCEPTION_WEBHOOK_URL")], UnknownException);
|
|
650
|
+
function nameOf(error) {
|
|
651
|
+
return error.name === "Error" ? error.constructor.name : error.name;
|
|
652
|
+
}
|
|
299
653
|
var UnhandledErrorContainer = class extends BuilderComponent {
|
|
300
|
-
constructor(data) {
|
|
654
|
+
constructor(data, suppressed) {
|
|
301
655
|
super("container");
|
|
302
656
|
const { uuid, error, guild, user, metadata } = data;
|
|
303
657
|
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)}\`\`\``));
|
|
304
658
|
this.addTimestampsIfAvailable(error);
|
|
305
659
|
this.addMetadataIfAvailable(metadata);
|
|
660
|
+
this.addSuppressedCount(suppressed);
|
|
661
|
+
}
|
|
662
|
+
addSuppressedCount(suppressed) {
|
|
663
|
+
if (suppressed === 0) return;
|
|
664
|
+
this.instance.addTextDisplayComponents((text) => text.setContent(`-# ${suppressed} more of these since the last report`));
|
|
306
665
|
}
|
|
307
666
|
addTimestampsIfAvailable(error) {
|
|
308
667
|
if (!(error instanceof DiscordAPIError)) return;
|
|
@@ -321,16 +680,23 @@ var UnhandledErrorContainer = class extends BuilderComponent {
|
|
|
321
680
|
}
|
|
322
681
|
};
|
|
323
682
|
|
|
683
|
+
//#endregion
|
|
684
|
+
//#region src/subscribers/publishDefault.ts
|
|
685
|
+
/** @internal PublishableKey excludes this key so publish() never emits it externally. */
|
|
686
|
+
const PublishDefault = Symbol("seedcord.publishDefault");
|
|
687
|
+
|
|
324
688
|
//#endregion
|
|
325
689
|
//#region src/subscribers/Bus.ts
|
|
690
|
+
const loggerSlot = Symbol("seedcord:bus:logger");
|
|
326
691
|
/**
|
|
327
692
|
* Registers and dispatches subscribers. Subscribers run on a programmatic or framework publish.
|
|
328
693
|
* Accessed via `core.bus`. Do not construct it directly.
|
|
329
694
|
*/
|
|
330
695
|
var Bus = class extends TypedEventEmitter {
|
|
331
696
|
core;
|
|
332
|
-
/** @internal */
|
|
333
697
|
logger = new Logger("Subscribers", { channel: "subscribers" });
|
|
698
|
+
/** @internal */
|
|
699
|
+
[loggerSlot] = this.logger;
|
|
334
700
|
subscribersMap = /* @__PURE__ */ new Map();
|
|
335
701
|
executedOnce = /* @__PURE__ */ new Set();
|
|
336
702
|
webhookProbes = /* @__PURE__ */ new Map();
|
|
@@ -374,7 +740,7 @@ var Bus = class extends TypedEventEmitter {
|
|
|
374
740
|
await Promise.all([...this.webhookProbes].map(async ([url, envKeys]) => {
|
|
375
741
|
const result = await WebhookLog.senderFor(url).verify();
|
|
376
742
|
if (result === "missing") missing.push(...envKeys);
|
|
377
|
-
else if (result === "unreachable") this.logger.warn(`could not verify webhook at ${paint.
|
|
743
|
+
else if (result === "unreachable") this.logger.warn(`could not verify webhook at ${envKeys.map((key) => paint.amber.bold(key)).join(", ")}`);
|
|
378
744
|
}));
|
|
379
745
|
if (missing.length > 0) throw new SeedcordError(SeedcordErrorCode.ConfigWebhookNotFound, [missing.join(", ")]);
|
|
380
746
|
}
|
|
@@ -383,7 +749,7 @@ var Bus = class extends TypedEventEmitter {
|
|
|
383
749
|
const envKey = WebhookLog.envKeyOf(ctor);
|
|
384
750
|
const url = WebhookLog.urlOf(envKey);
|
|
385
751
|
if (url === null) {
|
|
386
|
-
this.logger.warn(`${
|
|
752
|
+
this.logger.warn(`${paint.sky.bold(ctor.name)} disabled, ${paint.amber.bold(envKey)} is not set`);
|
|
387
753
|
return false;
|
|
388
754
|
}
|
|
389
755
|
const envKeys = this.webhookProbes.get(url) ?? [];
|
|
@@ -442,7 +808,7 @@ var Bus = class extends TypedEventEmitter {
|
|
|
442
808
|
name = Ctor.name;
|
|
443
809
|
await new Ctor(data, this.core).execute();
|
|
444
810
|
} catch (err) {
|
|
445
|
-
this.logger.error(`Error in subscriber ${String(subscriberName)} handler ${paint.sky.bold(name)}:`, err);
|
|
811
|
+
this.logger.error(`Error in subscriber ${paint.mute(String(subscriberName))} handler ${paint.sky.bold(name)}:`, err);
|
|
446
812
|
}
|
|
447
813
|
}
|
|
448
814
|
};
|
|
@@ -456,7 +822,11 @@ function registrationFor(ctor) {
|
|
|
456
822
|
ctor
|
|
457
823
|
};
|
|
458
824
|
}
|
|
825
|
+
/** @internal */
|
|
826
|
+
function busLoggerOf(bus) {
|
|
827
|
+
return bus[loggerSlot];
|
|
828
|
+
}
|
|
459
829
|
|
|
460
830
|
//#endregion
|
|
461
|
-
export {
|
|
462
|
-
//# sourceMappingURL=
|
|
831
|
+
export { WebhookUrlMetadataKey as C, SubscribeMetadataKey as S, GatedMetadataKey as _, WebhookUrl as a, InteractionRoutes as b, Subscriber as c, sendFlags as d, BuilderComponent as f, EventMetadataKey as g, CommandMetadataKey as h, PublishDefault as i, ReportThrottle as l, setBotColor as m, busLoggerOf as n, Subscribe as o, RowComponent as p, registrationFor as r, WebhookLog as s, Bus as t, deferFlags as u, InteractionMetadataKey as v, contextMenuLeaves as w, MiddlewareMetadataKey as x, InteractionRouteKeys as y };
|
|
832
|
+
//# sourceMappingURL=Bus-DcuUtOEL.mjs.map
|