@seedcord/core 0.10.0 → 0.11.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 +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-CvGQr7oN.mjs +191 -0
- package/dist/ServerHost-CvGQr7oN.mjs.map +1 -0
- package/dist/ServerHost-DYeh0KTk.d.mts +40 -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 +185 -22
- package/dist/internal.index.mjs +713 -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 +15 -15
- 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/cursor-CgNBnJfu.mjs
DELETED
|
@@ -1,802 +0,0 @@
|
|
|
1
|
-
import { A as BuilderComponent, C as InteractionRouteKeys, S as InteractionMiddlewareMetadataKey, b as InteractionKind, x as InteractionMetadataKey } from "./Bus-BxPa-Uvp.mjs";
|
|
2
|
-
import { n as asError, t as PublishDefault } from "./publishDefault-BuSZPbjT.mjs";
|
|
3
|
-
import "reflect-metadata";
|
|
4
|
-
import { CustomId, decodeFor } from "@seedcord/custom-id";
|
|
5
|
-
import { TextDisplayBuilder } from "@discordjs/builders";
|
|
6
|
-
import { ApplicationCommandType, PermissionFlagsBits } from "discord-api-types/v10";
|
|
7
|
-
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
8
|
-
import { SeedcordError, SeedcordTypeError } from "@seedcord/errors/internal";
|
|
9
|
-
import { Logger } from "@seedcord/logger";
|
|
10
|
-
import { prettify, toEpochSeconds } from "@seedcord/utils";
|
|
11
|
-
|
|
12
|
-
//#region src/stops/Notice.ts
|
|
13
|
-
/**
|
|
14
|
-
* Base class for a user-facing refusal or a reported fault.
|
|
15
|
-
*
|
|
16
|
-
* Throw a `Notice` to stop a handler and reply to the user. The framework catches it at the controller
|
|
17
|
-
* boundary and renders {@link Notice.render}, which always decides what the user sees. With `report`
|
|
18
|
-
* false that render is all that happens. With `report` true the framework also logs the fault and
|
|
19
|
-
* publishes it to the `handledException` bus. A raw, non-Notice throw shows the generic message.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* import { Notice, BuilderComponent, type RenderContext, type ReplyResponse } from '@seedcord/gateway';
|
|
24
|
-
* import { TextDisplayBuilder } from '@discordjs/builders';
|
|
25
|
-
*
|
|
26
|
-
* // reading `.component` applies the configured bot color to the container accent
|
|
27
|
-
* class TooPoorCard extends BuilderComponent<'container'> {
|
|
28
|
-
* constructor(balance: number) {
|
|
29
|
-
* super('container');
|
|
30
|
-
* this.instance.addTextDisplayComponents(
|
|
31
|
-
* new TextDisplayBuilder().setContent(`### Insufficient balance\nYou need more than ${balance} coins.`)
|
|
32
|
-
* );
|
|
33
|
-
* }
|
|
34
|
-
* }
|
|
35
|
-
*
|
|
36
|
-
* class TooPoor extends Notice {
|
|
37
|
-
* constructor(private readonly balance: number) {
|
|
38
|
-
* super(`balance ${balance} is below the cost`);
|
|
39
|
-
* }
|
|
40
|
-
*
|
|
41
|
-
* render(_ctx: RenderContext): ReplyResponse {
|
|
42
|
-
* return { components: [new TooPoorCard(this.balance).component] };
|
|
43
|
-
* }
|
|
44
|
-
* }
|
|
45
|
-
*
|
|
46
|
-
* // in a handler, throwing stops the handler and replies with render(ctx)
|
|
47
|
-
* if (wallet.balance < cost) throw new TooPoor(wallet.balance);
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
var Notice = class extends Error {
|
|
51
|
-
/**
|
|
52
|
-
* Whether this denial is a reported fault. True also logs it and publishes it to the `handledException`
|
|
53
|
-
* bus. The user always sees {@link Notice.render} either way.
|
|
54
|
-
*
|
|
55
|
-
* @defaultValue `false`
|
|
56
|
-
*/
|
|
57
|
-
report = false;
|
|
58
|
-
/**
|
|
59
|
-
* Whether the reply is ephemeral, seen only by the invoking user. Set it false for a refusal the
|
|
60
|
-
* whole channel should see.
|
|
61
|
-
*
|
|
62
|
-
* @defaultValue `true`
|
|
63
|
-
*/
|
|
64
|
-
ephemeral = true;
|
|
65
|
-
/**
|
|
66
|
-
* A short one-line reason. When every arm of an {@link or} gate refuses and each one sets this, `or`
|
|
67
|
-
* throws a refusal listing them under `You need to meet at least one of these:`. Write a verb phrase that
|
|
68
|
-
* finishes that line, for example, "be the bot owner".
|
|
69
|
-
*/
|
|
70
|
-
summary;
|
|
71
|
-
constructor(message, options) {
|
|
72
|
-
super(message, options);
|
|
73
|
-
this.name = new.target.name;
|
|
74
|
-
Error.captureStackTrace(this, this.constructor);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region src/stops/NoticeCard.ts
|
|
80
|
-
var NoticeCard = class extends BuilderComponent {
|
|
81
|
-
constructor(description, title = "Cannot Proceed") {
|
|
82
|
-
super("container");
|
|
83
|
-
this.instance.addTextDisplayComponents(new TextDisplayBuilder().setContent(`### ${title}\n${description}`));
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
//#endregion
|
|
88
|
-
//#region src/customId/routing.ts
|
|
89
|
-
/**
|
|
90
|
-
* route decorators store a handler's customId defs here, so the component base can decode
|
|
91
|
-
* against them at runtime.
|
|
92
|
-
*
|
|
93
|
-
* @internal
|
|
94
|
-
*/
|
|
95
|
-
const ComponentDefsKey = Symbol.for("seedcord:custom-id:component-defs");
|
|
96
|
-
/**
|
|
97
|
-
* throws `CustomIdHandlerRouteMissing` when the class has no stored defs. a drifted or corrupt wire
|
|
98
|
-
* throws `StaleCustomId` or `InvalidCustomId`.
|
|
99
|
-
*
|
|
100
|
-
* @internal
|
|
101
|
-
*/
|
|
102
|
-
function decodeComponentRoute(handlerClass, wire) {
|
|
103
|
-
const defs = Reflect.getMetadata(ComponentDefsKey, handlerClass);
|
|
104
|
-
if (!defs) throw new SeedcordError(SeedcordErrorCode.CustomIdHandlerRouteMissing, [handlerClass.name]);
|
|
105
|
-
return decodeFor(defs, wire);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/decorators/interactionRoutes.ts
|
|
110
|
-
function areRoutes(routes) {
|
|
111
|
-
return Array.isArray(routes) && routes.every((route) => typeof route === "string");
|
|
112
|
-
}
|
|
113
|
-
function interactionRoutesOf(constructor) {
|
|
114
|
-
const pairs = [];
|
|
115
|
-
for (const route of Object.values(InteractionKind)) {
|
|
116
|
-
const meta = Reflect.getMetadata(InteractionRouteKeys[route], constructor);
|
|
117
|
-
if (areRoutes(meta)) pairs.push([route, meta]);
|
|
118
|
-
}
|
|
119
|
-
return pairs;
|
|
120
|
-
}
|
|
121
|
-
function storeInteractionRoute(route, routes, constructor) {
|
|
122
|
-
const key = InteractionRouteKeys[route];
|
|
123
|
-
const saved = Reflect.getMetadata(key, constructor);
|
|
124
|
-
const existing = areRoutes(saved) ? saved : [];
|
|
125
|
-
const toStore = typeof routes === "string" ? [routes] : routes;
|
|
126
|
-
Reflect.defineMetadata(key, [...existing, ...toStore], constructor);
|
|
127
|
-
Reflect.defineMetadata(InteractionMetadataKey, true, constructor);
|
|
128
|
-
}
|
|
129
|
-
function storeComponentRoute(route, defs, constructor) {
|
|
130
|
-
storeInteractionRoute(route, defs.map((def) => def.prefix), constructor);
|
|
131
|
-
Reflect.defineMetadata(ComponentDefsKey, defs, constructor);
|
|
132
|
-
}
|
|
133
|
-
function contextMenuRouteOf(kind) {
|
|
134
|
-
return kind === ApplicationCommandType.User ? "userContextMenu" : "messageContextMenu";
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
//#endregion
|
|
138
|
-
//#region src/decorators/middleware.ts
|
|
139
|
-
/**
|
|
140
|
-
* Registers an interaction middleware. It runs after the dispatcher builds the handler and before its
|
|
141
|
-
* gates, over that handler's reply surface.
|
|
142
|
-
*
|
|
143
|
-
* @param options - The kinds this middleware runs on and its ordering.
|
|
144
|
-
* @decorator
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* ```ts
|
|
148
|
-
* \@RegisterInteractionMiddleware({ kinds: [InteractionKind.Button], priority: 10 })
|
|
149
|
-
* class Audit extends InteractionMiddleware<InteractionKind.Button> {
|
|
150
|
-
* async execute() {
|
|
151
|
-
* this.logger.info(this.dispatch.routeId);
|
|
152
|
-
* }
|
|
153
|
-
* }
|
|
154
|
-
* ```
|
|
155
|
-
*
|
|
156
|
-
* @throws A **SeedcordTypeError** If `priority` is not a finite number.
|
|
157
|
-
*/
|
|
158
|
-
function RegisterInteractionMiddleware(options = {}) {
|
|
159
|
-
return function(ctor) {
|
|
160
|
-
const priority = Number(options.priority ?? 0);
|
|
161
|
-
if (!Number.isFinite(priority)) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorInvalidMiddlewarePriority);
|
|
162
|
-
if (options.kinds?.length === 0) throw new SeedcordTypeError(SeedcordErrorCode.DecoratorEmptyMiddlewareFilter, ["kinds"]);
|
|
163
|
-
const metadata = {
|
|
164
|
-
priority,
|
|
165
|
-
...options.kinds && { kinds: options.kinds }
|
|
166
|
-
};
|
|
167
|
-
Reflect.defineMetadata(InteractionMiddlewareMetadataKey, metadata, ctor);
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
/** @internal */
|
|
171
|
-
function interactionMiddlewareMetaOf(constructor) {
|
|
172
|
-
return Reflect.getMetadata(InteractionMiddlewareMetadataKey, constructor);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
//#endregion
|
|
176
|
-
//#region src/reply/ackLegality.ts
|
|
177
|
-
const showModalReason = {
|
|
178
|
-
reason: "this interaction was already acknowledged",
|
|
179
|
-
alternative: "showModal() must be the initial response."
|
|
180
|
-
};
|
|
181
|
-
function deferReason(what) {
|
|
182
|
-
return {
|
|
183
|
-
reason: `this interaction was already acknowledged with ${what}`,
|
|
184
|
-
alternative: "A deferral can only be the initial response."
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
const ILLEGAL = {
|
|
188
|
-
reply: {
|
|
189
|
-
"deferred-reply": {
|
|
190
|
-
reason: "this interaction was already deferred",
|
|
191
|
-
alternative: "Use edit() to fill the deferred reply or followUp() for a new message."
|
|
192
|
-
},
|
|
193
|
-
"deferred-update": {
|
|
194
|
-
reason: "this interaction was already acknowledged with a deferred update",
|
|
195
|
-
alternative: "Use followUp() for a new message."
|
|
196
|
-
},
|
|
197
|
-
replied: {
|
|
198
|
-
reason: "this interaction was already replied to",
|
|
199
|
-
alternative: "Use followUp() for a new message or edit() to rewrite the reply."
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
defer: {
|
|
203
|
-
"deferred-reply": deferReason("a deferred reply"),
|
|
204
|
-
"deferred-update": deferReason("a deferred update"),
|
|
205
|
-
replied: deferReason("a reply")
|
|
206
|
-
},
|
|
207
|
-
deferUpdate: {
|
|
208
|
-
"deferred-reply": deferReason("a deferred reply"),
|
|
209
|
-
"deferred-update": deferReason("a deferred update"),
|
|
210
|
-
replied: deferReason("a reply")
|
|
211
|
-
},
|
|
212
|
-
update: {
|
|
213
|
-
"deferred-reply": {
|
|
214
|
-
reason: "this interaction was deferred as a reply",
|
|
215
|
-
alternative: "The placeholder is yours via edit(). update() rewrites a source message."
|
|
216
|
-
},
|
|
217
|
-
replied: {
|
|
218
|
-
reason: "this interaction was already replied to",
|
|
219
|
-
alternative: "update() rewrites a source message. Use edit() or followUp() instead."
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
followUp: { unacked: {
|
|
223
|
-
reason: "this interaction has not been acknowledged yet",
|
|
224
|
-
alternative: "Ack first with reply() or defer()."
|
|
225
|
-
} },
|
|
226
|
-
edit: { unacked: {
|
|
227
|
-
reason: "nothing has been sent yet",
|
|
228
|
-
alternative: "Send with reply() or defer() first, then edit() rewrites it."
|
|
229
|
-
} },
|
|
230
|
-
delete: { unacked: {
|
|
231
|
-
reason: "nothing has been sent yet",
|
|
232
|
-
alternative: "Acknowledge first with reply() or defer(), then delete() removes it."
|
|
233
|
-
} },
|
|
234
|
-
showModal: {
|
|
235
|
-
"deferred-reply": showModalReason,
|
|
236
|
-
"deferred-update": showModalReason,
|
|
237
|
-
replied: showModalReason
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
const SEND_TARGET = {
|
|
241
|
-
unacked: "reply",
|
|
242
|
-
"deferred-reply": "edit",
|
|
243
|
-
"deferred-update": "followUp",
|
|
244
|
-
replied: "followUp"
|
|
245
|
-
};
|
|
246
|
-
function checkAckLegality(method, state, routeId, ackedBy) {
|
|
247
|
-
if (method === "send") return;
|
|
248
|
-
const illegal = ILLEGAL[method][state];
|
|
249
|
-
if (!illegal) return;
|
|
250
|
-
throw new SeedcordError(SeedcordErrorCode.ReplyIllegalAckState, [
|
|
251
|
-
method,
|
|
252
|
-
illegal.reason,
|
|
253
|
-
illegal.alternative,
|
|
254
|
-
routeId
|
|
255
|
-
], { cause: ackedBy });
|
|
256
|
-
}
|
|
257
|
-
function sendTarget(state) {
|
|
258
|
-
return SEND_TARGET[state];
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
//#endregion
|
|
262
|
-
//#region src/reply/AckTrace.ts
|
|
263
|
-
var AckTrace = class extends Error {
|
|
264
|
-
constructor(method) {
|
|
265
|
-
super(`${method}() acknowledged this interaction`);
|
|
266
|
-
this.name = "AckTrace";
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
//#endregion
|
|
271
|
-
//#region src/reply/responseReport.ts
|
|
272
|
-
let replyLogger;
|
|
273
|
-
function logger() {
|
|
274
|
-
replyLogger ??= new Logger("Reply", { channel: "interactions" });
|
|
275
|
-
return replyLogger;
|
|
276
|
-
}
|
|
277
|
-
/** @internal */
|
|
278
|
-
function publishResponse(telemetry, report) {
|
|
279
|
-
const durationMs = performance.now() - report.startedAt;
|
|
280
|
-
const { routeId } = telemetry.dispatch;
|
|
281
|
-
const write = {
|
|
282
|
-
dispatchId: telemetry.dispatch.id,
|
|
283
|
-
routeId,
|
|
284
|
-
interactionId: telemetry.interactionId,
|
|
285
|
-
method: report.method,
|
|
286
|
-
durationMs
|
|
287
|
-
};
|
|
288
|
-
telemetry.bus[PublishDefault]("responseAttempted", report.outcome === "failed" ? {
|
|
289
|
-
...write,
|
|
290
|
-
outcome: "failed",
|
|
291
|
-
messageId: null,
|
|
292
|
-
error: report.error
|
|
293
|
-
} : {
|
|
294
|
-
...write,
|
|
295
|
-
outcome: "sent",
|
|
296
|
-
messageId: report.messageId
|
|
297
|
-
});
|
|
298
|
-
logger().trace(`${paint.sky.bold(routeId)} ${report.method} ${report.outcome} ${paint.mute("in")} ${Math.round(durationMs)}ms`);
|
|
299
|
-
}
|
|
300
|
-
async function reportedWrite(telemetry, method, write) {
|
|
301
|
-
const startedAt = performance.now();
|
|
302
|
-
const result = await attemptWrite(telemetry, method, startedAt, write);
|
|
303
|
-
publishResponse(telemetry, {
|
|
304
|
-
method,
|
|
305
|
-
startedAt,
|
|
306
|
-
outcome: "sent",
|
|
307
|
-
messageId: null
|
|
308
|
-
});
|
|
309
|
-
return result;
|
|
310
|
-
}
|
|
311
|
-
async function attemptWrite(telemetry, method, startedAt, write) {
|
|
312
|
-
try {
|
|
313
|
-
return await write();
|
|
314
|
-
} catch (caught) {
|
|
315
|
-
publishResponse(telemetry, {
|
|
316
|
-
method,
|
|
317
|
-
startedAt,
|
|
318
|
-
outcome: "failed",
|
|
319
|
-
error: asError(caught)
|
|
320
|
-
});
|
|
321
|
-
throw caught;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
//#endregion
|
|
326
|
-
//#region src/reply/translateSerialization.ts
|
|
327
|
-
const MAX_DEPTH = 8;
|
|
328
|
-
const OPERATOR_WORD = {
|
|
329
|
-
"<=": "at most",
|
|
330
|
-
">=": "at least",
|
|
331
|
-
"<": "fewer than",
|
|
332
|
-
">": "more than"
|
|
333
|
-
};
|
|
334
|
-
const KIND_WORD = {
|
|
335
|
-
string: "a string",
|
|
336
|
-
number: "a number",
|
|
337
|
-
boolean: "a boolean",
|
|
338
|
-
bigint: "a bigint",
|
|
339
|
-
array: "a list",
|
|
340
|
-
tuple: "a list",
|
|
341
|
-
date: "a date",
|
|
342
|
-
object: "an object"
|
|
343
|
-
};
|
|
344
|
-
function asRecord(value) {
|
|
345
|
-
return typeof value === "object" && value !== null ? value : null;
|
|
346
|
-
}
|
|
347
|
-
function firstLine(message) {
|
|
348
|
-
if (typeof message !== "string") return "unknown validation failure";
|
|
349
|
-
return message.split("\n")[0] ?? message;
|
|
350
|
-
}
|
|
351
|
-
const GIVEN_PREVIEW_LIMIT = 40;
|
|
352
|
-
function gotPhrase(given) {
|
|
353
|
-
if (given === void 0) return "nothing";
|
|
354
|
-
if (given === null) return "null";
|
|
355
|
-
if (typeof given === "string") {
|
|
356
|
-
if (given.length === 0) return "an empty string";
|
|
357
|
-
if (given.length > GIVEN_PREVIEW_LIMIT) return `${given.length} characters`;
|
|
358
|
-
return JSON.stringify(given);
|
|
359
|
-
}
|
|
360
|
-
if (Array.isArray(given)) return given.length === 0 ? "an empty list" : `${given.length} items`;
|
|
361
|
-
if (typeof given === "number" || typeof given === "bigint" || typeof given === "boolean") return String(given);
|
|
362
|
-
if (typeof given === "object") return given.constructor?.name ?? "an object";
|
|
363
|
-
return typeof given;
|
|
364
|
-
}
|
|
365
|
-
function needPhrase(expected, given) {
|
|
366
|
-
const bounds = [...expected.matchAll(/expected(\.length)?\s*(<=|>=|<|>)\s*(\d+)/g)];
|
|
367
|
-
if (bounds.length === 0) return null;
|
|
368
|
-
return bounds.map(([, lengthPart, operator, limit]) => {
|
|
369
|
-
const word = OPERATOR_WORD[operator ?? ""] ?? operator ?? "";
|
|
370
|
-
if (!lengthPart) return `${word} ${limit}`;
|
|
371
|
-
const unit = typeof given === "string" ? "character" : "item";
|
|
372
|
-
return `${word} ${limit} ${Number(limit) === 1 ? unit : `${unit}s`}`;
|
|
373
|
-
}).join(" and ");
|
|
374
|
-
}
|
|
375
|
-
function instanceName(expected) {
|
|
376
|
-
if (typeof expected === "function" && expected.name) return expected.name;
|
|
377
|
-
return "specific class instance";
|
|
378
|
-
}
|
|
379
|
-
function describeLeaf(record) {
|
|
380
|
-
if (typeof record.constraint === "string") {
|
|
381
|
-
const need = typeof record.expected === "string" ? needPhrase(record.expected, record.given) : null;
|
|
382
|
-
if (need) return `need ${need}, got ${gotPhrase(record.given)}`;
|
|
383
|
-
return `${firstLine(record.message)}${"given" in record ? ` (got ${gotPhrase(record.given)})` : ""}`;
|
|
384
|
-
}
|
|
385
|
-
if (typeof record.expected === "function") return `need a ${instanceName(record.expected)}, got ${gotPhrase(record.given)}`;
|
|
386
|
-
if (typeof record.validator === "string") {
|
|
387
|
-
const kind = KIND_WORD[record.validator.replace(/^s\./, "").replace(/\(.*$/, "")];
|
|
388
|
-
if (kind) return `need ${kind}, got ${gotPhrase(record.given)}`;
|
|
389
|
-
return `${firstLine(record.message)}, got ${gotPhrase(record.given)}`;
|
|
390
|
-
}
|
|
391
|
-
return firstLine(record.message);
|
|
392
|
-
}
|
|
393
|
-
function describeUnion(branches, depth) {
|
|
394
|
-
const records = [];
|
|
395
|
-
for (const branch of branches) {
|
|
396
|
-
const record = asRecord(branch);
|
|
397
|
-
if (record) records.push(record);
|
|
398
|
-
}
|
|
399
|
-
const bounded = records.find((record) => typeof record.constraint === "string");
|
|
400
|
-
if (bounded) return describeLeaf(bounded);
|
|
401
|
-
const names = [];
|
|
402
|
-
let allInstances = records.length > 0;
|
|
403
|
-
for (const record of records) if (typeof record.expected === "function") names.push(instanceName(record.expected));
|
|
404
|
-
else allInstances = false;
|
|
405
|
-
if (allInstances) return `need a ${[...new Set(names)].join(" or ")}, got ${gotPhrase(records[0]?.given)}`;
|
|
406
|
-
return detailOf(branches[0], depth + 1);
|
|
407
|
-
}
|
|
408
|
-
function detailOf(value, depth) {
|
|
409
|
-
const record = asRecord(value);
|
|
410
|
-
if (!record || depth >= MAX_DEPTH) return firstLine(record?.message);
|
|
411
|
-
if (Array.isArray(record.errors) && record.errors.length > 0) {
|
|
412
|
-
const errors = record.errors;
|
|
413
|
-
const [first] = errors;
|
|
414
|
-
if (Array.isArray(first)) {
|
|
415
|
-
const [key, inner] = first;
|
|
416
|
-
return `${String(key)}: ${detailOf(inner, depth + 1)}`;
|
|
417
|
-
}
|
|
418
|
-
return describeUnion(errors, depth);
|
|
419
|
-
}
|
|
420
|
-
return describeLeaf(record);
|
|
421
|
-
}
|
|
422
|
-
function translateSerializationError(error, componentClass, index, routeId) {
|
|
423
|
-
const detail = detailOf(error, 0);
|
|
424
|
-
return new SeedcordError(SeedcordErrorCode.ReplyComponentSerialization, [
|
|
425
|
-
componentClass,
|
|
426
|
-
index,
|
|
427
|
-
detail,
|
|
428
|
-
routeId
|
|
429
|
-
], { cause: error });
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
//#endregion
|
|
433
|
-
//#region src/reply/serializeReply.ts
|
|
434
|
-
function serializeReply(response, routeId) {
|
|
435
|
-
if (typeof response === "string") try {
|
|
436
|
-
return { components: [new TextDisplayBuilder().setContent(response).toJSON()] };
|
|
437
|
-
} catch (error) {
|
|
438
|
-
throw translateSerializationError(error, "TextDisplayBuilder", 0, routeId);
|
|
439
|
-
}
|
|
440
|
-
return {
|
|
441
|
-
components: response.components.map((component, index) => {
|
|
442
|
-
try {
|
|
443
|
-
return component.toJSON();
|
|
444
|
-
} catch (error) {
|
|
445
|
-
const name = component.constructor?.name;
|
|
446
|
-
throw translateSerializationError(error, !name || name === "Object" ? "component" : name, index, routeId);
|
|
447
|
-
}
|
|
448
|
-
}),
|
|
449
|
-
...response.allowedMentions && { allowedMentions: response.allowedMentions },
|
|
450
|
-
...response.files && { files: response.files }
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
//#endregion
|
|
455
|
-
//#region src/gates/Gate.ts
|
|
456
|
-
/**
|
|
457
|
-
* Builds a {@link Gate} from a check. The check refuses by throwing a Notice or a Silence and passes
|
|
458
|
-
* by returning. The required context is inferred from `ctx`, so annotate it as narrowly as the fields
|
|
459
|
-
* the check reads. No annotation defaults to {@link GateContextBase}, an agnostic gate that fits every
|
|
460
|
-
* handler.
|
|
461
|
-
*
|
|
462
|
-
* @typeParam Name - The gate's name, captured as a literal for mismatch errors.
|
|
463
|
-
* @typeParam Ctx - The context the check reads, inferred from the `ctx` annotation.
|
|
464
|
-
*
|
|
465
|
-
* @param name - The gate's name, used in mismatch errors.
|
|
466
|
-
* @param fn - The check, which refuses by throwing a Notice or a Silence and passes by returning.
|
|
467
|
-
*
|
|
468
|
-
* @see {@link defineEffectGate}
|
|
469
|
-
* @see the `@Gated` decorator from your transport package
|
|
470
|
-
*
|
|
471
|
-
* @example
|
|
472
|
-
* ```ts
|
|
473
|
-
* // a gate factory that takes its own option and closes over it
|
|
474
|
-
* function MinRoles(min: number): Gate<GateContextBase, 'MinRoles'> {
|
|
475
|
-
* return defineGate('MinRoles', (ctx) => {
|
|
476
|
-
* if (ctx.memberRoleIds.length < min) throw new NotEnoughRoles(min);
|
|
477
|
-
* });
|
|
478
|
-
* }
|
|
479
|
-
* ```
|
|
480
|
-
*
|
|
481
|
-
* @example
|
|
482
|
-
* ```ts
|
|
483
|
-
* // agnostic gate (no ctx annotation): fits any handler
|
|
484
|
-
* const Owner = defineGate('owner', (ctx) => {
|
|
485
|
-
* if (!ctx.userId) throw new NotOwner();
|
|
486
|
-
* });
|
|
487
|
-
* ```
|
|
488
|
-
*/
|
|
489
|
-
function defineGate(name, fn) {
|
|
490
|
-
return {
|
|
491
|
-
name,
|
|
492
|
-
check: async (ctx) => fn(ctx)
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Builds an {@link EffectGate} from a `check` and a `commit`. `check` peeks and refuses by throwing, and
|
|
497
|
-
* `commit` applies the side effect, running only once the whole gate set passes so a later refusal never
|
|
498
|
-
* commits. In an `or`, a refusing arm's queued commit is rolled back. This is how {@link Cooldown} peeks
|
|
499
|
-
* in `check` and charges the slot in `commit`.
|
|
500
|
-
*
|
|
501
|
-
* @typeParam Name - The gate's literal name, used to name a mismatch error.
|
|
502
|
-
* @typeParam Ctx - The context both `check` and `commit` read, inferred from the `ctx` annotation.
|
|
503
|
-
*
|
|
504
|
-
* @param name - The gate's name, used in mismatch errors.
|
|
505
|
-
* @param check - Peeks and refuses by throwing, without applying the side effect.
|
|
506
|
-
* @param commit - Applies the side effect, running only after the whole gate set passes.
|
|
507
|
-
*
|
|
508
|
-
* @see {@link defineGate}
|
|
509
|
-
* @see the `@Gated` decorator from your transport package
|
|
510
|
-
* @see {@link Cooldown}
|
|
511
|
-
*
|
|
512
|
-
* @example
|
|
513
|
-
* ```ts
|
|
514
|
-
* // an effect-gate factory that takes its own limit and closes over it
|
|
515
|
-
* function UsesPerDay(max: number): EffectGate<GateContextBase, 'UsesPerDay'> {
|
|
516
|
-
* return defineEffectGate(
|
|
517
|
-
* 'UsesPerDay',
|
|
518
|
-
* (ctx) => {
|
|
519
|
-
* // peek and refuse by throwing. do not mutate yet
|
|
520
|
-
* if (usedToday(ctx.userId) >= max) throw new OutOfUsesNotice(max);
|
|
521
|
-
* },
|
|
522
|
-
* (ctx) => {
|
|
523
|
-
* // runs only after the whole gate set passes
|
|
524
|
-
* recordUse(ctx.userId);
|
|
525
|
-
* }
|
|
526
|
-
* );
|
|
527
|
-
* }
|
|
528
|
-
* ```
|
|
529
|
-
*/
|
|
530
|
-
function defineEffectGate(name, check, commit) {
|
|
531
|
-
return {
|
|
532
|
-
name,
|
|
533
|
-
check: async (ctx) => check(ctx),
|
|
534
|
-
commit: async (ctx) => commit(ctx)
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
//#endregion
|
|
539
|
-
//#region src/notices/index.ts
|
|
540
|
-
var GateNotice = class extends Notice {
|
|
541
|
-
render() {
|
|
542
|
-
return { components: [new NoticeCard(this.message).component] };
|
|
543
|
-
}
|
|
544
|
-
};
|
|
545
|
-
var NotOwner = class extends GateNotice {
|
|
546
|
-
constructor(message = "Only the bot owner can use this.") {
|
|
547
|
-
super(message);
|
|
548
|
-
this.summary = "be the bot owner";
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
var NotInGuild = class extends GateNotice {
|
|
552
|
-
constructor(message = "This can only be used in a server.") {
|
|
553
|
-
super(message);
|
|
554
|
-
this.summary = "be in a server";
|
|
555
|
-
}
|
|
556
|
-
};
|
|
557
|
-
var NotInDm = class extends GateNotice {
|
|
558
|
-
constructor(message = "This can only be used in a direct message.") {
|
|
559
|
-
super(message);
|
|
560
|
-
this.summary = "be in a direct message";
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
function retryAt(resetAt) {
|
|
564
|
-
return `<t:${toEpochSeconds(resetAt)}:R>`;
|
|
565
|
-
}
|
|
566
|
-
var OnCooldown = class extends GateNotice {
|
|
567
|
-
resetAt;
|
|
568
|
-
constructor(resetAt, message) {
|
|
569
|
-
super(message ?? `You are doing that too fast. Try again ${retryAt(resetAt)}.`);
|
|
570
|
-
this.resetAt = resetAt;
|
|
571
|
-
this.summary = `try again ${retryAt(resetAt)}`;
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
var NotAllowed = class extends Notice {
|
|
575
|
-
constructor() {
|
|
576
|
-
super("not allowed");
|
|
577
|
-
}
|
|
578
|
-
render() {
|
|
579
|
-
return { components: [new NoticeCard("You are not allowed to use this command.").component] };
|
|
580
|
-
}
|
|
581
|
-
};
|
|
582
|
-
var NeedsAny = class extends Notice {
|
|
583
|
-
summaries;
|
|
584
|
-
constructor(summaries) {
|
|
585
|
-
super("not allowed");
|
|
586
|
-
this.summaries = [...new Set(summaries)];
|
|
587
|
-
}
|
|
588
|
-
render() {
|
|
589
|
-
const bullets = this.summaries.map((summary) => `• ${summary}`).join("\n");
|
|
590
|
-
return { components: [new NoticeCard(`You need to meet at least one of these:\n${bullets}`).component] };
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
var MissingRole = class extends GateNotice {
|
|
594
|
-
constructor(message, roleId) {
|
|
595
|
-
super(message ?? (roleId ? `You need the <@&${roleId}> role to use this.` : "You do not have the required role."));
|
|
596
|
-
this.summary = roleId ? `hold the <@&${roleId}> role` : "hold the required role";
|
|
597
|
-
}
|
|
598
|
-
};
|
|
599
|
-
var MissingPermissions = class extends Notice {
|
|
600
|
-
subject;
|
|
601
|
-
missingPerms;
|
|
602
|
-
customLead;
|
|
603
|
-
constructor(message, subject, missingPerms) {
|
|
604
|
-
super(message ?? "A required permission is missing.");
|
|
605
|
-
this.subject = subject;
|
|
606
|
-
this.missingPerms = missingPerms;
|
|
607
|
-
this.customLead = message;
|
|
608
|
-
const names = missingPerms.join(", ");
|
|
609
|
-
this.summary = subject === null ? `hold ${names}` : `${subject} needs ${names}`;
|
|
610
|
-
}
|
|
611
|
-
render() {
|
|
612
|
-
const bullets = this.missingPerms.map((perm) => `• ${perm}`).join("\n");
|
|
613
|
-
const lead = this.customLead ?? `${this.subject === null ? "You are" : `${this.subject} is`} missing the following permission entries:`;
|
|
614
|
-
return { components: [new NoticeCard(`${lead}\n\n${bullets}`).component] };
|
|
615
|
-
}
|
|
616
|
-
};
|
|
617
|
-
var HasDangerousPermissions = class extends Notice {
|
|
618
|
-
subject;
|
|
619
|
-
dangerousPerms;
|
|
620
|
-
customLead;
|
|
621
|
-
constructor(message, subject, dangerousPerms) {
|
|
622
|
-
super(message ?? "A dangerous permission is enabled.");
|
|
623
|
-
this.subject = subject;
|
|
624
|
-
this.dangerousPerms = dangerousPerms;
|
|
625
|
-
this.customLead = message;
|
|
626
|
-
this.summary = `${subject} must not hold ${dangerousPerms.join(", ")}`;
|
|
627
|
-
}
|
|
628
|
-
render() {
|
|
629
|
-
const bullets = this.dangerousPerms.map((perm) => `• ${perm}`).join("\n");
|
|
630
|
-
const lead = this.customLead ?? `${this.subject} has the following permission entries that must not be enabled:`;
|
|
631
|
-
return { components: [new NoticeCard(`${lead}\n\n${bullets}`).component] };
|
|
632
|
-
}
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
//#endregion
|
|
636
|
-
//#region src/gates/effects.ts
|
|
637
|
-
const commitQueues = /* @__PURE__ */ new WeakMap();
|
|
638
|
-
function isEffectGate(gate) {
|
|
639
|
-
return typeof gate.commit === "function";
|
|
640
|
-
}
|
|
641
|
-
async function runCheck(gate, ctx) {
|
|
642
|
-
await gate.check(ctx);
|
|
643
|
-
if (!isEffectGate(gate)) return;
|
|
644
|
-
let queue = commitQueues.get(ctx);
|
|
645
|
-
if (!queue) {
|
|
646
|
-
queue = [];
|
|
647
|
-
commitQueues.set(ctx, queue);
|
|
648
|
-
}
|
|
649
|
-
queue.push(() => gate.commit(ctx));
|
|
650
|
-
}
|
|
651
|
-
async function runCommits(ctx) {
|
|
652
|
-
const queue = commitQueues.get(ctx);
|
|
653
|
-
if (!queue) return;
|
|
654
|
-
for (const commit of queue) await commit();
|
|
655
|
-
}
|
|
656
|
-
function discardCommits(ctx) {
|
|
657
|
-
commitQueues.delete(ctx);
|
|
658
|
-
}
|
|
659
|
-
function markCommits(ctx) {
|
|
660
|
-
return commitQueues.get(ctx)?.length ?? 0;
|
|
661
|
-
}
|
|
662
|
-
function rollbackCommits(ctx, mark) {
|
|
663
|
-
const queue = commitQueues.get(ctx);
|
|
664
|
-
if (queue && queue.length > mark) queue.length = mark;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
//#endregion
|
|
668
|
-
//#region src/gates/catalog/options.ts
|
|
669
|
-
function pickNotice(options, makeDefault) {
|
|
670
|
-
return options?.notice ?? makeDefault(options?.message);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
//#endregion
|
|
674
|
-
//#region src/permissions/bits.ts
|
|
675
|
-
const PermissionNames = new Map(Object.entries(PermissionFlagsBits).map(([key, bit]) => [bit, prettify(key)]));
|
|
676
|
-
const ADMINISTRATOR = PermissionFlagsBits.Administrator;
|
|
677
|
-
function nameOf(bit) {
|
|
678
|
-
return PermissionNames.get(bit) ?? String(bit);
|
|
679
|
-
}
|
|
680
|
-
function holds(held, bit) {
|
|
681
|
-
return (held & ADMINISTRATOR) === ADMINISTRATOR || (held & bit) === bit;
|
|
682
|
-
}
|
|
683
|
-
/** @internal */
|
|
684
|
-
function hasAll(held, scope) {
|
|
685
|
-
return scope.every((bit) => holds(held, bit));
|
|
686
|
-
}
|
|
687
|
-
/** @internal only reached once hasAll failed, so the administrator bit is absent */
|
|
688
|
-
function missingNames(held, scope) {
|
|
689
|
-
return scope.reduce((names, bit) => {
|
|
690
|
-
if ((held & bit) !== bit) names.push(nameOf(bit));
|
|
691
|
-
return names;
|
|
692
|
-
}, []);
|
|
693
|
-
}
|
|
694
|
-
/** @internal */
|
|
695
|
-
function presentNames(held, scope) {
|
|
696
|
-
return scope.reduce((names, bit) => {
|
|
697
|
-
if (holds(held, bit)) names.push(nameOf(bit));
|
|
698
|
-
return names;
|
|
699
|
-
}, []);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
//#endregion
|
|
703
|
-
//#region src/gates/catalog/permissions.ts
|
|
704
|
-
const MEMBER_CHECK = {
|
|
705
|
-
unresolved: "Your server member data could not be resolved. Try again.",
|
|
706
|
-
subject: null
|
|
707
|
-
};
|
|
708
|
-
const BOT_CHECK = {
|
|
709
|
-
unresolved: "The bot's permissions could not be resolved. Try again.",
|
|
710
|
-
subject: "The bot"
|
|
711
|
-
};
|
|
712
|
-
function refusePermissions(held, scope, options, guildId, kind) {
|
|
713
|
-
if (held === null) throw pickNotice(options?.notInGuild, (message) => new NotInGuild(message ?? (guildId ? kind.unresolved : void 0)));
|
|
714
|
-
if (hasAll(held, scope)) return;
|
|
715
|
-
throw pickNotice(options?.missing, (message) => new MissingPermissions(message, kind.subject, missingNames(held, scope)));
|
|
716
|
-
}
|
|
717
|
-
function RequirePermissions(scope, options) {
|
|
718
|
-
if (options?.in === "guild") return defineGate("RequirePermissions", (ctx) => {
|
|
719
|
-
refusePermissions(ctx.memberGuildPermissions, scope, options, ctx.guildId, MEMBER_CHECK);
|
|
720
|
-
});
|
|
721
|
-
return defineGate("RequirePermissions", (ctx) => {
|
|
722
|
-
refusePermissions(ctx.memberPermissions, scope, options, ctx.guildId, MEMBER_CHECK);
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
function RequireBotPermissions(scope, options) {
|
|
726
|
-
if (options?.in === "guild") return defineGate("RequireBotPermissions", (ctx) => {
|
|
727
|
-
refusePermissions(ctx.appGuildPermissions, scope, options, ctx.guildId, BOT_CHECK);
|
|
728
|
-
});
|
|
729
|
-
return defineGate("RequireBotPermissions", (ctx) => {
|
|
730
|
-
refusePermissions(ctx.appPermissions, scope, options, ctx.guildId, BOT_CHECK);
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Requires the caller to hold `roleId`, read from the member's role ids. Refuses outside a guild first,
|
|
735
|
-
* then when the role is missing. Roles are guild-global, so there is no scope dimension.
|
|
736
|
-
*
|
|
737
|
-
* @param roleId - The role snowflake the caller must hold.
|
|
738
|
-
* @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-role one with `missingRole`.
|
|
739
|
-
*
|
|
740
|
-
* @see the `@Gated` decorator from your transport package
|
|
741
|
-
*
|
|
742
|
-
* @example
|
|
743
|
-
* ```ts
|
|
744
|
-
* \@Gated(RequireRole('123456789012345678'))
|
|
745
|
-
* \@SlashRoute('vip')
|
|
746
|
-
* class VipHandler extends SlashHandler<'vip'> {
|
|
747
|
-
* async execute() {
|
|
748
|
-
* // ...
|
|
749
|
-
* }
|
|
750
|
-
* }
|
|
751
|
-
* ```
|
|
752
|
-
*/
|
|
753
|
-
function RequireRole(roleId, options) {
|
|
754
|
-
return defineGate("RequireRole", (ctx) => {
|
|
755
|
-
if (!ctx.guildId) throw pickNotice(options?.notInGuild, (message) => new NotInGuild(message));
|
|
756
|
-
if (ctx.memberRoleIds.includes(roleId)) return;
|
|
757
|
-
throw pickNotice(options?.missingRole, (message) => new MissingRole(message, roleId));
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
//#endregion
|
|
762
|
-
//#region src/stops/Silence.ts
|
|
763
|
-
/**
|
|
764
|
-
* Throw to stop a handler with no reply and no report.
|
|
765
|
-
*
|
|
766
|
-
* The framework boundary catches `Silence` before {@link Notice}, makes zero Discord calls, and stops.
|
|
767
|
-
* Ideally you'd only throw this in `EventHandlers` (or `Gates` for those), because it doesn't make sense to
|
|
768
|
-
* leave the user with no reply for an interaction.
|
|
769
|
-
*
|
|
770
|
-
* @example
|
|
771
|
-
* ```ts
|
|
772
|
-
* import { Silence } from '@seedcord/gateway';
|
|
773
|
-
*
|
|
774
|
-
* // a helper under a guildMemberAdd handler, stopping when this guild configured nothing to send
|
|
775
|
-
* const template = await settings.welcome(member.guild.id);
|
|
776
|
-
* if (!template) throw new Silence('no welcome message set');
|
|
777
|
-
* ```
|
|
778
|
-
*/
|
|
779
|
-
var Silence = class extends Error {
|
|
780
|
-
reason;
|
|
781
|
-
/**
|
|
782
|
-
* @param reason - Optional note written only to a debug log, never shown to the user or reported.
|
|
783
|
-
*/
|
|
784
|
-
constructor(reason) {
|
|
785
|
-
super(reason ?? "Silence");
|
|
786
|
-
this.reason = reason;
|
|
787
|
-
Error.captureStackTrace(this, this.constructor);
|
|
788
|
-
}
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
//#endregion
|
|
792
|
-
//#region src/pagination/cursor.ts
|
|
793
|
-
const PAGE_MAX = 1e6;
|
|
794
|
-
const SLOT_MAX = 4;
|
|
795
|
-
/** Build the page cursor for a paginator. */
|
|
796
|
-
function pageCursor(prefix) {
|
|
797
|
-
return new CustomId(prefix).int("page", 0, PAGE_MAX).int("slot", 0, SLOT_MAX);
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
//#endregion
|
|
801
|
-
export { translateSerializationError as A, contextMenuRouteOf as B, NotInDm as C, defineEffectGate as D, OnCooldown as E, checkAckLegality as F, decodeComponentRoute as G, storeComponentRoute as H, sendTarget as I, NoticeCard as K, RegisterInteractionMiddleware as L, publishResponse as M, reportedWrite as N, defineGate as O, AckTrace as P, interactionMiddlewareMetaOf as R, NotAllowed as S, NotOwner as T, storeInteractionRoute as U, interactionRoutesOf as V, ComponentDefsKey as W, GateNotice as _, RequirePermissions as a, MissingRole as b, hasAll as c, pickNotice as d, discardCommits as f, runCommits as g, runCheck as h, RequireBotPermissions as i, attemptWrite as j, serializeReply as k, missingNames as l, rollbackCommits as m, pageCursor as n, RequireRole as o, markCommits as p, Notice as q, Silence as r, PermissionNames as s, PAGE_MAX as t, presentNames as u, HasDangerousPermissions as v, NotInGuild as w, NeedsAny as x, MissingPermissions as y, areRoutes as z };
|
|
802
|
-
//# sourceMappingURL=cursor-CgNBnJfu.mjs.map
|