seedcord 0.14.0 → 0.15.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/dist/index.cjs +246 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +150 -12
- package/dist/index.mjs +247 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -467,7 +467,7 @@ function AutocompleteRoute(...routes) {
|
|
|
467
467
|
* @example
|
|
468
468
|
* ```typescript
|
|
469
469
|
* \@SelectMenuRoute(SelectMenuKind.User, AssignId)
|
|
470
|
-
* class AssignSelect extends
|
|
470
|
+
* class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {
|
|
471
471
|
* // handles user select menus minted from AssignId
|
|
472
472
|
* }
|
|
473
473
|
* ```
|
|
@@ -1675,9 +1675,8 @@ var ReplySender = class {
|
|
|
1675
1675
|
async dispatch(response, ephemeral) {
|
|
1676
1676
|
if (this.interaction.replied) return await this.interaction.followUp(this.replyOptions(response, ephemeral));
|
|
1677
1677
|
if (this.interaction.deferred) {
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
return message;
|
|
1678
|
+
if (this.interaction.ephemeral === null) return await this.interaction.followUp(this.replyOptions(response, ephemeral));
|
|
1679
|
+
return await this.interaction.editReply(this.editBody(response));
|
|
1681
1680
|
}
|
|
1682
1681
|
await this.interaction.reply(this.replyOptions(response, ephemeral));
|
|
1683
1682
|
return await this.interaction.fetchReply();
|
|
@@ -1698,13 +1697,6 @@ var ReplySender = class {
|
|
|
1698
1697
|
...response.files && { files: response.files }
|
|
1699
1698
|
};
|
|
1700
1699
|
}
|
|
1701
|
-
async clearStaleDefer() {
|
|
1702
|
-
try {
|
|
1703
|
-
await this.interaction.deleteReply();
|
|
1704
|
-
} catch (error) {
|
|
1705
|
-
this.logSwallowed("clear stale defer", error);
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
1700
|
logSwallowed(action, error) {
|
|
1709
1701
|
if (error instanceof discord_js.DiscordAPIError && HARMLESS_API_CODES.has(error.code)) {
|
|
1710
1702
|
this.logger.debug(`reply ${action} hit harmless code ${error.code}`);
|
|
@@ -1939,7 +1931,7 @@ var InteractionMiddleware = class extends BaseHandler {
|
|
|
1939
1931
|
* Shared base the typed interaction handlers extend.
|
|
1940
1932
|
*
|
|
1941
1933
|
* Not a public entry point. You should be using {@link SlashHandler}, {@link ButtonHandler}, {@link ModalHandler},
|
|
1942
|
-
* or {@link
|
|
1934
|
+
* or {@link SelectMenuHandler} instead. This class only carries the repliable-event plumbing those bases share,
|
|
1943
1935
|
* so DO NOT use it directly.
|
|
1944
1936
|
*
|
|
1945
1937
|
* @typeParam Repliable - The interaction type this handler processes
|
|
@@ -2160,7 +2152,7 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
2160
2152
|
/**
|
|
2161
2153
|
* Shared base the customId-routed component handlers extend.
|
|
2162
2154
|
*
|
|
2163
|
-
* Not a public entry point. You should be using {@link ButtonHandler}, {@link
|
|
2155
|
+
* Not a public entry point. You should be using {@link ButtonHandler}, {@link SelectMenuHandler}, or
|
|
2164
2156
|
* {@link ModalHandler} instead. This class only carries the customId decode and route-matching plumbing
|
|
2165
2157
|
* those bases share, so DO NOT use it directly.
|
|
2166
2158
|
*
|
|
@@ -2276,7 +2268,7 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
2276
2268
|
var ModalHandler = class extends ComponentHandler {};
|
|
2277
2269
|
|
|
2278
2270
|
//#endregion
|
|
2279
|
-
//#region src/handlers/interaction/components/
|
|
2271
|
+
//#region src/handlers/interaction/components/SelectMenuHandler.ts
|
|
2280
2272
|
/**
|
|
2281
2273
|
* Base class for a select menu handler.
|
|
2282
2274
|
*
|
|
@@ -2291,7 +2283,7 @@ var ModalHandler = class extends ComponentHandler {};
|
|
|
2291
2283
|
* @example
|
|
2292
2284
|
* ```ts
|
|
2293
2285
|
* \@SelectMenuRoute(SelectMenuKind.User, AssignId)
|
|
2294
|
-
* class AssignSelect extends
|
|
2286
|
+
* class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {
|
|
2295
2287
|
* async execute() {
|
|
2296
2288
|
* const { roleId } = this.params;
|
|
2297
2289
|
* await this.event.reply(`assigning ${this.event.values.length} member(s) to <@&${roleId}>`);
|
|
@@ -2299,7 +2291,7 @@ var ModalHandler = class extends ComponentHandler {};
|
|
|
2299
2291
|
* }
|
|
2300
2292
|
* ```
|
|
2301
2293
|
*/
|
|
2302
|
-
var
|
|
2294
|
+
var SelectMenuHandler = class extends ComponentHandler {};
|
|
2303
2295
|
|
|
2304
2296
|
//#endregion
|
|
2305
2297
|
//#region src/handlers/event/EventHandler.ts
|
|
@@ -2395,6 +2387,239 @@ var EventMiddleware = class extends BaseHandler {
|
|
|
2395
2387
|
}
|
|
2396
2388
|
};
|
|
2397
2389
|
|
|
2390
|
+
//#endregion
|
|
2391
|
+
//#region src/pagination/controls.ts
|
|
2392
|
+
const DEFAULT_LABEL = {
|
|
2393
|
+
first: "First",
|
|
2394
|
+
prev: "Prev",
|
|
2395
|
+
indicator: "",
|
|
2396
|
+
next: "Next",
|
|
2397
|
+
last: "Last"
|
|
2398
|
+
};
|
|
2399
|
+
const CONTROL_SLOT = {
|
|
2400
|
+
first: 0,
|
|
2401
|
+
prev: 1,
|
|
2402
|
+
indicator: 2,
|
|
2403
|
+
next: 3,
|
|
2404
|
+
last: 4
|
|
2405
|
+
};
|
|
2406
|
+
const MAX_BUTTONS_PER_ROW = 5;
|
|
2407
|
+
var Controls = class {
|
|
2408
|
+
cursor;
|
|
2409
|
+
view;
|
|
2410
|
+
constructor(cursor, view) {
|
|
2411
|
+
this.cursor = cursor;
|
|
2412
|
+
this.view = view;
|
|
2413
|
+
}
|
|
2414
|
+
button(key, cosmetics) {
|
|
2415
|
+
const { target, disabled } = this.navState(key);
|
|
2416
|
+
const defaultLabel = key === "indicator" ? this.indicatorText() : DEFAULT_LABEL[key];
|
|
2417
|
+
const label = cosmetics?.label ?? (key === "indicator" || !cosmetics?.emoji ? defaultLabel : void 0);
|
|
2418
|
+
const button = new discord_js.ButtonBuilder().setCustomId(this.cursor.encode({
|
|
2419
|
+
page: Math.min(Math.max(0, target), _seedcord_kit_internal.PAGE_MAX),
|
|
2420
|
+
slot: CONTROL_SLOT[key]
|
|
2421
|
+
})).setStyle(cosmetics?.style ?? discord_js.ButtonStyle.Secondary).setDisabled(disabled);
|
|
2422
|
+
if (label !== void 0) button.setLabel(label);
|
|
2423
|
+
if (cosmetics?.emoji) button.setEmoji(cosmetics.emoji);
|
|
2424
|
+
return button;
|
|
2425
|
+
}
|
|
2426
|
+
navState(key) {
|
|
2427
|
+
const { page, totalPages, hasPrev, hasNext } = this.view;
|
|
2428
|
+
const knownTotal = totalPages !== void 0;
|
|
2429
|
+
return {
|
|
2430
|
+
target: {
|
|
2431
|
+
first: 0,
|
|
2432
|
+
prev: page - 1,
|
|
2433
|
+
indicator: page,
|
|
2434
|
+
next: page + 1,
|
|
2435
|
+
last: knownTotal ? totalPages - 1 : page
|
|
2436
|
+
}[key],
|
|
2437
|
+
disabled: {
|
|
2438
|
+
first: !hasPrev,
|
|
2439
|
+
prev: !hasPrev,
|
|
2440
|
+
indicator: true,
|
|
2441
|
+
next: !hasNext || page + 1 > _seedcord_kit_internal.PAGE_MAX,
|
|
2442
|
+
last: !hasNext || !knownTotal || totalPages - 1 > _seedcord_kit_internal.PAGE_MAX
|
|
2443
|
+
}[key]
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
row(...keys) {
|
|
2447
|
+
if (keys.length < 1) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationEmptyControls);
|
|
2448
|
+
if (keys.length > MAX_BUTTONS_PER_ROW) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationTooManyControls, [keys.length]);
|
|
2449
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2450
|
+
for (const key of keys) {
|
|
2451
|
+
if (seen.has(key)) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.PaginationDuplicateControls, [key]);
|
|
2452
|
+
seen.add(key);
|
|
2453
|
+
}
|
|
2454
|
+
return new discord_js.ActionRowBuilder().addComponents(keys.map((key) => this.button(key)));
|
|
2455
|
+
}
|
|
2456
|
+
indicatorText() {
|
|
2457
|
+
const { page, totalPages } = this.view;
|
|
2458
|
+
return totalPages === void 0 ? `Page ${page + 1}` : `Page ${page + 1} of ${totalPages}`;
|
|
2459
|
+
}
|
|
2460
|
+
};
|
|
2461
|
+
|
|
2462
|
+
//#endregion
|
|
2463
|
+
//#region src/pagination/render.ts
|
|
2464
|
+
var PageContainer = class extends _seedcord_kit.BuilderComponent {
|
|
2465
|
+
constructor(view, renderItem, controlRow) {
|
|
2466
|
+
super("container");
|
|
2467
|
+
const base = view.page * view.perPage;
|
|
2468
|
+
let lines = [];
|
|
2469
|
+
const flush = () => {
|
|
2470
|
+
if (lines.length === 0) return;
|
|
2471
|
+
this.instance.addTextDisplayComponents(new discord_js.TextDisplayBuilder().setContent(lines.join("\n")));
|
|
2472
|
+
lines = [];
|
|
2473
|
+
};
|
|
2474
|
+
view.items.forEach((item, offset) => {
|
|
2475
|
+
const rendered = renderItem(item, base + offset);
|
|
2476
|
+
if (typeof rendered === "string") {
|
|
2477
|
+
lines.push(rendered);
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
flush();
|
|
2481
|
+
this.instance.addSectionComponents(rendered.component);
|
|
2482
|
+
});
|
|
2483
|
+
flush();
|
|
2484
|
+
this.instance.addActionRowComponents(controlRow);
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
function toReplyResponse(renderable) {
|
|
2488
|
+
if (typeof renderable === "string") return { components: [new discord_js.TextDisplayBuilder().setContent(renderable)] };
|
|
2489
|
+
if (Array.isArray(renderable)) return { components: renderable };
|
|
2490
|
+
return renderable;
|
|
2491
|
+
}
|
|
2492
|
+
const ARRAY_KEYS = [
|
|
2493
|
+
"first",
|
|
2494
|
+
"prev",
|
|
2495
|
+
"indicator",
|
|
2496
|
+
"next",
|
|
2497
|
+
"last"
|
|
2498
|
+
];
|
|
2499
|
+
const CURSOR_KEYS = [
|
|
2500
|
+
"prev",
|
|
2501
|
+
"indicator",
|
|
2502
|
+
"next"
|
|
2503
|
+
];
|
|
2504
|
+
/**
|
|
2505
|
+
* Build a page's V2 reply. A `render` override builds the whole tree, otherwise the default container lists
|
|
2506
|
+
* the items and appends the controls (all five for a known total, prev/indicator/next for a cursor source).
|
|
2507
|
+
*/
|
|
2508
|
+
function renderPage(view, cursor, config) {
|
|
2509
|
+
const controls = new Controls(cursor, view);
|
|
2510
|
+
if (config.render) return toReplyResponse(config.render(view, controls));
|
|
2511
|
+
return { components: [new PageContainer(view, config.renderItem ?? ((item) => String(item)), controls.row(...view.totalPages === void 0 ? CURSOR_KEYS : ARRAY_KEYS)).component] };
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
//#endregion
|
|
2515
|
+
//#region src/pagination/Paginator.ts
|
|
2516
|
+
function contextOf(interaction, core) {
|
|
2517
|
+
return {
|
|
2518
|
+
interaction,
|
|
2519
|
+
user: interaction.user,
|
|
2520
|
+
guild: interaction.guild,
|
|
2521
|
+
...core && { core }
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
/**
|
|
2525
|
+
* A restart-proof paginator. Each nav button's customId encodes its full target page, so clicks are
|
|
2526
|
+
* idempotent and survive a restart. A persistent `@ButtonRoute` on `Handler` dispatches the clicks.
|
|
2527
|
+
*
|
|
2528
|
+
* @typeParam Item - The item type, inferred from the source.
|
|
2529
|
+
* @typeParam Prefix - The route prefix, inferred from `config.prefix`.
|
|
2530
|
+
*
|
|
2531
|
+
* @example
|
|
2532
|
+
* ```ts
|
|
2533
|
+
* export const Bans = new Paginator({
|
|
2534
|
+
* prefix: 'bans',
|
|
2535
|
+
* source: new ArraySource((ctx) => ctx.guild.bans.fetch().then((b) => [...b.values()]), { perPage: 10 }),
|
|
2536
|
+
* renderItem: (ban) => ban.user.tag
|
|
2537
|
+
* });
|
|
2538
|
+
*
|
|
2539
|
+
* \@ButtonRoute(Bans.cursor)
|
|
2540
|
+
* export class BansNav extends Bans.Handler {}
|
|
2541
|
+
* ```
|
|
2542
|
+
*/
|
|
2543
|
+
var Paginator = class {
|
|
2544
|
+
/** The page cursor, pass it to your `@ButtonRoute`. */
|
|
2545
|
+
cursor;
|
|
2546
|
+
/** The nav handler base. Extend it with an empty body and decorate it, `@ButtonRoute(p.cursor)`. */
|
|
2547
|
+
Handler;
|
|
2548
|
+
config;
|
|
2549
|
+
constructor(config) {
|
|
2550
|
+
this.cursor = (0, _seedcord_kit_internal.pageCursor)(config.prefix);
|
|
2551
|
+
this.config = config;
|
|
2552
|
+
const loadPage = (ctx, n) => this.page(ctx, n);
|
|
2553
|
+
this.Handler = class Nav extends ButtonHandler {
|
|
2554
|
+
async execute() {
|
|
2555
|
+
await this.event.deferUpdate();
|
|
2556
|
+
const response = await loadPage(contextOf(this.event, this.core), this.params.page);
|
|
2557
|
+
await new ReplySender(this.event).edit(this.event.message, response);
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2561
|
+
/** Render page 0 and send it, picking reply or followUp from the interaction's state. */
|
|
2562
|
+
async start(interaction, core) {
|
|
2563
|
+
const response = await this.page(contextOf(interaction, core), 0);
|
|
2564
|
+
return new ReplySender(interaction).send(response, this.config.ephemeral ?? false);
|
|
2565
|
+
}
|
|
2566
|
+
/** Render a page as a {@link ReplyResponse}. To post it elsewhere, add `flags: MessageFlags.IsComponentsV2`. */
|
|
2567
|
+
async page(ctx, n) {
|
|
2568
|
+
return renderPage(await this.config.source.page(ctx, n), this.cursor, this.config);
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
|
|
2572
|
+
//#endregion
|
|
2573
|
+
//#region src/pagination/sources.ts
|
|
2574
|
+
const DEFAULT_PER_PAGE = 10;
|
|
2575
|
+
/**
|
|
2576
|
+
* A source for a bounded list you can load whole. It loads the full list on every click, then slices the
|
|
2577
|
+
* page via {@link paginate}, so the real total is known (a real last button, "Page X of Y"). Cache inside
|
|
2578
|
+
* your loader if the load is expensive.
|
|
2579
|
+
*
|
|
2580
|
+
* @typeParam Item - The item type, inferred from the loader's return.
|
|
2581
|
+
*/
|
|
2582
|
+
var ArraySource = class {
|
|
2583
|
+
load;
|
|
2584
|
+
perPage;
|
|
2585
|
+
constructor(load, opts) {
|
|
2586
|
+
this.load = load;
|
|
2587
|
+
this.perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
|
|
2588
|
+
if (!Number.isInteger(this.perPage) || this.perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [this.perPage]);
|
|
2589
|
+
}
|
|
2590
|
+
async page(ctx, n) {
|
|
2591
|
+
return (0, _seedcord_kit.paginate)(await this.load(ctx), n, this.perPage);
|
|
2592
|
+
}
|
|
2593
|
+
};
|
|
2594
|
+
/**
|
|
2595
|
+
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
2596
|
+
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
2597
|
+
* source has no cheap total, so `totalPages` is undefined, the last button is omitted, and the indicator
|
|
2598
|
+
* reads "Page X".
|
|
2599
|
+
*
|
|
2600
|
+
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
2601
|
+
*/
|
|
2602
|
+
var CursorSource = class {
|
|
2603
|
+
fetch;
|
|
2604
|
+
perPage;
|
|
2605
|
+
constructor(fetch, opts) {
|
|
2606
|
+
this.fetch = fetch;
|
|
2607
|
+
this.perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
|
|
2608
|
+
if (!Number.isInteger(this.perPage) || this.perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [this.perPage]);
|
|
2609
|
+
}
|
|
2610
|
+
async page(ctx, n) {
|
|
2611
|
+
const page = Math.max(0, Math.trunc(n));
|
|
2612
|
+
const { items, hasNext } = await this.fetch(ctx, page, this.perPage);
|
|
2613
|
+
return {
|
|
2614
|
+
items: [...items],
|
|
2615
|
+
page,
|
|
2616
|
+
perPage: this.perPage,
|
|
2617
|
+
hasPrev: page > 0,
|
|
2618
|
+
hasNext
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2398
2623
|
//#endregion
|
|
2399
2624
|
//#region src/subscribers/Subscriber.ts
|
|
2400
2625
|
/**
|
|
@@ -4125,9 +4350,10 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
4125
4350
|
//#endregion
|
|
4126
4351
|
//#region src/index.ts
|
|
4127
4352
|
/** Package version */
|
|
4128
|
-
const version = "0.
|
|
4353
|
+
const version = "0.15.0-next.0";
|
|
4129
4354
|
|
|
4130
4355
|
//#endregion
|
|
4356
|
+
exports.ArraySource = ArraySource;
|
|
4131
4357
|
exports.AutocompleteHandler = AutocompleteHandler;
|
|
4132
4358
|
exports.AutocompleteRoute = AutocompleteRoute;
|
|
4133
4359
|
exports.ButtonHandler = ButtonHandler;
|
|
@@ -4136,6 +4362,7 @@ exports.CommandMentions = CommandMentions;
|
|
|
4136
4362
|
exports.ContextMenuHandler = ContextMenuHandler;
|
|
4137
4363
|
exports.ContextMenuRoute = ContextMenuRoute;
|
|
4138
4364
|
exports.Cooldown = Cooldown;
|
|
4365
|
+
exports.CursorSource = CursorSource;
|
|
4139
4366
|
exports.DmOnly = DmOnly;
|
|
4140
4367
|
exports.Emojis = Emojis;
|
|
4141
4368
|
exports.EventHandler = EventHandler;
|
|
@@ -4152,6 +4379,7 @@ exports.ModalHandler = ModalHandler;
|
|
|
4152
4379
|
exports.ModalRoute = ModalRoute;
|
|
4153
4380
|
exports.Nsfw = Nsfw;
|
|
4154
4381
|
exports.OwnerOnly = OwnerOnly;
|
|
4382
|
+
exports.Paginator = Paginator;
|
|
4155
4383
|
exports.Pluggable = Pluggable;
|
|
4156
4384
|
exports.Plugin = Plugin;
|
|
4157
4385
|
exports.RegisterCommand = RegisterCommand;
|
|
@@ -4161,7 +4389,7 @@ exports.RequireBotPermissions = RequireBotPermissions;
|
|
|
4161
4389
|
exports.RequirePermissions = RequirePermissions;
|
|
4162
4390
|
exports.RequireRole = RequireRole;
|
|
4163
4391
|
exports.Seedcord = Seedcord;
|
|
4164
|
-
exports.
|
|
4392
|
+
exports.SelectMenuHandler = SelectMenuHandler;
|
|
4165
4393
|
exports.SelectMenuKind = SelectMenuKind;
|
|
4166
4394
|
exports.SelectMenuRoute = SelectMenuRoute;
|
|
4167
4395
|
exports.SlashHandler = SlashHandler;
|