@seedcord/core 0.1.0-next.1 → 0.1.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CustomId-XgZ2KDgT.mjs → CustomId-Bq6WagiA.mjs} +196 -88
- package/dist/CustomId-Bq6WagiA.mjs.map +1 -0
- package/dist/{CustomId-CqiuEllp.cjs → CustomId-DQ6ZH4s-.cjs} +321 -87
- package/dist/CustomId-DQ6ZH4s-.cjs.map +1 -0
- package/dist/CustomId-Ddwg48VY.d.mts +654 -0
- package/dist/index.cjs +323 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +201 -41
- package/dist/index.mjs +316 -4
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.cjs +39 -1
- package/dist/internal.index.cjs.map +1 -1
- package/dist/internal.index.d.mts +60 -41
- package/dist/internal.index.mjs +22 -2
- package/dist/internal.index.mjs.map +1 -1
- package/package.json +7 -3
- package/dist/CustomId-BJF6mbJs.d.mts +0 -317
- package/dist/CustomId-CqiuEllp.cjs.map +0 -1
- package/dist/CustomId-XgZ2KDgT.mjs.map +0 -1
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
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";
|
|
2
|
+
import { Snowflake } from "discord-api-types/v10";
|
|
3
|
+
import { Config, IRateLimiter, RenderContext, ReplyResponse } from "@seedcord/types";
|
|
4
|
+
|
|
5
|
+
//#region src/components/builderTypes.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Available Discord.js builder classes for use with BuilderComponent for commands, embeds, modals, etc.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
declare const BuilderTypes: {
|
|
12
|
+
command: typeof SlashCommandBuilder;
|
|
13
|
+
context_menu: typeof ContextMenuCommandBuilder;
|
|
14
|
+
subcommand: typeof SlashCommandSubcommandBuilder;
|
|
15
|
+
group: typeof SlashCommandSubcommandGroupBuilder;
|
|
16
|
+
embed: typeof EmbedBuilder;
|
|
17
|
+
modal: typeof ModalBuilder;
|
|
18
|
+
label: typeof LabelBuilder;
|
|
19
|
+
text_input: typeof TextInputBuilder;
|
|
20
|
+
file_upload: typeof FileUploadBuilder;
|
|
21
|
+
checkbox: typeof CheckboxBuilder;
|
|
22
|
+
checkbox_group: typeof CheckboxGroupBuilder;
|
|
23
|
+
checkbox_group_option: typeof CheckboxGroupOptionBuilder;
|
|
24
|
+
radio_group: typeof RadioGroupBuilder;
|
|
25
|
+
radio_group_option: typeof RadioGroupOptionBuilder;
|
|
26
|
+
button: typeof ButtonBuilder;
|
|
27
|
+
menu_string: typeof StringSelectMenuBuilder;
|
|
28
|
+
menu_option_string: typeof StringSelectMenuOptionBuilder;
|
|
29
|
+
menu_user: typeof UserSelectMenuBuilder;
|
|
30
|
+
menu_channel: typeof ChannelSelectMenuBuilder;
|
|
31
|
+
menu_mentionable: typeof MentionableSelectMenuBuilder;
|
|
32
|
+
menu_role: typeof RoleSelectMenuBuilder;
|
|
33
|
+
container: typeof ContainerBuilder;
|
|
34
|
+
text_display: typeof TextDisplayBuilder;
|
|
35
|
+
file: typeof FileBuilder;
|
|
36
|
+
media: typeof MediaGalleryBuilder;
|
|
37
|
+
section: typeof SectionBuilder;
|
|
38
|
+
separator: typeof SeparatorBuilder;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Available Discord.js action row classes for use with RowComponent for Select Menus and Buttons
|
|
42
|
+
*
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
declare const RowTypes: {
|
|
46
|
+
button: typeof ActionRowBuilder<ButtonBuilder>;
|
|
47
|
+
menu_string: typeof ActionRowBuilder<StringSelectMenuBuilder>;
|
|
48
|
+
menu_user: typeof ActionRowBuilder<UserSelectMenuBuilder>;
|
|
49
|
+
menu_channel: typeof ActionRowBuilder<ChannelSelectMenuBuilder>;
|
|
50
|
+
menu_mentionable: typeof ActionRowBuilder<MentionableSelectMenuBuilder>;
|
|
51
|
+
menu_role: typeof ActionRowBuilder<RoleSelectMenuBuilder>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Available Discord.js builder types for use with BuilderComponent
|
|
55
|
+
*/
|
|
56
|
+
type BuilderType = keyof typeof BuilderTypes;
|
|
57
|
+
/**
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
type InstantiatedBuilder<BuilderKey extends BuilderType> = InstanceType<(typeof BuilderTypes)[BuilderKey]>;
|
|
61
|
+
/**
|
|
62
|
+
* Available Discord.js action row types for use with RowComponent
|
|
63
|
+
*/
|
|
64
|
+
type RowType = keyof typeof RowTypes;
|
|
65
|
+
/**
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
type InstantiatedActionRow<RowKey extends RowType> = InstanceType<(typeof RowTypes)[RowKey]>;
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/components/Component.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* Base class for Discord component wrappers.
|
|
73
|
+
*
|
|
74
|
+
* @typeParam TComponent - The Discord.js component type being wrapped
|
|
75
|
+
*
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
declare abstract class BaseComponent<TComponent> {
|
|
79
|
+
private readonly _component;
|
|
80
|
+
protected constructor(ComponentClass: new () => TComponent);
|
|
81
|
+
/**
|
|
82
|
+
* Returns the live builder, ready to send in a Discord message or nest in another component.
|
|
83
|
+
*
|
|
84
|
+
* Configure it through `this.instance`. Reading this can apply the bot color (see
|
|
85
|
+
* {@link BuilderComponent}), so it has a side effect.
|
|
86
|
+
* @example new SomeComponent().component
|
|
87
|
+
*/
|
|
88
|
+
abstract get component(): InstantiatedBuilder<BuilderType> | InstantiatedActionRow<RowType>;
|
|
89
|
+
/**
|
|
90
|
+
* The wrapped builder, for calling Discord.js methods like setTitle() and setDescription() inside a subclass.
|
|
91
|
+
*
|
|
92
|
+
* @example this.instance.setTitle('My Modal')
|
|
93
|
+
*/
|
|
94
|
+
protected get instance(): TComponent;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Base class for Discord.js builder components
|
|
98
|
+
*
|
|
99
|
+
* Wraps Discord.js builders (SlashCommandBuilder, EmbedBuilder, etc.) with
|
|
100
|
+
* Seedcord-specific defaults and helper methods.
|
|
101
|
+
*
|
|
102
|
+
* @typeParam BuilderKey - The type of Discord.js builder being wrapped
|
|
103
|
+
*/
|
|
104
|
+
declare abstract class BuilderComponent<BuilderKey extends BuilderType> extends BaseComponent<InstantiatedBuilder<BuilderKey>> {
|
|
105
|
+
readonly type: BuilderKey;
|
|
106
|
+
private colorApplied;
|
|
107
|
+
protected constructor(type: BuilderKey);
|
|
108
|
+
get component(): InstantiatedBuilder<BuilderKey>;
|
|
109
|
+
private applyBotColor;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Base class for Discord action row components
|
|
113
|
+
*
|
|
114
|
+
* Wraps Discord.js action row builder with Seedcord-specific defaults and helper methods.
|
|
115
|
+
*
|
|
116
|
+
* @typeParam RowKey - The Discord.js action row type being wrapped
|
|
117
|
+
*/
|
|
118
|
+
declare abstract class RowComponent<RowKey extends RowType> extends BaseComponent<InstantiatedActionRow<RowKey>> {
|
|
119
|
+
readonly type: RowKey;
|
|
120
|
+
protected constructor(type: RowKey);
|
|
121
|
+
get component(): InstantiatedActionRow<RowKey>;
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region ../../node_modules/.pnpm/type-fest@5.7.0/node_modules/type-fest/source/basic.d.ts
|
|
125
|
+
/**
|
|
126
|
+
Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
|
|
127
|
+
|
|
128
|
+
@category Class
|
|
129
|
+
*/
|
|
130
|
+
type Constructor<T, Arguments extends unknown[] = any[]> = new (...arguments_: Arguments) => T;
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region ../../node_modules/.pnpm/type-fest@5.7.0/node_modules/type-fest/source/non-empty-tuple.d.ts
|
|
133
|
+
/**
|
|
134
|
+
Matches any non-empty tuple.
|
|
135
|
+
|
|
136
|
+
@example
|
|
137
|
+
```
|
|
138
|
+
import type {NonEmptyTuple} from 'type-fest';
|
|
139
|
+
|
|
140
|
+
const sum = (...numbers: NonEmptyTuple<number>) => numbers.reduce((total, value) => total + value, 0);
|
|
141
|
+
|
|
142
|
+
sum(1, 2, 3);
|
|
143
|
+
// Ok
|
|
144
|
+
|
|
145
|
+
// @ts-expect-error
|
|
146
|
+
sum();
|
|
147
|
+
// Error: Expected at least 1 arguments, but got 0.
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
@see {@link RequireAtLeastOne} for objects
|
|
151
|
+
|
|
152
|
+
@category Array
|
|
153
|
+
*/
|
|
154
|
+
type NonEmptyTuple<T = unknown> = readonly [T, ...T[]];
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/decorators/Command.d.ts
|
|
157
|
+
type CommandCtor = Constructor<BuilderComponent<'command' | 'context_menu'>>;
|
|
158
|
+
interface GlobalMeta {
|
|
159
|
+
scope: 'global';
|
|
160
|
+
}
|
|
161
|
+
interface GuildMeta {
|
|
162
|
+
scope: 'guild';
|
|
163
|
+
guilds: string[];
|
|
164
|
+
}
|
|
165
|
+
type CommandMeta = GlobalMeta | GuildMeta;
|
|
166
|
+
/**
|
|
167
|
+
* Attaches deployment metadata so the framework registers this command globally or in specific guilds.
|
|
168
|
+
*
|
|
169
|
+
* @param scope - Must be 'global' for global registration
|
|
170
|
+
* @decorator
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* \@RegisterCommand('global')
|
|
174
|
+
* class PingCommand extends BuilderComponent<'command'> {} // or BuilderComponent<'context_menu'>
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
declare function RegisterCommand(scope: 'global'): (ctor: CommandCtor) => void;
|
|
178
|
+
/**
|
|
179
|
+
* Registers a command for specific guild deployment.
|
|
180
|
+
*
|
|
181
|
+
* @param scope - Must be 'guild' for guild-specific registration
|
|
182
|
+
* @param guilds - Array of guild IDs where the command should be registered
|
|
183
|
+
* @decorator
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* \@RegisterCommand('guild', ['123456789'])
|
|
187
|
+
* class AdminCommand extends BuilderComponent<'command'> {} // or BuilderComponent<'context_menu'>
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
declare function RegisterCommand(scope: 'guild', guilds: string[]): (ctor: CommandCtor) => void;
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/interfaces/CoreBase.d.ts
|
|
193
|
+
/**
|
|
194
|
+
* The transport-agnostic slice of the running framework, what core code (gates, notices, plugins'
|
|
195
|
+
* agnostic surface) accesses through `ctx.core`. Each transport's `Core` extends it, the gateway
|
|
196
|
+
* adds `bot` and `bus`.
|
|
197
|
+
*/
|
|
198
|
+
interface CoreBase {
|
|
199
|
+
/** Transports narrow this to their own config type. */
|
|
200
|
+
readonly config: Config;
|
|
201
|
+
/** Sliding-window rate limiting, the `Cooldown` gate's backend and a public direct-use API. */
|
|
202
|
+
readonly rateLimiter: IRateLimiter;
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/gates/Gate.d.ts
|
|
206
|
+
/**
|
|
207
|
+
* The transport-agnostic gate context, the scalar identity of the acting user resolvable from any
|
|
208
|
+
* transport's payload. Every field except `core` is nullable, since an event or DM may carry no
|
|
209
|
+
* guild or member. Each transport extends it with its own rich arm. For e.g., the gateway adds the djs
|
|
210
|
+
* `user`/`guild`/`member` objects and the live interaction or event payload.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* // a gate reading only the base fields fits any handler on any transport
|
|
215
|
+
* const NeedsGuild = defineGate('NeedsGuild', (ctx: GateContextBase) => {
|
|
216
|
+
* if (!ctx.guildId) throw new NotInGuild();
|
|
217
|
+
* });
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
interface GateContextBase {
|
|
221
|
+
/** Gates read the config and the rate limiter through it. */
|
|
222
|
+
readonly core: CoreBase;
|
|
223
|
+
/** The acting user's id, or null on an event that carries none. */
|
|
224
|
+
readonly userId: string | null;
|
|
225
|
+
/** The guild id, or null outside a guild. */
|
|
226
|
+
readonly guildId: string | null;
|
|
227
|
+
/** The channel id, or null when the source carries none. */
|
|
228
|
+
readonly channelId: string | null;
|
|
229
|
+
/**
|
|
230
|
+
* The acting member's role ids without the everyone role, the shape a raw interaction payload
|
|
231
|
+
* carries. Empty outside a guild or when the source carries none.
|
|
232
|
+
*/
|
|
233
|
+
readonly memberRoleIds: readonly string[];
|
|
234
|
+
/**
|
|
235
|
+
* The acting member's permission bits, or null outside a guild. On an interaction these are the
|
|
236
|
+
* channel-scoped permissions the payload carries, on a gateway event the member's guild-level ones.
|
|
237
|
+
*/
|
|
238
|
+
readonly memberPermissions: bigint | null;
|
|
239
|
+
}
|
|
240
|
+
declare const GateBrand: unique symbol;
|
|
241
|
+
/**
|
|
242
|
+
* A precondition attached to a handler. It refuses by throwing a `Notice` (a reply) or a `Silence`
|
|
243
|
+
* (a drop with no reply), and passes by returning. `Ctx` is the context it requires, so a gate that reads
|
|
244
|
+
* a field absent on a handler's context is rejected at compile time. `Name` is captured as a literal so
|
|
245
|
+
* a mismatch error can name the gate. Build one with {@link defineGate} or {@link defineEffectGate}.
|
|
246
|
+
*
|
|
247
|
+
* @typeParam Ctx - The context the gate requires, so reading a field absent on a handler's context is a compile error.
|
|
248
|
+
* @typeParam Name - The gate's name captured as a literal, so a mismatch error can name the gate.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* // a factory returning a typed, agnostic gate
|
|
253
|
+
* function MyGate(): Gate<GateContextBase, 'MyGate'> {
|
|
254
|
+
* return defineGate('MyGate', (ctx) => {
|
|
255
|
+
* if (shouldRefuse(ctx)) throw new MyNotice();
|
|
256
|
+
* });
|
|
257
|
+
* }
|
|
258
|
+
* ```
|
|
259
|
+
*
|
|
260
|
+
* @see {@link defineGate}
|
|
261
|
+
*/
|
|
262
|
+
interface Gate<Ctx extends GateContextBase = GateContextBase, Name extends string = string> {
|
|
263
|
+
/** The gate's name, surfaced in a mismatch error and joined by the combinators. */
|
|
264
|
+
readonly name: Name;
|
|
265
|
+
/** Reads the context and refuses by throwing a Notice or a Silence, or passes by resolving. */
|
|
266
|
+
check(ctx: Ctx): Promise<void>;
|
|
267
|
+
/** @internal */
|
|
268
|
+
readonly [GateBrand]: true;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* A gate that carries a side effect, split so the effect fires only once the gate is the one that
|
|
272
|
+
* let the request through. `check` peeks and refuses, `commit` applies the effect after the whole
|
|
273
|
+
* gate set passes. {@link Cooldown} is the catalog gate that returns one. Build one with
|
|
274
|
+
* {@link defineEffectGate}.
|
|
275
|
+
*
|
|
276
|
+
* @typeParam Ctx - The context the gate requires, so reading a field absent on a handler's context is a compile error.
|
|
277
|
+
* @typeParam Name - The gate's name captured as a literal, so a mismatch error can name the gate.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```ts
|
|
281
|
+
* // a factory returning a typed effect gate
|
|
282
|
+
* function MyEffectGate(): EffectGate<GateContextBase, 'MyEffectGate'> {
|
|
283
|
+
* return defineEffectGate(
|
|
284
|
+
* 'MyEffectGate',
|
|
285
|
+
* (ctx) => {
|
|
286
|
+
* if (shouldRefuse(ctx)) throw new MyNotice();
|
|
287
|
+
* },
|
|
288
|
+
* (ctx) => {
|
|
289
|
+
* applyMyEffect(ctx);
|
|
290
|
+
* }
|
|
291
|
+
* );
|
|
292
|
+
* }
|
|
293
|
+
* ```
|
|
294
|
+
*
|
|
295
|
+
* @see {@link defineEffectGate}
|
|
296
|
+
*/
|
|
297
|
+
interface EffectGate<Ctx extends GateContextBase = GateContextBase, Name extends string = string> extends Gate<Ctx, Name> {
|
|
298
|
+
/** Applies the side effect, run only after the whole gate set passes. */
|
|
299
|
+
commit(ctx: Ctx): Promise<void>;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Builds a {@link Gate} from a check. The check refuses by throwing a Notice or a Silence and passes
|
|
303
|
+
* by returning. The required context is inferred from `ctx`, so annotate it as narrowly as the fields
|
|
304
|
+
* the check reads. No annotation defaults to {@link GateContextBase}, an agnostic gate that fits every
|
|
305
|
+
* handler.
|
|
306
|
+
*
|
|
307
|
+
* @typeParam Name - The gate's name captured as a literal, so a mismatch error can name the gate.
|
|
308
|
+
* @typeParam Ctx - The context the check reads, inferred from the `ctx` annotation.
|
|
309
|
+
*
|
|
310
|
+
* @param name - The gate's name, used in mismatch errors.
|
|
311
|
+
* @param fn - The check, which refuses by throwing a Notice or a Silence and passes by returning.
|
|
312
|
+
*
|
|
313
|
+
* @see {@link defineEffectGate}
|
|
314
|
+
* @see the `@Gated` decorator from `seedcord`
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* // a gate factory that takes its own option and closes over it
|
|
319
|
+
* function MinRoles(min: number): Gate<GateContextBase, 'MinRoles'> {
|
|
320
|
+
* return defineGate('MinRoles', (ctx) => {
|
|
321
|
+
* if (ctx.memberRoleIds.length < min) throw new NotEnoughRoles(min);
|
|
322
|
+
* });
|
|
323
|
+
* }
|
|
324
|
+
* ```
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* // agnostic gate (no ctx annotation): fits any handler
|
|
329
|
+
* const Owner = defineGate('owner', (ctx) => {
|
|
330
|
+
* if (!ctx.userId) throw new NotOwner();
|
|
331
|
+
* });
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
declare function defineGate<const Name extends string, Ctx extends GateContextBase = GateContextBase>(name: Name, fn: (ctx: Ctx) => void | Promise<void>): Gate<Ctx, Name>;
|
|
335
|
+
/**
|
|
336
|
+
* Builds an {@link EffectGate} from a `check` and a `commit`. `check` peeks and refuses by throwing, and
|
|
337
|
+
* `commit` applies the side effect, running only once the whole gate set passes so a later refusal never
|
|
338
|
+
* commits. In an `or`, a refusing arm's queued commit is rolled back. This is how {@link Cooldown} peeks
|
|
339
|
+
* in `check` and charges the slot in `commit`.
|
|
340
|
+
*
|
|
341
|
+
* @typeParam Name - The gate's name captured as a literal, so a mismatch error can name the gate.
|
|
342
|
+
* @typeParam Ctx - The context both `check` and `commit` read, inferred from the `ctx` annotation.
|
|
343
|
+
*
|
|
344
|
+
* @param name - The gate's name, used in mismatch errors.
|
|
345
|
+
* @param check - Peeks and refuses by throwing, without applying the side effect.
|
|
346
|
+
* @param commit - Applies the side effect, running only after the whole gate set passes.
|
|
347
|
+
*
|
|
348
|
+
* @see {@link defineGate}
|
|
349
|
+
* @see the `@Gated` decorator from `seedcord`
|
|
350
|
+
* @see {@link Cooldown}
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```ts
|
|
354
|
+
* // an effect-gate factory that takes its own limit and closes over it
|
|
355
|
+
* function UsesPerDay(max: number): EffectGate<GateContextBase, 'UsesPerDay'> {
|
|
356
|
+
* return defineEffectGate(
|
|
357
|
+
* 'UsesPerDay',
|
|
358
|
+
* (ctx) => {
|
|
359
|
+
* // peek and refuse by throwing, do not mutate yet
|
|
360
|
+
* if (usedToday(ctx.userId) >= max) throw new OutOfUsesNotice(max);
|
|
361
|
+
* },
|
|
362
|
+
* (ctx) => {
|
|
363
|
+
* // runs only after the whole gate set passes
|
|
364
|
+
* recordUse(ctx.userId);
|
|
365
|
+
* }
|
|
366
|
+
* );
|
|
367
|
+
* }
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
declare function defineEffectGate<const Name extends string, Ctx extends GateContextBase = GateContextBase>(name: Name, check: (ctx: Ctx) => void | Promise<void>, commit: (ctx: Ctx) => void | Promise<void>): EffectGate<Ctx, Name>;
|
|
371
|
+
/**
|
|
372
|
+
* The context a {@link Gate} requires.
|
|
373
|
+
*
|
|
374
|
+
* @typeParam TGate - The gate type to read the required context from.
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```ts
|
|
378
|
+
* type OwnerCtx = RequiredOf<ReturnType<typeof OwnerOnly>>; // GateContextBase
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
type RequiredOf<TGate> = TGate extends Gate<infer Ctx> ? Ctx : never;
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region src/stops/Notice.d.ts
|
|
384
|
+
/**
|
|
385
|
+
* Base class for a user-facing refusal or a reported fault.
|
|
386
|
+
*
|
|
387
|
+
* Throw a `Notice` to stop a handler and reply to the user. The framework catches it at the controller
|
|
388
|
+
* boundary and renders {@link Notice.render}, which always decides what the user sees. With `report`
|
|
389
|
+
* false that render is all that happens. With `report` true the framework also logs the fault and
|
|
390
|
+
* publishes it to the `handledException` bus. A raw, non-Notice throw shows the generic message.
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```ts
|
|
394
|
+
* import { Notice, BuilderComponent, type RenderContext, type ReplyResponse } from 'seedcord';
|
|
395
|
+
* import { TextDisplayBuilder } from '@discordjs/builders';
|
|
396
|
+
*
|
|
397
|
+
* // reading `.component` applies the configured bot color to the container accent
|
|
398
|
+
* class TooPoorCard extends BuilderComponent<'container'> {
|
|
399
|
+
* constructor(balance: number) {
|
|
400
|
+
* super('container');
|
|
401
|
+
* this.instance.addTextDisplayComponents(
|
|
402
|
+
* new TextDisplayBuilder().setContent(`### Insufficient balance\nYou need more than ${balance} coins.`)
|
|
403
|
+
* );
|
|
404
|
+
* }
|
|
405
|
+
* }
|
|
406
|
+
*
|
|
407
|
+
* class TooPoor extends Notice {
|
|
408
|
+
* constructor(private readonly balance: number) {
|
|
409
|
+
* super(`balance ${balance} is below the cost`);
|
|
410
|
+
* }
|
|
411
|
+
*
|
|
412
|
+
* render(_ctx: RenderContext): ReplyResponse {
|
|
413
|
+
* return { components: [new TooPoorCard(this.balance).component] };
|
|
414
|
+
* }
|
|
415
|
+
* }
|
|
416
|
+
*
|
|
417
|
+
* // in a handler, throwing stops the handler and replies with render(ctx)
|
|
418
|
+
* if (wallet.balance < cost) throw new TooPoor(wallet.balance);
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
declare abstract class Notice extends Error {
|
|
422
|
+
/**
|
|
423
|
+
* Whether this denial is a reported fault. True also logs it and publishes it to the `handledException`
|
|
424
|
+
* bus. The user always sees {@link Notice.render} either way.
|
|
425
|
+
*
|
|
426
|
+
* @defaultValue `false`
|
|
427
|
+
*/
|
|
428
|
+
report: boolean;
|
|
429
|
+
/**
|
|
430
|
+
* Whether the reply is ephemeral, so only the invoking user sees it. Set it false for a refusal the
|
|
431
|
+
* whole channel should see.
|
|
432
|
+
*
|
|
433
|
+
* @defaultValue `true`
|
|
434
|
+
*/
|
|
435
|
+
ephemeral: boolean;
|
|
436
|
+
/**
|
|
437
|
+
* A short one-line reason. When every arm of an `or` gate refuses and each refusal sets this, `or`
|
|
438
|
+
* lists them instead of showing a neutral message.
|
|
439
|
+
*/
|
|
440
|
+
summary?: string;
|
|
441
|
+
protected constructor(message: string, options?: ErrorOptions);
|
|
442
|
+
/**
|
|
443
|
+
* Builds what the user sees. Called fresh each time the denial is shown, so the builders are new
|
|
444
|
+
* and the bot color resolves at render time rather than at construction time.
|
|
445
|
+
*/
|
|
446
|
+
abstract render(ctx: RenderContext): ReplyResponse;
|
|
447
|
+
}
|
|
448
|
+
//#endregion
|
|
449
|
+
//#region src/gates/matching.d.ts
|
|
450
|
+
type IntersectRequired<Gates extends readonly Gate<GateContextBase>[]> = Gates extends readonly [infer First extends Gate<GateContextBase>, ...infer Rest extends readonly Gate<GateContextBase>[]] ? RequiredOf<First> & IntersectRequired<Rest> : unknown;
|
|
451
|
+
type TwoOrMore<Item> = readonly [Item, Item, ...Item[]];
|
|
452
|
+
type GateName<TGate> = TGate extends Gate<GateContextBase, infer Name> ? Name : string;
|
|
453
|
+
type JoinNames<Gates extends readonly Gate<GateContextBase>[], Sep extends string> = Gates extends readonly [infer Only extends Gate<GateContextBase>] ? GateName<Only> : Gates extends readonly [infer First extends Gate<GateContextBase>, ...infer Rest extends readonly Gate<GateContextBase>[]] ? `${GateName<First>}${Sep}${JoinNames<Rest, Sep>}` : string;
|
|
454
|
+
type GateMismatch<Name extends string, Want extends string, Got extends string> = `gate '${Name}' wants a ${Want} handler but this handler is ${Got}`;
|
|
455
|
+
type GateFitsWith<Provided, ProvidedLabel extends string, TGate, ReqLabel extends string> = [Provided] extends [RequiredOf<TGate>] ? TGate : Constructor<[GateMismatch<GateName<TGate>, ReqLabel, ProvidedLabel>]>;
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region src/gates/catalog/options.d.ts
|
|
458
|
+
/**
|
|
459
|
+
* The override a catalog gate accepts, a one-line `message` reword or a full `notice` replacement. Accepted by
|
|
460
|
+
* the agnostic gates ({@link OwnerOnly}, {@link GuildOnly}, {@link DmOnly}, {@link Cooldown}) and the gateway's
|
|
461
|
+
* `RequireRole` and `Nsfw`. The gateway's `RequirePermissions` and `RequireBotPermissions` take
|
|
462
|
+
* `RequirePermissionsOptions` instead.
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```ts
|
|
466
|
+
* // reword the default refusal
|
|
467
|
+
* OwnerOnly({ message: 'Bot owners only.' });
|
|
468
|
+
* ```
|
|
469
|
+
*
|
|
470
|
+
* @example
|
|
471
|
+
* ```ts
|
|
472
|
+
* // replace the refusal entirely, for example a translated Notice
|
|
473
|
+
* GuildOnly({ notice: new MyTranslatedNotice() });
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
interface GateNoticeOptions {
|
|
477
|
+
/** Reword the default refusal, keeping its embed styling. */
|
|
478
|
+
message?: string;
|
|
479
|
+
/** Replace the default refusal Notice entirely, for full control or a translated copy. */
|
|
480
|
+
notice?: Notice;
|
|
481
|
+
}
|
|
482
|
+
declare function pickNotice(options: GateNoticeOptions | undefined, makeDefault: (message?: string) => Notice): Notice;
|
|
483
|
+
//#endregion
|
|
484
|
+
//#region src/customId/Field.d.ts
|
|
485
|
+
/**
|
|
486
|
+
* One field in a customId shape, its wire kind plus the type it decodes to.
|
|
487
|
+
*
|
|
488
|
+
* @internal
|
|
489
|
+
*/
|
|
490
|
+
interface CustomIdField<Decoded> {
|
|
491
|
+
/** Which wire encoding this field uses. */
|
|
492
|
+
readonly kind: 'snowflake' | 'uuid' | 'int' | 'bool' | 'oneOf' | 'string';
|
|
493
|
+
/** Lower bound, for a bounded int field. */
|
|
494
|
+
readonly min?: number;
|
|
495
|
+
/** Upper bound, for a bounded int field. */
|
|
496
|
+
readonly max?: number;
|
|
497
|
+
/** The allowed values, for a oneOf field. */
|
|
498
|
+
readonly choices?: readonly string[];
|
|
499
|
+
/** Phantom only, carries the decoded type and is never set at runtime. */
|
|
500
|
+
readonly decoded?: Decoded;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* The set of fields a customId carries, keyed by name.
|
|
504
|
+
*
|
|
505
|
+
* @internal
|
|
506
|
+
*/
|
|
507
|
+
type CustomIdShape = Record<string, CustomIdField<unknown>>;
|
|
508
|
+
/**
|
|
509
|
+
* The decoded result, each field name mapped to its decoded type.
|
|
510
|
+
*
|
|
511
|
+
* @internal
|
|
512
|
+
*/
|
|
513
|
+
type DecodedParams<Shape extends CustomIdShape> = { [Name in keyof Shape]: Shape[Name] extends CustomIdField<infer Decoded> ? Decoded : never };
|
|
514
|
+
//#endregion
|
|
515
|
+
//#region src/customId/CustomId.d.ts
|
|
516
|
+
/** Strip the layout hash off the routeKey to recover the stable prefix the controller routes by. @internal */
|
|
517
|
+
declare function prefixOf(wire: string): string;
|
|
518
|
+
/**
|
|
519
|
+
* A typed customId. The single source of truth shared by the component that mints it and the handler
|
|
520
|
+
* that reads it. This gives you typed reads on the `.customId` field in components. Values are packed into a compact wire string rather than plain stringified tokens, so the 100-char Discord limit goes further. More string per string, basically.
|
|
521
|
+
*
|
|
522
|
+
* @typeParam Prefix - The stable route prefix, e.g. 'approve'.
|
|
523
|
+
* @typeParam Shape - The accumulated fields, filled in by the chain.
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```ts
|
|
527
|
+
* const ApproveId = new CustomId('approve')
|
|
528
|
+
* .snowflake('userId')
|
|
529
|
+
* .oneOf('action', ['approve', 'deny']);
|
|
530
|
+
*
|
|
531
|
+
* // Set the custom id on a button when creating it.
|
|
532
|
+
* new ButtonBuilder().setCustomId(ApproveId.encode({ userId: '123', action: 'approve' }));
|
|
533
|
+
*
|
|
534
|
+
* // reading in the handler: userId comes back a string
|
|
535
|
+
* const { userId, action } = this.params; // userId: string, action: 'approve' | 'deny'
|
|
536
|
+
* await this.event.guild?.members.fetch(userId);
|
|
537
|
+
* ```
|
|
538
|
+
*/
|
|
539
|
+
declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}> {
|
|
540
|
+
/** The stable route prefix, e.g. 'approve'. */
|
|
541
|
+
readonly prefix: Prefix;
|
|
542
|
+
/** Like a zod shape */
|
|
543
|
+
readonly shape: Shape;
|
|
544
|
+
/** The prefix plus a short hash of the shape, the part of the wire before the colon. */
|
|
545
|
+
readonly routeKey: string;
|
|
546
|
+
constructor(prefix: Prefix, shape?: Shape);
|
|
547
|
+
private add;
|
|
548
|
+
/**
|
|
549
|
+
* Add a Discord ID field, decoded as a string (the `Snowflake` type from discord-api-types).
|
|
550
|
+
*
|
|
551
|
+
* @example
|
|
552
|
+
* ```ts
|
|
553
|
+
* new CustomId('ban').snowflake('userId');
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
556
|
+
snowflake<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<Snowflake>>>;
|
|
557
|
+
/**
|
|
558
|
+
* Add a UUID field, decoded as a lowercase uuid string.
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```ts
|
|
562
|
+
* new CustomId('ticket').uuid('ticketId');
|
|
563
|
+
* ```
|
|
564
|
+
*/
|
|
565
|
+
uuid<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<string>>>;
|
|
566
|
+
/**
|
|
567
|
+
* Add an integer field with no bounds, for a value up to 2^53.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```ts
|
|
571
|
+
* new CustomId('shop').int('amount');
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
int<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<number>>>;
|
|
575
|
+
/**
|
|
576
|
+
* Add an integer field bounded by min and max, so it packs into fewer characters on the wire.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* ```ts
|
|
580
|
+
* new CustomId('paginate').int('page', 1, 50);
|
|
581
|
+
* ```
|
|
582
|
+
*/
|
|
583
|
+
int<Name extends string>(name: Name, min: number, max: number): CustomId<Prefix, Shape & Record<Name, CustomIdField<number>>>;
|
|
584
|
+
/**
|
|
585
|
+
* Add a boolean flag.
|
|
586
|
+
*
|
|
587
|
+
* @example
|
|
588
|
+
* ```ts
|
|
589
|
+
* new CustomId('settings').bool('silent');
|
|
590
|
+
* ```
|
|
591
|
+
*/
|
|
592
|
+
bool<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<boolean>>>;
|
|
593
|
+
/**
|
|
594
|
+
* Add a field that is one value from a fixed list, decoded as the literal union. No `as const` needed.
|
|
595
|
+
*
|
|
596
|
+
* @example
|
|
597
|
+
* ```ts
|
|
598
|
+
* new CustomId('poll').oneOf('choice', ['yes', 'no', 'abstain']);
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
oneOf<Name extends string, const Choices extends NonEmptyTuple<string>>(name: Name, choices: Choices): CustomId<Prefix, Shape & Record<Name, CustomIdField<Choices[number]>>>;
|
|
602
|
+
/**
|
|
603
|
+
* Add a free short text field. Avoid it where possible, it cannot be packed so it costs the most wire space.
|
|
604
|
+
*
|
|
605
|
+
* @example
|
|
606
|
+
* ```ts
|
|
607
|
+
* new CustomId('note').str('message');
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
str<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<string>>>;
|
|
611
|
+
/**
|
|
612
|
+
* Mint a wire string from values. Throws if a value is out of its field's range or the wire is over 100 chars.
|
|
613
|
+
*
|
|
614
|
+
* @param values - One value per field, typed by the chain.
|
|
615
|
+
* @returns The wire string to put on the component's customId.
|
|
616
|
+
*/
|
|
617
|
+
encode(values: DecodedParams<Shape>): string;
|
|
618
|
+
/**
|
|
619
|
+
* Read a wire string back into values.
|
|
620
|
+
*
|
|
621
|
+
* - This will refuse with a default Notice when the shape changed since the wire was minted.
|
|
622
|
+
* - Or refuse with a different default Notice on a corrupt or foreign wire.
|
|
623
|
+
*
|
|
624
|
+
* @param wire - The customId string from the interaction.
|
|
625
|
+
* @returns The decoded values, typed by the chain.
|
|
626
|
+
*/
|
|
627
|
+
decode(wire: string): DecodedParams<Shape>;
|
|
628
|
+
/** True if this wire was minted from this customId's prefix, ignoring the shape hash. */
|
|
629
|
+
owns(wire: string): boolean;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Any customId, for places where the exact prefix and shape do not matter.
|
|
633
|
+
*
|
|
634
|
+
* @internal
|
|
635
|
+
*/
|
|
636
|
+
type AnyCustomId = CustomId<string, CustomIdShape>;
|
|
637
|
+
/**
|
|
638
|
+
* The outcome of decoding a wire against several customIds, the matched prefix paired with its values.
|
|
639
|
+
*
|
|
640
|
+
* @internal
|
|
641
|
+
*/
|
|
642
|
+
type DecodedRoute<Defs extends readonly AnyCustomId[]> = { [Index in keyof Defs]: Defs[Index] extends AnyCustomId ? {
|
|
643
|
+
readonly prefix: Defs[Index]['prefix'];
|
|
644
|
+
readonly params: DecodedParams<Defs[Index]['shape']>;
|
|
645
|
+
} : never }[number];
|
|
646
|
+
/**
|
|
647
|
+
* Find the customId whose prefix owns this wire, decode against it, and report which one matched.
|
|
648
|
+
*
|
|
649
|
+
* @internal
|
|
650
|
+
*/
|
|
651
|
+
declare function decodeFor<Defs extends readonly AnyCustomId[]>(defs: Defs, wire: string): DecodedRoute<Defs>;
|
|
652
|
+
//#endregion
|
|
653
|
+
export { BuilderComponent as C, RowType as E, RegisterCommand as S, BuilderType as T, RequiredOf as _, CustomIdField as a, CoreBase as b, pickNotice as c, JoinNames as d, TwoOrMore as f, GateContextBase as g, Gate as h, prefixOf as i, GateFitsWith as l, EffectGate as m, CustomId as n, DecodedParams as o, Notice as p, decodeFor as r, GateNoticeOptions as s, AnyCustomId as t, IntersectRequired as u, defineEffectGate as v, RowComponent as w, CommandMeta as x, defineGate as y };
|
|
654
|
+
//# sourceMappingURL=CustomId-Ddwg48VY.d.mts.map
|