@seedcord/core 0.1.4 → 0.2.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/dist/index.cjs
DELETED
|
@@ -1,889 +0,0 @@
|
|
|
1
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_subscribers = require('./subscribers-6x4xw39Z.cjs');
|
|
3
|
-
const require_publishDefault = require('./publishDefault-D52iT8mF.cjs');
|
|
4
|
-
const require_cursor = require('./cursor-DFgJ70_B.cjs');
|
|
5
|
-
const require_phases = require('./phases-8qiiLKjS.cjs');
|
|
6
|
-
require("reflect-metadata");
|
|
7
|
-
let _discordjs_builders = require("@discordjs/builders");
|
|
8
|
-
let _seedcord_logger = require("@seedcord/logger");
|
|
9
|
-
let discord_api_types_v10 = require("discord-api-types/v10");
|
|
10
|
-
let _seedcord_errors = require("@seedcord/errors");
|
|
11
|
-
let _seedcord_errors_internal = require("@seedcord/errors/internal");
|
|
12
|
-
let _seedcord_utils = require("@seedcord/utils");
|
|
13
|
-
|
|
14
|
-
//#region src/decorators/Command.ts
|
|
15
|
-
function RegisterCommand(scope, guilds = []) {
|
|
16
|
-
return (ctor) => {
|
|
17
|
-
const existingMeta = Reflect.getOwnMetadata(require_publishDefault.CommandMetadataKey, ctor);
|
|
18
|
-
if (existingMeta) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorCommandAlreadyRegistered, [
|
|
19
|
-
ctor.name,
|
|
20
|
-
existingMeta.scope,
|
|
21
|
-
scope
|
|
22
|
-
]);
|
|
23
|
-
if (scope === "global" && guilds.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorCommandGlobalWithGuilds);
|
|
24
|
-
if (scope === "guild" && (!Array.isArray(guilds) || guilds.length === 0)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorCommandGuildWithoutGuilds);
|
|
25
|
-
const meta = scope === "global" ? { scope } : {
|
|
26
|
-
scope,
|
|
27
|
-
guilds
|
|
28
|
-
};
|
|
29
|
-
Reflect.defineMetadata(require_publishDefault.CommandMetadataKey, meta, ctor);
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/decorators/routes.ts
|
|
35
|
-
/**
|
|
36
|
-
* Routes one or more slash commands to a `SlashHandler`.
|
|
37
|
-
*
|
|
38
|
-
* Pass the same route string(s) as the handler's generic. A single command reads `this.options`, several
|
|
39
|
-
* commands branch with `this.match`. The decorator routes and the generic must match exactly. Listing
|
|
40
|
-
* fewer or more than the handler declares is a compile error. Subcommands use a slash path.
|
|
41
|
-
*
|
|
42
|
-
* @param routes - The route string(s) this handler serves, e.g. `'ban'`, `'ban', 'kick'`, or `'demo/setup'`.
|
|
43
|
-
* @decorator
|
|
44
|
-
* @example
|
|
45
|
-
* ```ts
|
|
46
|
-
* \@SlashRoute('ban')
|
|
47
|
-
* class BanHandler extends SlashHandler<'ban'> {
|
|
48
|
-
* async execute() {
|
|
49
|
-
* const target = this.options.getUser('target');
|
|
50
|
-
* }
|
|
51
|
-
* }
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
function SlashRoute(...routes) {
|
|
55
|
-
return function(constructor) {
|
|
56
|
-
require_cursor.storeInteractionRoute("slash", routes, constructor);
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Routes one or more commands' autocomplete to an `AutocompleteHandler`.
|
|
61
|
-
*
|
|
62
|
-
* Pass the same command route string(s) as the handler's generic. One command branches with `this.match`
|
|
63
|
-
* over its autocompletable fields, several commands share one handler whose arms span every command's
|
|
64
|
-
* fields. Listing fewer or more commands than the handler declares is a compile error, since the
|
|
65
|
-
* decorator routes and the generic must match exactly.
|
|
66
|
-
*
|
|
67
|
-
* @param routes - The command route string(s) this handler serves, e.g. `'search'` or `'search', 'find'`.
|
|
68
|
-
* @decorator
|
|
69
|
-
* @example
|
|
70
|
-
* ```ts
|
|
71
|
-
* \@AutocompleteRoute('search')
|
|
72
|
-
* class SearchAutocomplete extends AutocompleteHandler<'search'> {
|
|
73
|
-
* async execute() {
|
|
74
|
-
* await this.match({ query: (value, respond) => respond([{ name: value, value }]) });
|
|
75
|
-
* }
|
|
76
|
-
* }
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
function AutocompleteRoute(...routes) {
|
|
80
|
-
return function(constructor) {
|
|
81
|
-
require_cursor.storeInteractionRoute("autocomplete", routes, constructor);
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Routes one or more context-menu commands to a `ContextMenuHandler`.
|
|
86
|
-
*
|
|
87
|
-
* Pass the kind (`ApplicationCommandType.User` or `ApplicationCommandType.Message`) and the command name(s),
|
|
88
|
-
* each checked against that kind's registry. A typo there is a compile error. The handler's generic must
|
|
89
|
-
* declare the same kind, cross-checked both directions. Context menus carry no options, so a multi-name
|
|
90
|
-
* handler reads `this.target` uniformly with no branch.
|
|
91
|
-
*
|
|
92
|
-
* @param kind - The context-menu kind, `ApplicationCommandType.User` or `ApplicationCommandType.Message`.
|
|
93
|
-
* @param names - The command name(s) this handler serves, keyed off the matching registry.
|
|
94
|
-
* @decorator
|
|
95
|
-
* @example
|
|
96
|
-
* ```ts
|
|
97
|
-
* \@ContextMenuRoute(ApplicationCommandType.User, 'View Profile')
|
|
98
|
-
* class ViewProfile extends ContextMenuHandler<ApplicationCommandType.User> {
|
|
99
|
-
* async execute() {
|
|
100
|
-
* const user = this.target;
|
|
101
|
-
* }
|
|
102
|
-
* }
|
|
103
|
-
* ```
|
|
104
|
-
*/
|
|
105
|
-
function ContextMenuRoute(kind, ...names) {
|
|
106
|
-
return function(constructor) {
|
|
107
|
-
require_cursor.storeInteractionRoute(require_cursor.contextMenuRouteOf(kind), names, constructor);
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Routes button interactions to handler classes.
|
|
112
|
-
*
|
|
113
|
-
* Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
|
|
114
|
-
* generic. Passing different definitions to the decorator and the generic is a compile error. Routing
|
|
115
|
-
* matches the stable prefix, so a wire minted from an older shape still reaches the handler, where
|
|
116
|
-
* reading this.params throws StaleCustomId and the controller boundary turns it into a reply.
|
|
117
|
-
*
|
|
118
|
-
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
119
|
-
* @decorator
|
|
120
|
-
*/
|
|
121
|
-
function ButtonRoute(...defs) {
|
|
122
|
-
return function(constructor) {
|
|
123
|
-
require_cursor.storeComponentRoute("button", defs, constructor);
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Routes modal submissions to handler classes.
|
|
128
|
-
*
|
|
129
|
-
* Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
|
|
130
|
-
* generic. Passing different definitions to the decorator and the generic is a compile error. Routing
|
|
131
|
-
* matches the stable prefix here too, throwing StaleCustomId on read the same as a button route.
|
|
132
|
-
*
|
|
133
|
-
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
134
|
-
* @decorator
|
|
135
|
-
*/
|
|
136
|
-
function ModalRoute(...defs) {
|
|
137
|
-
return function(constructor) {
|
|
138
|
-
require_cursor.storeComponentRoute("modal", defs, constructor);
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Routes select menu interactions to handler classes.
|
|
143
|
-
*
|
|
144
|
-
* Pass the select kind and the {@link CustomId} definition(s) this handler decodes. The handler's
|
|
145
|
-
* generic must list the same kind and definitions, or it is a compile error.
|
|
146
|
-
*
|
|
147
|
-
* @param type - Select menu kind from {@link SelectMenuKind}.
|
|
148
|
-
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
149
|
-
* @decorator
|
|
150
|
-
*
|
|
151
|
-
* @example
|
|
152
|
-
* ```typescript
|
|
153
|
-
* \@SelectMenuRoute(SelectMenuKind.User, AssignId)
|
|
154
|
-
* class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {}
|
|
155
|
-
* ```
|
|
156
|
-
*/
|
|
157
|
-
function SelectMenuRoute(type, ...defs) {
|
|
158
|
-
return function(constructor) {
|
|
159
|
-
require_cursor.storeComponentRoute(require_cursor.selectMenuRouteOf(type), defs, constructor);
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
//#endregion
|
|
164
|
-
//#region src/dispatch/DispatchContext.ts
|
|
165
|
-
/**
|
|
166
|
-
* A typed bag allocated once per interaction dispatch and handed to the handler. The middleware and i18n
|
|
167
|
-
* work reads and writes it through {@link DispatchState}. Today the bag is empty, so it only carries the
|
|
168
|
-
* dispatched `routeId`.
|
|
169
|
-
*/
|
|
170
|
-
var DispatchContext = class {
|
|
171
|
-
routeId;
|
|
172
|
-
state = {};
|
|
173
|
-
constructor(routeId) {
|
|
174
|
-
this.routeId = routeId;
|
|
175
|
-
}
|
|
176
|
-
set(key, value) {
|
|
177
|
-
this.state[key] = value;
|
|
178
|
-
}
|
|
179
|
-
get(key) {
|
|
180
|
-
return this.state[key];
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
//#endregion
|
|
185
|
-
//#region src/handlers/BaseHandler.ts
|
|
186
|
-
/**
|
|
187
|
-
* Base class every transport handler extends. Don't register handlers directly. Use the more specific
|
|
188
|
-
* handler subclasses.
|
|
189
|
-
*
|
|
190
|
-
* @typeParam Event - The event or interaction this handler processes
|
|
191
|
-
* @typeParam TCore - The transport's Core
|
|
192
|
-
*/
|
|
193
|
-
var BaseHandler = class {
|
|
194
|
-
core;
|
|
195
|
-
event;
|
|
196
|
-
logger;
|
|
197
|
-
dispatch;
|
|
198
|
-
constructor(event, core, dispatch, channel) {
|
|
199
|
-
this.core = core;
|
|
200
|
-
this.event = event;
|
|
201
|
-
this.logger = new _seedcord_logger.Logger(this.constructor.name, { channel });
|
|
202
|
-
this.dispatch = dispatch;
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
//#endregion
|
|
207
|
-
//#region src/handlers/RepliableHandler.ts
|
|
208
|
-
/**
|
|
209
|
-
* Shared base the repliable handlers on both transports extend. Defines the reply members over the sender
|
|
210
|
-
* the transport builds.
|
|
211
|
-
*
|
|
212
|
-
* @typeParam Event - The repliable interaction type this handler processes
|
|
213
|
-
* @typeParam TCore - The transport's Core
|
|
214
|
-
* @typeParam TMessage - The transport's created-message lens
|
|
215
|
-
* @typeParam TNative - The transport's own file forms, beyond the portable `ReplyFile`
|
|
216
|
-
* @typeParam TSender - The transport's reply sender
|
|
217
|
-
*/
|
|
218
|
-
var RepliableHandler = class extends BaseHandler {
|
|
219
|
-
sender;
|
|
220
|
-
routeId;
|
|
221
|
-
constructor(event, core, dispatch) {
|
|
222
|
-
super(event, core, dispatch, "interactions");
|
|
223
|
-
this.routeId = dispatch?.routeId ?? this.constructor.name;
|
|
224
|
-
this.sender = this.buildSender(event, core, this.routeId);
|
|
225
|
-
}
|
|
226
|
-
/** @internal The dispatcher's fault boundary replies through the live ack state. */
|
|
227
|
-
getSender() {
|
|
228
|
-
return this.sender;
|
|
229
|
-
}
|
|
230
|
-
/** Send the initial response, exactly once. */
|
|
231
|
-
reply(response, opts) {
|
|
232
|
-
return this.sender.reply(response, opts);
|
|
233
|
-
}
|
|
234
|
-
/** Show a "thinking" placeholder, then fill it later with {@link edit}. */
|
|
235
|
-
defer(opts) {
|
|
236
|
-
return this.sender.defer(opts);
|
|
237
|
-
}
|
|
238
|
-
/** Send a new message once the interaction is acknowledged. */
|
|
239
|
-
followUp(response, opts) {
|
|
240
|
-
return this.sender.followUp(response, opts);
|
|
241
|
-
}
|
|
242
|
-
edit(targetOrResponse, maybeResponse) {
|
|
243
|
-
if (maybeResponse === void 0) return this.sender.edit(targetOrResponse);
|
|
244
|
-
return this.sender.edit(targetOrResponse, maybeResponse);
|
|
245
|
-
}
|
|
246
|
-
/** Routes to reply, followUp, or edit based on the current ack state. */
|
|
247
|
-
send(response, opts) {
|
|
248
|
-
return this.sender.send(response, opts);
|
|
249
|
-
}
|
|
250
|
-
/** Delete the initial reply or deferred placeholder. Pass a target to delete a message a prior send returned. */
|
|
251
|
-
delete(target) {
|
|
252
|
-
return target === void 0 ? this.sender.delete() : this.sender.delete(target);
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
//#endregion
|
|
257
|
-
//#region src/stops/Fault.ts
|
|
258
|
-
/**
|
|
259
|
-
* A generic fault you throw after catching an error you do not have a specific message for.
|
|
260
|
-
*
|
|
261
|
-
* The user sees a fixed generic reply with the tracking uuid, never the cause. `report` defaults to
|
|
262
|
-
* `true`, so the framework logs it and publishes it to the `handledException` bus. Pass `report: false`
|
|
263
|
-
* to show the generic reply without the bus publish. The original error is stored as the standard
|
|
264
|
-
* `cause`, so the real stack reaches the webhook. When the user should see a real message for a fault,
|
|
265
|
-
* subclass {@link Notice} and write your own `render` instead.
|
|
266
|
-
*
|
|
267
|
-
* The framework also renders this for an unhandled throw, where it points the user at
|
|
268
|
-
* `ctx.developerUsername`.
|
|
269
|
-
*
|
|
270
|
-
* @example
|
|
271
|
-
* ```ts
|
|
272
|
-
* import { Fault } from '@seedcord/gateway';
|
|
273
|
-
*
|
|
274
|
-
* try {
|
|
275
|
-
* await db.write(record);
|
|
276
|
-
* } catch (cause) {
|
|
277
|
-
* // user sees the generic reply with the uuid, and the real error rides along as cause for the webhook
|
|
278
|
-
* throw new Fault({ cause });
|
|
279
|
-
*
|
|
280
|
-
* // pass report: false to show the same reply without publishing to the handledException bus
|
|
281
|
-
* // throw new Fault({ cause, report: false });
|
|
282
|
-
* }
|
|
283
|
-
* ```
|
|
284
|
-
*/
|
|
285
|
-
var Fault = class extends require_cursor.Notice {
|
|
286
|
-
constructor(options) {
|
|
287
|
-
super("A fault occurred", options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
288
|
-
this.report = options?.report ?? true;
|
|
289
|
-
}
|
|
290
|
-
render(ctx) {
|
|
291
|
-
const contact = ctx.developerUsername ?? "the developer";
|
|
292
|
-
return { components: [new require_cursor.NoticeCard(`Something went wrong. Please reach out to ${contact} with a way to reproduce the error and the following:\n### UUID: \`${ctx.uuid}\``, "Error").component] };
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
//#endregion
|
|
297
|
-
//#region src/gates/combinators.ts
|
|
298
|
-
function and(...gates) {
|
|
299
|
-
return require_cursor.defineGate(gates.map((gate) => gate.name).join(" & "), async (ctx) => {
|
|
300
|
-
for (const gate of gates) await require_cursor.runCheck(gate, ctx);
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
function isOrOptions(arg) {
|
|
304
|
-
return !("check" in arg);
|
|
305
|
-
}
|
|
306
|
-
function or(...args) {
|
|
307
|
-
const last = args.at(-1);
|
|
308
|
-
const options = last !== void 0 && isOrOptions(last) ? last : void 0;
|
|
309
|
-
const gates = options ? args.slice(0, -1) : args;
|
|
310
|
-
return require_cursor.defineGate(gates.map((gate) => gate.name).join(" | "), async (ctx) => {
|
|
311
|
-
const summaries = [];
|
|
312
|
-
let everyArmHasSummary = true;
|
|
313
|
-
for (const gate of gates) {
|
|
314
|
-
const mark = require_cursor.markCommits(ctx);
|
|
315
|
-
try {
|
|
316
|
-
await require_cursor.runCheck(gate, ctx);
|
|
317
|
-
return;
|
|
318
|
-
} catch (error) {
|
|
319
|
-
if (error instanceof require_cursor.Notice && !(error instanceof Fault) && !error.report) {
|
|
320
|
-
require_cursor.rollbackCommits(ctx, mark);
|
|
321
|
-
if (error.summary === void 0) everyArmHasSummary = false;
|
|
322
|
-
else summaries.push(error.summary);
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
throw error;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (options) throw typeof options.fail === "function" ? options.fail(ctx) : options.fail;
|
|
329
|
-
if (everyArmHasSummary && summaries.length > 0) throw new require_cursor.NeedsAny(summaries);
|
|
330
|
-
throw new require_cursor.NotAllowed();
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
//#endregion
|
|
335
|
-
//#region src/gates/catalog/access.ts
|
|
336
|
-
/**
|
|
337
|
-
* Passes only for a user id listed in `config.ownerIds`, else refuses.
|
|
338
|
-
*
|
|
339
|
-
* Agnostic, so it attaches to interaction and event handlers alike. With no `ownerIds` configured it refuses every caller. Pass {@link GateNoticeOptions} to reword or replace the refusal.
|
|
340
|
-
*
|
|
341
|
-
* @param options - Reword the default refusal with `message`, or replace it with `notice`.
|
|
342
|
-
*
|
|
343
|
-
* @see the `@Gated` decorator from `seedcord`
|
|
344
|
-
*
|
|
345
|
-
* @example
|
|
346
|
-
* ```ts
|
|
347
|
-
* \@Gated(OwnerOnly())
|
|
348
|
-
* \@SlashRoute('shutdown')
|
|
349
|
-
* class ShutdownHandler extends SlashHandler<'shutdown'> {
|
|
350
|
-
* async execute() {
|
|
351
|
-
* // ...
|
|
352
|
-
* }
|
|
353
|
-
* }
|
|
354
|
-
* ```
|
|
355
|
-
*
|
|
356
|
-
* @example
|
|
357
|
-
* ```ts
|
|
358
|
-
* // reword the refusal, keeping the embed styling
|
|
359
|
-
* OwnerOnly({ message: 'Owners only.' });
|
|
360
|
-
* ```
|
|
361
|
-
*/
|
|
362
|
-
function OwnerOnly(options) {
|
|
363
|
-
return require_cursor.defineGate("OwnerOnly", (ctx) => {
|
|
364
|
-
const owners = ctx.core.config.ownerIds ?? [];
|
|
365
|
-
if (ctx.userId !== null && owners.includes(ctx.userId)) return;
|
|
366
|
-
throw require_cursor.pickNotice(options, (message) => new require_cursor.NotOwner(message));
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Passes inside a guild, else refuses.
|
|
371
|
-
*
|
|
372
|
-
* Agnostic. Attaches to any handler kind. Often paired with `RequirePermissions` in an {@link and}.
|
|
373
|
-
*
|
|
374
|
-
* @param options - Reword the default refusal with `message`, or replace it with `notice`.
|
|
375
|
-
*
|
|
376
|
-
* @see the `@Gated` decorator from `seedcord`
|
|
377
|
-
*
|
|
378
|
-
* @example
|
|
379
|
-
* ```ts
|
|
380
|
-
* import { PermissionFlagsBits } from 'discord.js';
|
|
381
|
-
*
|
|
382
|
-
* \@Gated(GuildOnly(), RequirePermissions([PermissionFlagsBits.Administrator]))
|
|
383
|
-
* \@SlashRoute('maintenance')
|
|
384
|
-
* class Maintenance extends SlashHandler<'maintenance'> {
|
|
385
|
-
* async execute() {
|
|
386
|
-
* // ...
|
|
387
|
-
* }
|
|
388
|
-
* }
|
|
389
|
-
* ```
|
|
390
|
-
*/
|
|
391
|
-
function GuildOnly(options) {
|
|
392
|
-
return require_cursor.defineGate("GuildOnly", (ctx) => {
|
|
393
|
-
if (ctx.guildId) return;
|
|
394
|
-
throw require_cursor.pickNotice(options, (message) => new require_cursor.NotInGuild(message));
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Passes in a direct message, else refuses.
|
|
399
|
-
*
|
|
400
|
-
* Agnostic, fitting any handler kind. The inverse of {@link GuildOnly}, so combining the two in an {@link and} can never pass.
|
|
401
|
-
*
|
|
402
|
-
* @param options - Reword the default refusal with `message`, or replace it with `notice`.
|
|
403
|
-
*
|
|
404
|
-
* @see the `@Gated` decorator from `seedcord`
|
|
405
|
-
*
|
|
406
|
-
* @example
|
|
407
|
-
* ```ts
|
|
408
|
-
* \@Gated(DmOnly())
|
|
409
|
-
* \@SlashRoute('verify')
|
|
410
|
-
* class VerifyHandler extends SlashHandler<'verify'> {
|
|
411
|
-
* async execute() {
|
|
412
|
-
* // ...
|
|
413
|
-
* }
|
|
414
|
-
* }
|
|
415
|
-
* ```
|
|
416
|
-
*/
|
|
417
|
-
function DmOnly(options) {
|
|
418
|
-
return require_cursor.defineGate("DmOnly", (ctx) => {
|
|
419
|
-
if (!ctx.guildId) return;
|
|
420
|
-
throw require_cursor.pickNotice(options, (message) => new require_cursor.NotInDm(message));
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
//#endregion
|
|
425
|
-
//#region src/gates/catalog/Cooldown.ts
|
|
426
|
-
let anonSeq = 0;
|
|
427
|
-
function scopeValue(ctx, per) {
|
|
428
|
-
if (per === "guild") return ctx.guildId ?? "global";
|
|
429
|
-
if (per === "channel") return ctx.channelId ?? "global";
|
|
430
|
-
return ctx.userId ?? "global";
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Allows `limit` uses per window, scoped by `per`. A number `duration` is **seconds**, and a string is
|
|
434
|
-
* a duration like `30m` or `24h`. An unparseable string throws a **SeedcordTypeError** at construction. The
|
|
435
|
-
* slot is charged in commit, only after the whole gate set passes, so a later refusal never burns the cooldown.
|
|
436
|
-
* That split also means two requests racing the same key can both pass before either charges the slot.
|
|
437
|
-
* The key combines the handler's route with the window settings, keeping two different handlers on separate
|
|
438
|
-
* windows. Reword the refusal with {@link CooldownOptions.message} or replace it with {@link CooldownOptions.notice}.
|
|
439
|
-
*
|
|
440
|
-
* @param duration - A number is seconds, and a string is a duration like `30m` or `24h`. An unparseable string throws a **SeedcordTypeError**.
|
|
441
|
-
* @param options - Sets the scope with `per`, the uses per window with `limit`, and the refusal text with `message` or `notice`.
|
|
442
|
-
*
|
|
443
|
-
* @see the `@Gated` decorator from `seedcord`
|
|
444
|
-
* @see {@link IRateLimiter}
|
|
445
|
-
*
|
|
446
|
-
* @example
|
|
447
|
-
* ```ts
|
|
448
|
-
* // a number argument is SECONDS
|
|
449
|
-
* \@Gated(Cooldown(5))
|
|
450
|
-
* \@SlashRoute('daily')
|
|
451
|
-
* class DailyHandler extends SlashHandler<'daily'> {
|
|
452
|
-
* async execute() {
|
|
453
|
-
* // ...
|
|
454
|
-
* }
|
|
455
|
-
* }
|
|
456
|
-
* ```
|
|
457
|
-
*
|
|
458
|
-
* @example
|
|
459
|
-
* ```ts
|
|
460
|
-
* // a string is a duration, here scoped per guild (the default is per user)
|
|
461
|
-
* Cooldown('30m', { per: 'guild' });
|
|
462
|
-
* ```
|
|
463
|
-
*
|
|
464
|
-
* @example
|
|
465
|
-
* ```ts
|
|
466
|
-
* // a burst, 3 uses per 10 minutes per user, then it refuses until a slot frees
|
|
467
|
-
* \@Gated(Cooldown('10m', { limit: 3 }))
|
|
468
|
-
* \@SlashRoute('report')
|
|
469
|
-
* class ReportHandler extends SlashHandler<'report'> {
|
|
470
|
-
* async execute() {
|
|
471
|
-
* // ...
|
|
472
|
-
* }
|
|
473
|
-
* }
|
|
474
|
-
* ```
|
|
475
|
-
*/
|
|
476
|
-
function Cooldown(duration, options) {
|
|
477
|
-
let windowMs;
|
|
478
|
-
if (typeof duration === "number") windowMs = duration * 1e3;
|
|
479
|
-
else {
|
|
480
|
-
const parsed = (0, _seedcord_utils.parseDuration)(duration);
|
|
481
|
-
if (parsed === null) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.GateInvalidCooldownDuration, [duration]);
|
|
482
|
-
windowMs = parsed;
|
|
483
|
-
}
|
|
484
|
-
if (windowMs <= 0) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.GateInvalidCooldownDuration, [String(duration)]);
|
|
485
|
-
const per = options?.per ?? "user";
|
|
486
|
-
const window = options?.limit === void 0 ? { windowMs } : {
|
|
487
|
-
windowMs,
|
|
488
|
-
limit: options.limit
|
|
489
|
-
};
|
|
490
|
-
const anonId = anonSeq++;
|
|
491
|
-
const keyOf = (ctx) => `cooldown:${ctx.routeId ?? `anon${anonId}`}:${per}:w${windowMs}:l${options?.limit ?? 1}:${scopeValue(ctx, per)}`;
|
|
492
|
-
return require_cursor.defineEffectGate("Cooldown", async (ctx) => {
|
|
493
|
-
const result = await ctx.core.rateLimiter.peek(keyOf(ctx), window);
|
|
494
|
-
if (!result.limited) return;
|
|
495
|
-
if (options?.notice) throw options.notice(result.resetAt);
|
|
496
|
-
throw new require_cursor.OnCooldown(result.resetAt, options?.message?.(result.resetAt));
|
|
497
|
-
}, async (ctx) => {
|
|
498
|
-
await ctx.core.rateLimiter.charge(keyOf(ctx), window);
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
//#endregion
|
|
503
|
-
//#region src/permissions/assert.ts
|
|
504
|
-
/**
|
|
505
|
-
* Refuses when the subject is missing a permission in `scope`, or with `inverse` when it holds one.
|
|
506
|
-
* The Administrator bit passes any scope, and on the inverse arm it reports every bit in `scope`.
|
|
507
|
-
*
|
|
508
|
-
* The caller computes `permissions`, gateway off a discord.js role, member, or channel, and HTTP off the
|
|
509
|
-
* bitfield the interaction payload carries.
|
|
510
|
-
*
|
|
511
|
-
* @throws A {@link Notice} listing the missing or present permissions.
|
|
512
|
-
*
|
|
513
|
-
* @example
|
|
514
|
-
* ```ts
|
|
515
|
-
* import { assertPermissions } from '@seedcord/core';
|
|
516
|
-
* import { PermissionFlagsBits } from 'discord-api-types/v10';
|
|
517
|
-
*
|
|
518
|
-
* // gateway, the bits come off a discord.js member
|
|
519
|
-
* assertPermissions({
|
|
520
|
-
* subject: `<@${member.id}>`,
|
|
521
|
-
* permissions: member.permissions.bitfield,
|
|
522
|
-
* scope: [PermissionFlagsBits.BanMembers]
|
|
523
|
-
* });
|
|
524
|
-
* ```
|
|
525
|
-
*
|
|
526
|
-
* @example
|
|
527
|
-
* ```ts
|
|
528
|
-
* // HTTP, the payload carries the member's bits as a string
|
|
529
|
-
* assertPermissions({
|
|
530
|
-
* subject: `<@${this.event.member.user.id}>`,
|
|
531
|
-
* permissions: BigInt(this.event.member.permissions),
|
|
532
|
-
* scope: [PermissionFlagsBits.ManageMessages]
|
|
533
|
-
* });
|
|
534
|
-
* ```
|
|
535
|
-
*
|
|
536
|
-
* @example
|
|
537
|
-
* ```ts
|
|
538
|
-
* // inverse, refuse a role that carries Administrator
|
|
539
|
-
* assertPermissions({
|
|
540
|
-
* subject: `<@&${role.id}>`,
|
|
541
|
-
* permissions: role.permissions.bitfield,
|
|
542
|
-
* scope: [PermissionFlagsBits.Administrator],
|
|
543
|
-
* inverse: true
|
|
544
|
-
* });
|
|
545
|
-
* ```
|
|
546
|
-
*
|
|
547
|
-
* @example
|
|
548
|
-
* ```ts
|
|
549
|
-
* import { Notice } from '@seedcord/core';
|
|
550
|
-
*
|
|
551
|
-
* // a replacement notice takes the same three arguments the default one does
|
|
552
|
-
* class CannotBan extends Notice {
|
|
553
|
-
* public constructor(
|
|
554
|
-
* message: string | undefined,
|
|
555
|
-
* private readonly subject: string,
|
|
556
|
-
* private readonly missing: readonly string[]
|
|
557
|
-
* ) {
|
|
558
|
-
* super(message ?? 'You cannot ban here.');
|
|
559
|
-
* }
|
|
560
|
-
*
|
|
561
|
-
* public render(): ReplyResponse {
|
|
562
|
-
* return { components: [new BanDenied(this.subject, this.missing).component] };
|
|
563
|
-
* }
|
|
564
|
-
* }
|
|
565
|
-
*
|
|
566
|
-
* assertPermissions({
|
|
567
|
-
* subject: `<@${member.id}>`,
|
|
568
|
-
* permissions: member.permissions.bitfield,
|
|
569
|
-
* scope: [PermissionFlagsBits.BanMembers],
|
|
570
|
-
* missingNotice: CannotBan
|
|
571
|
-
* });
|
|
572
|
-
* ```
|
|
573
|
-
*/
|
|
574
|
-
function assertPermissions(check) {
|
|
575
|
-
const { subject, permissions, scope, inverse = false, missingNotice, dangerousNotice } = check;
|
|
576
|
-
if (inverse) {
|
|
577
|
-
const present = require_cursor.presentNames(permissions, scope);
|
|
578
|
-
if (present.length === 0) return;
|
|
579
|
-
throw new (dangerousNotice ?? require_cursor.HasDangerousPermissions)(void 0, subject, present);
|
|
580
|
-
}
|
|
581
|
-
if (require_cursor.hasAll(permissions, scope)) return;
|
|
582
|
-
throw new (missingNotice ?? require_cursor.MissingPermissions)(void 0, subject, require_cursor.missingNames(permissions, scope));
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
//#endregion
|
|
586
|
-
//#region src/permissions/mergeRoles.ts
|
|
587
|
-
/**
|
|
588
|
-
* The role ids a member holds after adding and removing the given sets. Removals win over additions.
|
|
589
|
-
*
|
|
590
|
-
* This does not write the roles. Apply the result with `member.roles.set()` on gateway, or PATCH the member over HTTP.
|
|
591
|
-
*
|
|
592
|
-
* @param current - The role ids the member holds.
|
|
593
|
-
* @param add - The role ids to add.
|
|
594
|
-
* @param remove - The role ids to remove.
|
|
595
|
-
* @returns The merged role ids.
|
|
596
|
-
*
|
|
597
|
-
* @example
|
|
598
|
-
* ```ts
|
|
599
|
-
* import { mergeRoles } from '@seedcord/core';
|
|
600
|
-
*
|
|
601
|
-
* // gateway
|
|
602
|
-
* const ids = mergeRoles(member.roles.cache.map((role) => role.id), ['123'], ['456']);
|
|
603
|
-
* await member.roles.set(ids);
|
|
604
|
-
* ```
|
|
605
|
-
*/
|
|
606
|
-
function mergeRoles(current, add, remove) {
|
|
607
|
-
return [...new Set(current).union(new Set(add)).difference(new Set(remove))];
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
//#endregion
|
|
611
|
-
//#region src/pagination/paginate.ts
|
|
612
|
-
/**
|
|
613
|
-
* Pure page math, usable headless. Clamps `page` into range and slices the window.
|
|
614
|
-
*
|
|
615
|
-
* @param items - The full list to page over.
|
|
616
|
-
* @param page - The requested zero-based page, clamped into `[0, totalPages - 1]`.
|
|
617
|
-
* @param perPage - The page size. Must be a positive integer.
|
|
618
|
-
* @returns The sliced {@link PageView}, with `totalPages` always known.
|
|
619
|
-
* @throws SeedcordRangeError when `perPage` is not a positive integer.
|
|
620
|
-
*/
|
|
621
|
-
function paginate(items, page, perPage) {
|
|
622
|
-
if (!Number.isSafeInteger(perPage) || perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [perPage]);
|
|
623
|
-
const totalPages = Math.max(1, Math.ceil(items.length / perPage));
|
|
624
|
-
const clamped = Math.min(Math.max(Math.trunc(page), 0), totalPages - 1);
|
|
625
|
-
const start = clamped * perPage;
|
|
626
|
-
return {
|
|
627
|
-
items: items.slice(start, start + perPage),
|
|
628
|
-
page: clamped,
|
|
629
|
-
perPage,
|
|
630
|
-
totalPages,
|
|
631
|
-
hasPrev: clamped > 0,
|
|
632
|
-
hasNext: clamped < totalPages - 1
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
//#endregion
|
|
637
|
-
//#region src/pagination/controls.ts
|
|
638
|
-
const DEFAULT_LABEL = {
|
|
639
|
-
first: "First",
|
|
640
|
-
prev: "Prev",
|
|
641
|
-
indicator: "",
|
|
642
|
-
next: "Next",
|
|
643
|
-
last: "Last"
|
|
644
|
-
};
|
|
645
|
-
const CONTROL_SLOT = {
|
|
646
|
-
first: 0,
|
|
647
|
-
prev: 1,
|
|
648
|
-
indicator: 2,
|
|
649
|
-
next: 3,
|
|
650
|
-
last: 4
|
|
651
|
-
};
|
|
652
|
-
const MAX_BUTTONS_PER_ROW = 5;
|
|
653
|
-
var Controls = class {
|
|
654
|
-
cursor;
|
|
655
|
-
view;
|
|
656
|
-
constructor(cursor, view) {
|
|
657
|
-
this.cursor = cursor;
|
|
658
|
-
this.view = view;
|
|
659
|
-
}
|
|
660
|
-
button(key, cosmetics) {
|
|
661
|
-
const { target, disabled } = this.navState(key);
|
|
662
|
-
const defaultLabel = key === "indicator" ? this.indicatorText() : DEFAULT_LABEL[key];
|
|
663
|
-
const label = cosmetics?.label ?? (key === "indicator" || !cosmetics?.emoji ? defaultLabel : void 0);
|
|
664
|
-
const button = new _discordjs_builders.ButtonBuilder().setCustomId(this.cursor.encode({
|
|
665
|
-
page: Math.min(Math.max(0, target), require_cursor.PAGE_MAX),
|
|
666
|
-
slot: CONTROL_SLOT[key]
|
|
667
|
-
})).setStyle(cosmetics?.style ?? discord_api_types_v10.ButtonStyle.Secondary).setDisabled(disabled);
|
|
668
|
-
if (label !== void 0) button.setLabel(label);
|
|
669
|
-
if (cosmetics?.emoji) button.setEmoji(cosmetics.emoji);
|
|
670
|
-
return button;
|
|
671
|
-
}
|
|
672
|
-
navState(key) {
|
|
673
|
-
const { page, totalPages, hasPrev, hasNext } = this.view;
|
|
674
|
-
const knownTotal = totalPages !== void 0;
|
|
675
|
-
return {
|
|
676
|
-
target: {
|
|
677
|
-
first: 0,
|
|
678
|
-
prev: page - 1,
|
|
679
|
-
indicator: page,
|
|
680
|
-
next: page + 1,
|
|
681
|
-
last: knownTotal ? totalPages - 1 : page
|
|
682
|
-
}[key],
|
|
683
|
-
disabled: {
|
|
684
|
-
first: !hasPrev,
|
|
685
|
-
prev: !hasPrev,
|
|
686
|
-
indicator: true,
|
|
687
|
-
next: !hasNext || page + 1 > 1e6,
|
|
688
|
-
last: !hasNext || !knownTotal || totalPages - 1 > 1e6
|
|
689
|
-
}[key]
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
row(...keys) {
|
|
693
|
-
if (keys.length === 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationEmptyControls);
|
|
694
|
-
if (keys.length > MAX_BUTTONS_PER_ROW) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationTooManyControls, [keys.length]);
|
|
695
|
-
const seen = /* @__PURE__ */ new Set();
|
|
696
|
-
for (const key of keys) {
|
|
697
|
-
if (seen.has(key)) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.PaginationDuplicateControls, [key]);
|
|
698
|
-
seen.add(key);
|
|
699
|
-
}
|
|
700
|
-
return new _discordjs_builders.ActionRowBuilder().addComponents(keys.map((key) => this.button(key)));
|
|
701
|
-
}
|
|
702
|
-
indicatorText() {
|
|
703
|
-
const { page, totalPages } = this.view;
|
|
704
|
-
return totalPages === void 0 ? `Page ${page + 1}` : `Page ${page + 1} of ${totalPages}`;
|
|
705
|
-
}
|
|
706
|
-
};
|
|
707
|
-
|
|
708
|
-
//#endregion
|
|
709
|
-
//#region src/pagination/render.ts
|
|
710
|
-
var PageContainer = class extends require_publishDefault.BuilderComponent {
|
|
711
|
-
constructor(view, renderItem, controlRow) {
|
|
712
|
-
super("container");
|
|
713
|
-
const base = view.page * view.perPage;
|
|
714
|
-
let lines = [];
|
|
715
|
-
const flush = () => {
|
|
716
|
-
if (lines.length === 0) return;
|
|
717
|
-
this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(lines.join("\n")));
|
|
718
|
-
lines = [];
|
|
719
|
-
};
|
|
720
|
-
view.items.forEach((item, offset) => {
|
|
721
|
-
const rendered = renderItem(item, base + offset);
|
|
722
|
-
if (typeof rendered === "string") {
|
|
723
|
-
lines.push(rendered);
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
flush();
|
|
727
|
-
this.instance.addSectionComponents(rendered.component);
|
|
728
|
-
});
|
|
729
|
-
flush();
|
|
730
|
-
this.instance.addActionRowComponents(controlRow);
|
|
731
|
-
}
|
|
732
|
-
};
|
|
733
|
-
function toReplyResponse(renderable) {
|
|
734
|
-
if (typeof renderable === "string") return { components: [new _discordjs_builders.TextDisplayBuilder().setContent(renderable)] };
|
|
735
|
-
if (Array.isArray(renderable)) return { components: renderable };
|
|
736
|
-
return renderable;
|
|
737
|
-
}
|
|
738
|
-
const ARRAY_KEYS = [
|
|
739
|
-
"first",
|
|
740
|
-
"prev",
|
|
741
|
-
"indicator",
|
|
742
|
-
"next",
|
|
743
|
-
"last"
|
|
744
|
-
];
|
|
745
|
-
const CURSOR_KEYS = [
|
|
746
|
-
"prev",
|
|
747
|
-
"indicator",
|
|
748
|
-
"next"
|
|
749
|
-
];
|
|
750
|
-
function renderPage(view, cursor, config) {
|
|
751
|
-
const controls = new Controls(cursor, view);
|
|
752
|
-
if (config.render) return toReplyResponse(config.render(view, controls));
|
|
753
|
-
const renderItem = config.renderItem ?? String;
|
|
754
|
-
const row = controls.row(...view.totalPages === void 0 ? CURSOR_KEYS : ARRAY_KEYS);
|
|
755
|
-
return { components: [new PageContainer(view, renderItem, row).component] };
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
//#endregion
|
|
759
|
-
//#region src/pagination/PaginatorBase.ts
|
|
760
|
-
/**
|
|
761
|
-
* Holds a paginator's cursor and renders one page. Each transport extends this to add the nav handler and
|
|
762
|
-
* the method that sends the first page.
|
|
763
|
-
*
|
|
764
|
-
* @typeParam Item - The item type, inferred from the source.
|
|
765
|
-
* @typeParam Prefix - The route prefix, inferred from `config.prefix`.
|
|
766
|
-
* @typeParam Ctx - The transport's page context.
|
|
767
|
-
*/
|
|
768
|
-
var PaginatorBase = class {
|
|
769
|
-
/** The page cursor, pass it to your `@ButtonRoute`. */
|
|
770
|
-
cursor;
|
|
771
|
-
config;
|
|
772
|
-
constructor(config) {
|
|
773
|
-
this.cursor = require_cursor.pageCursor(config.prefix);
|
|
774
|
-
this.config = config;
|
|
775
|
-
}
|
|
776
|
-
/** Render a page as a {@link ReplyResponse}. To post it elsewhere, add `flags: MessageFlags.IsComponentsV2`. */
|
|
777
|
-
async page(ctx, n) {
|
|
778
|
-
const view = await this.config.source.page(ctx, n);
|
|
779
|
-
return renderPage(view, this.cursor, this.config);
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
//#endregion
|
|
784
|
-
//#region src/pagination/sources.ts
|
|
785
|
-
const DEFAULT_PER_PAGE = 10;
|
|
786
|
-
function perPageOf(opts) {
|
|
787
|
-
const perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
|
|
788
|
-
if (!Number.isSafeInteger(perPage) || perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [perPage]);
|
|
789
|
-
return perPage;
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* A source for a bounded list you can load whole. It loads the full list on every click, then slices the
|
|
793
|
-
* page via {@link paginate}, so the real total is known (a real last button, "Page X of Y"). Cache inside
|
|
794
|
-
* your loader if the load is expensive.
|
|
795
|
-
*
|
|
796
|
-
* @typeParam Item - The item type, inferred from the loader's return.
|
|
797
|
-
* @typeParam Ctx - The transport's page context. Each transport exports a subclass that fixes it.
|
|
798
|
-
*/
|
|
799
|
-
var ArraySource = class {
|
|
800
|
-
load;
|
|
801
|
-
perPage;
|
|
802
|
-
constructor(load, opts) {
|
|
803
|
-
this.load = load;
|
|
804
|
-
this.perPage = perPageOf(opts);
|
|
805
|
-
}
|
|
806
|
-
async page(ctx, n) {
|
|
807
|
-
return paginate(await this.load(ctx), n, this.perPage);
|
|
808
|
-
}
|
|
809
|
-
};
|
|
810
|
-
/**
|
|
811
|
-
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
812
|
-
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
813
|
-
* source has no cheap total, so `totalPages` is undefined, the last button is omitted, and the indicator
|
|
814
|
-
* reads "Page X".
|
|
815
|
-
*
|
|
816
|
-
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
817
|
-
* @typeParam Ctx - The transport's page context. Each transport exports a subclass that fixes it.
|
|
818
|
-
*/
|
|
819
|
-
var CursorSource = class {
|
|
820
|
-
fetch;
|
|
821
|
-
perPage;
|
|
822
|
-
constructor(fetch, opts) {
|
|
823
|
-
this.fetch = fetch;
|
|
824
|
-
this.perPage = perPageOf(opts);
|
|
825
|
-
}
|
|
826
|
-
async page(ctx, n) {
|
|
827
|
-
const page = Math.max(0, Math.trunc(n));
|
|
828
|
-
const { items, hasNext } = await this.fetch(ctx, page, this.perPage);
|
|
829
|
-
return {
|
|
830
|
-
items: [...items],
|
|
831
|
-
page,
|
|
832
|
-
perPage: this.perPage,
|
|
833
|
-
hasPrev: page > 0,
|
|
834
|
-
hasNext
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
};
|
|
838
|
-
|
|
839
|
-
//#endregion
|
|
840
|
-
//#region src/index.ts
|
|
841
|
-
/** Package version */
|
|
842
|
-
const version = "0.1.4";
|
|
843
|
-
|
|
844
|
-
//#endregion
|
|
845
|
-
exports.ArraySource = ArraySource;
|
|
846
|
-
exports.AutocompleteRoute = AutocompleteRoute;
|
|
847
|
-
exports.BaseHandler = BaseHandler;
|
|
848
|
-
exports.BuilderComponent = require_publishDefault.BuilderComponent;
|
|
849
|
-
exports.Bus = require_subscribers.Bus;
|
|
850
|
-
exports.ButtonRoute = ButtonRoute;
|
|
851
|
-
exports.Commands = require_cursor.Commands;
|
|
852
|
-
exports.ContextMenuRoute = ContextMenuRoute;
|
|
853
|
-
exports.ContextMenus = require_cursor.ContextMenus;
|
|
854
|
-
exports.Cooldown = Cooldown;
|
|
855
|
-
exports.CursorSource = CursorSource;
|
|
856
|
-
exports.CustomId = require_cursor.CustomId;
|
|
857
|
-
exports.DispatchContext = DispatchContext;
|
|
858
|
-
exports.DmOnly = DmOnly;
|
|
859
|
-
exports.Fault = Fault;
|
|
860
|
-
exports.GuildOnly = GuildOnly;
|
|
861
|
-
exports.ModalRoute = ModalRoute;
|
|
862
|
-
exports.Notice = require_cursor.Notice;
|
|
863
|
-
exports.OwnerOnly = OwnerOnly;
|
|
864
|
-
exports.PaginatorBase = PaginatorBase;
|
|
865
|
-
exports.RegisterCommand = RegisterCommand;
|
|
866
|
-
exports.RepliableHandler = RepliableHandler;
|
|
867
|
-
exports.RequireBotPermissions = require_cursor.RequireBotPermissions;
|
|
868
|
-
exports.RequirePermissions = require_cursor.RequirePermissions;
|
|
869
|
-
exports.RequireRole = require_cursor.RequireRole;
|
|
870
|
-
exports.RowComponent = require_publishDefault.RowComponent;
|
|
871
|
-
exports.SelectMenuKind = require_cursor.SelectMenuKind;
|
|
872
|
-
exports.SelectMenuRoute = SelectMenuRoute;
|
|
873
|
-
exports.ShutdownPhase = require_phases.ShutdownPhase;
|
|
874
|
-
exports.Silence = require_cursor.Silence;
|
|
875
|
-
exports.SlashRoute = SlashRoute;
|
|
876
|
-
exports.StartupPhase = require_phases.StartupPhase;
|
|
877
|
-
exports.Subscribe = require_subscribers.Subscribe;
|
|
878
|
-
exports.Subscriber = require_subscribers.Subscriber;
|
|
879
|
-
exports.WebhookLog = require_subscribers.WebhookLog;
|
|
880
|
-
exports.WebhookUrl = require_subscribers.WebhookUrl;
|
|
881
|
-
exports.and = and;
|
|
882
|
-
exports.assertPermissions = assertPermissions;
|
|
883
|
-
exports.defineEffectGate = require_cursor.defineEffectGate;
|
|
884
|
-
exports.defineGate = require_cursor.defineGate;
|
|
885
|
-
exports.mergeRoles = mergeRoles;
|
|
886
|
-
exports.or = or;
|
|
887
|
-
exports.paginate = paginate;
|
|
888
|
-
exports.version = version;
|
|
889
|
-
//# sourceMappingURL=index.cjs.map
|