spearkit 0.3.0 → 0.4.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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as discord_js from 'discord.js';
2
- import { APIEmbedField, APIEmbedFooter, APIEmbedAuthor, EmbedBuilder, Client, Channel, Guild, GuildMember, MessageManager, Message, Role, User, APIInteractionGuildMember, Awaitable, PermissionResolvable, RepliableInteraction, InteractionReplyOptions, InteractionResponse, APIApplicationCommandChannelOption, CommandInteractionOption, Attachment, ApplicationCommandOptionType, LocalizationMap, ChatInputCommandInteraction, APIApplicationCommandBasicOption, AutocompleteInteraction, JSONEncodable, APIModalInteractionResponseCallbackData, ModalComponentData, ModalBuilder, RESTPostAPIChatInputApplicationCommandsJSONBody, RESTPostAPIApplicationCommandsJSONBody, REST, RESTPutAPIApplicationCommandsResult, RESTPutAPIApplicationGuildCommandsResult, ClientEvents, ButtonInteraction, ChannelSelectMenuInteraction, StringSelectMenuInteraction, UserSelectMenuInteraction, RoleSelectMenuInteraction, MentionableSelectMenuInteraction, ModalSubmitInteraction, Interaction, MessagePayload, MessageReplyOptions, MessageCreateOptions, GatewayIntentBits, ClientOptions, RESTPostAPIContextMenuApplicationCommandsJSONBody, UserContextMenuCommandInteraction, MessageContextMenuCommandInteraction, BaseMessageOptions, InteractionUpdateOptions, ButtonBuilder, ButtonStyle, ComponentEmojiResolvable, ChannelSelectMenuBuilder, MentionableSelectMenuBuilder, TextInputStyle, RoleSelectMenuBuilder, StringSelectMenuBuilder, SelectMenuComponentOptionData, UserSelectMenuBuilder, ChannelType, MessageActionRowComponentBuilder, ActionRowBuilder } from 'discord.js';
2
+ import { APIEmbedField, APIEmbedFooter, APIEmbedAuthor, EmbedBuilder, Client, Channel, Guild, GuildMember, MessageManager, Message, Role, User, APIInteractionGuildMember, Awaitable, PermissionResolvable, GuildBasedChannel, PermissionsString, RESTJSONErrorCodes, DiscordAPIError, HTTPError, MessageComponentInteraction, ComponentType, ModalSubmitInteraction, JSONEncodable, APIModalInteractionResponseCallbackData, ModalComponentData, ModalBuilder, TextBasedChannel, PartialGroupDMChannel, ChatInputCommandInteraction, UserContextMenuCommandInteraction, MessageContextMenuCommandInteraction, RepliableInteraction, InteractionReplyOptions, InteractionResponse, PermissionsBitField, APIApplicationCommandChannelOption, CommandInteractionOption, Attachment, ApplicationCommandOptionType, LocalizationMap, APIApplicationCommandBasicOption, AutocompleteInteraction, RESTPostAPIChatInputApplicationCommandsJSONBody, RESTPostAPIApplicationCommandsJSONBody, REST, RESTPutAPIApplicationCommandsResult, RESTPutAPIApplicationGuildCommandsResult, ClientEvents, ButtonInteraction, ChannelSelectMenuInteraction, StringSelectMenuInteraction, UserSelectMenuInteraction, RoleSelectMenuInteraction, MentionableSelectMenuInteraction, Interaction, MessagePayload, MessageReplyOptions, MessageCreateOptions, GatewayIntentBits, ClientOptions, RESTPostAPIContextMenuApplicationCommandsJSONBody, BaseMessageOptions, InteractionUpdateOptions, ButtonBuilder, ButtonStyle, ComponentEmojiResolvable, ChannelSelectMenuBuilder, MentionableSelectMenuBuilder, TextInputStyle, RoleSelectMenuBuilder, StringSelectMenuBuilder, SelectMenuComponentOptionData, UserSelectMenuBuilder, ChannelType, MessageActionRowComponentBuilder, ActionRowBuilder } from 'discord.js';
3
3
  export * from 'discord.js';
4
4
 
5
5
  /**
@@ -255,6 +255,34 @@ declare function parseDuration(input: string): number | null;
255
255
  declare function discordTimestamp(date: Date | number, style?: DiscordTimestampStyle): string;
256
256
  /** Short-hand for the relative Discord timestamp (`R` style). */
257
257
  declare function relativeTimestamp(date: Date | number): string;
258
+ /** The hard cap Discord enforces on a single message's `content`. */
259
+ declare const MESSAGE_CHARACTER_LIMIT = 2000;
260
+ /**
261
+ * Truncate `text` to at most `max` characters, appending `suffix` (default `…`)
262
+ * when it had to cut. The result — suffix included — never exceeds `max`.
263
+ *
264
+ * @example
265
+ * ```ts
266
+ * truncate("a very long reason", 10); // → "a very lo…"
267
+ * ```
268
+ */
269
+ declare function truncate(text: string, max: number, suffix?: string): string;
270
+ /** Options for {@link chunkMessage}. */
271
+ interface ChunkOptions {
272
+ /** Maximum characters per chunk. Default {@link MESSAGE_CHARACTER_LIMIT} (2000). */
273
+ max?: number;
274
+ }
275
+ /**
276
+ * Split `text` into chunks that each fit within Discord's per-message limit,
277
+ * breaking on line boundaries (and word boundaries for over-long lines) so you
278
+ * never silently lose the tail of a long reply.
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * for (const part of chunkMessage(hugeLog)) await ctx.followUp(part);
283
+ * ```
284
+ */
285
+ declare function chunkMessage(text: string, options?: ChunkOptions): string[];
258
286
 
259
287
  /**
260
288
  * Pluggable cache abstraction with TTL, counters and rate-limit primitives.
@@ -348,6 +376,96 @@ declare function lookup<K extends string, V>(table: Readonly<Record<K, V>>, reso
348
376
  /** Build a non-throwing lookup that returns `undefined` for missing keys. */
349
377
  declare function lookupOptional<K extends string, V>(table: Readonly<Record<K, V>>): (key: K) => V | undefined;
350
378
 
379
+ /**
380
+ * A minimal async key-value store. Values must be JSON-serialisable. All
381
+ * backends share these semantics so you can develop against {@link MemoryStore}
382
+ * and ship with {@link JsonStore} (or your own) without code changes.
383
+ */
384
+ interface KeyValueStore {
385
+ /** Resolve the value for `key`, or `undefined` if absent. */
386
+ get<T>(key: string): Promise<T | undefined>;
387
+ /** Store `value` under `key`, overwriting any previous value. */
388
+ set<T>(key: string, value: T): Promise<void>;
389
+ /** Whether `key` currently has a value. */
390
+ has(key: string): Promise<boolean>;
391
+ /** Remove `key`. Resolves `true` if it existed. */
392
+ delete(key: string): Promise<boolean>;
393
+ /** Every key currently stored. */
394
+ keys(): Promise<string[]>;
395
+ /** Remove every key. */
396
+ clear(): Promise<void>;
397
+ }
398
+ /**
399
+ * In-memory {@link KeyValueStore}. Values are deep-cloned on read and write so
400
+ * callers can't accidentally mutate stored state — matching what a persistent
401
+ * backend would do. Ideal for tests and ephemeral data.
402
+ */
403
+ declare class MemoryStore implements KeyValueStore {
404
+ private readonly map;
405
+ get<T>(key: string): Promise<T | undefined>;
406
+ set<T>(key: string, value: T): Promise<void>;
407
+ has(key: string): Promise<boolean>;
408
+ delete(key: string): Promise<boolean>;
409
+ keys(): Promise<string[]>;
410
+ clear(): Promise<void>;
411
+ }
412
+ /**
413
+ * File-backed {@link KeyValueStore} persisting the whole map as one JSON object.
414
+ * Reads are served from an in-memory cache (loaded once, lazily); writes are
415
+ * serialised through a queue and committed atomically (temp file + rename) so a
416
+ * crash mid-write can never corrupt the file.
417
+ */
418
+ declare class JsonStore implements KeyValueStore {
419
+ private readonly path;
420
+ private readonly cache;
421
+ private loading?;
422
+ private writeChain;
423
+ constructor(path: string);
424
+ private ensureLoaded;
425
+ private load;
426
+ /** Queue an atomic write of the current cache; serialised against prior writes. */
427
+ private persist;
428
+ get<T>(key: string): Promise<T | undefined>;
429
+ set<T>(key: string, value: T): Promise<void>;
430
+ has(key: string): Promise<boolean>;
431
+ delete(key: string): Promise<boolean>;
432
+ keys(): Promise<string[]>;
433
+ clear(): Promise<void>;
434
+ }
435
+ /**
436
+ * Wrap a store so every key is transparently prefixed with `${prefix}:`. Lets
437
+ * several features share one backing file without key collisions.
438
+ */
439
+ declare function namespaced(store: KeyValueStore, prefix: string): KeyValueStore;
440
+ /** A typed settings accessor returned by {@link createSettings}. */
441
+ interface SettingsManager<T extends Record<string, unknown>> {
442
+ /** The defaults merged into every {@link get}. */
443
+ readonly defaults: T;
444
+ /** The underlying store. */
445
+ readonly store: KeyValueStore;
446
+ /** Read `id`'s settings, always fully populated from {@link defaults}. */
447
+ get(id: string): Promise<T>;
448
+ /** Shallow-merge `patch` into `id`'s stored settings and persist; returns the merged result. */
449
+ set(id: string, patch: Partial<T>): Promise<T>;
450
+ /** Restore `id` to defaults by removing its stored overrides. */
451
+ reset(id: string): Promise<void>;
452
+ }
453
+ /** Options for {@link createSettings}. */
454
+ interface CreateSettingsOptions<T extends Record<string, unknown>> {
455
+ /** Backing store (e.g. `new JsonStore(path)`). */
456
+ store: KeyValueStore;
457
+ /** Default values applied to ids with no (or partial) stored settings. */
458
+ defaults: T;
459
+ /** Key prefix; lets one store hold several settings groups. Default `"settings"`. */
460
+ namespace?: string;
461
+ }
462
+ /**
463
+ * Build a typed, defaults-merged settings accessor over a {@link KeyValueStore}.
464
+ * `get` always returns a complete object (stored overrides on top of defaults),
465
+ * and `set` only persists the overrides — so widening `defaults` later is safe.
466
+ */
467
+ declare function createSettings<T extends Record<string, unknown>>(options: CreateSettingsOptions<T>): SettingsManager<T>;
468
+
351
469
  /**
352
470
  * Declarative preconditions ("guards") that run before a command, component
353
471
  * or prefix-command handler. Replaces the role/permission/guild-only/owner
@@ -408,6 +526,345 @@ declare function requireBotPermissions(permission: PermissionResolvable, reason?
408
526
  /** Inline custom predicate; sugar so a one-off check still types as a Guard. */
409
527
  declare function guard<TCtx extends GuardContext = GuardContext>(predicate: Guard<TCtx>): Guard<TCtx>;
410
528
 
529
+ /**
530
+ * Permission and role-hierarchy preflight helpers for moderation-style actions.
531
+ *
532
+ * Two failures dominate moderation bots: doing work and *then* hitting a
533
+ * `Missing Permissions` (50013) error, and trying to ban/kick/timeout a member
534
+ * who is above the bot (or above the moderator) in the role list. Both are
535
+ * checkable up front. These helpers compute exactly what's missing so you can
536
+ * bail out with a clear message before touching the API.
537
+ *
538
+ * @example
539
+ * ```ts
540
+ * const missing = botMissingPermissions(ctx.channel, [PermissionFlagsBits.ManageMessages]);
541
+ * if (missing.length) return ctx.error(`I need: ${formatPermissions(missing)}`);
542
+ *
543
+ * const check = moderationCheck({ moderator: ctx.member, target, action: "ban" });
544
+ * if (!check.ok) return ctx.error(check.reason);
545
+ * await target.ban();
546
+ * ```
547
+ */
548
+
549
+ /** A member or role whose permissions are being resolved in a channel. */
550
+ type PermissionHolder = GuildMember | Role;
551
+ /**
552
+ * Return the names of the `required` permissions that `who` does NOT have in
553
+ * `channel` (taking channel overwrites and Administrator into account). An empty
554
+ * array means every required permission is granted. When permissions can't be
555
+ * resolved (e.g. the member isn't cached) every required permission is reported
556
+ * missing.
557
+ */
558
+ declare function missingPermissions(channel: GuildBasedChannel, who: PermissionHolder, required: PermissionResolvable): PermissionsString[];
559
+ /**
560
+ * Like {@link missingPermissions} but for the bot's own member in `channel`.
561
+ * Resolves `channel.guild.members.me`; if that isn't available, every required
562
+ * permission is reported missing.
563
+ */
564
+ declare function botMissingPermissions(channel: GuildBasedChannel, required: PermissionResolvable): PermissionsString[];
565
+ /** Whether `who` has all of `required` in `channel`. */
566
+ declare function hasPermissions(channel: GuildBasedChannel, who: PermissionHolder, required: PermissionResolvable): boolean;
567
+ /**
568
+ * Compare two members by their highest role position. Returns a positive number
569
+ * when `a` is above `b`, negative when below, `0` when equal. This is the raw
570
+ * comparison Discord enforces for moderation actions.
571
+ */
572
+ declare function compareRoles(a: GuildMember, b: GuildMember): number;
573
+ /**
574
+ * Whether `actor` outranks `target` enough to act on them: not the same member,
575
+ * `target` isn't the guild owner, and `actor` is either the owner or holds a
576
+ * higher top role.
577
+ */
578
+ declare function canActOn(actor: GuildMember, target: GuildMember): boolean;
579
+ /** The result of a {@link moderationCheck}: pass, or fail with a reason. */
580
+ type ModerationCheckResult = {
581
+ ok: true;
582
+ } | {
583
+ ok: false;
584
+ reason: string;
585
+ };
586
+ /** Options for {@link moderationCheck}. */
587
+ interface ModerationCheckOptions {
588
+ /** The member attempting the action. */
589
+ moderator: GuildMember;
590
+ /** The member the action targets. */
591
+ target: GuildMember;
592
+ /**
593
+ * The bot's own member. Defaults to `target.guild.members.me`. Pass `null` to
594
+ * skip the bot-hierarchy check (e.g. when the action doesn't need it).
595
+ */
596
+ me?: GuildMember | null;
597
+ /** Verb used in the failure messages, e.g. `"ban"`. Default `"moderate"`. */
598
+ action?: string;
599
+ }
600
+ /**
601
+ * Validate that both the moderator and the bot may act on `target`, returning a
602
+ * ready-to-show reason on the first failing rule. Checks, in order: acting on
603
+ * self, acting on the server owner, moderator role hierarchy, and bot role
604
+ * hierarchy.
605
+ */
606
+ declare function moderationCheck(options: ModerationCheckOptions): ModerationCheckResult;
607
+ /**
608
+ * Render permission flag names into a human, comma-separated string. Accepts a
609
+ * {@link PermissionsString} array (the output of {@link missingPermissions}) or
610
+ * anything {@link PermissionResolvable}.
611
+ *
612
+ * @example
613
+ * ```ts
614
+ * formatPermissions(botMissingPermissions(ctx.channel, [PermissionFlagsBits.BanMembers]));
615
+ * // → "Ban Members"
616
+ * ```
617
+ */
618
+ declare function formatPermissions(permissions: PermissionResolvable): string;
619
+
620
+ /**
621
+ * Discord API error helpers — turn opaque `DiscordAPIError` throws into
622
+ * something you can branch on and show to users.
623
+ *
624
+ * discord.js surfaces REST failures as {@link DiscordAPIError} with a numeric
625
+ * `code` (e.g. `10062` "Unknown interaction", `50013` "Missing permissions").
626
+ * Memorising those numbers is a chore, and `try/catch` blocks that re-throw
627
+ * everything turn small, recoverable failures (a deleted message, a closed DM)
628
+ * into crashes or scary stack traces. This module gives you named codes, a
629
+ * type-narrowing predicate, and a friendly, end-user-appropriate explanation.
630
+ *
631
+ * @example
632
+ * ```ts
633
+ * try {
634
+ * await message.delete();
635
+ * } catch (err) {
636
+ * if (isDiscordError(err, DiscordErrorCode.UnknownMessage)) return; // already gone
637
+ * throw err;
638
+ * }
639
+ * ```
640
+ */
641
+
642
+ /**
643
+ * The Discord JSON error codes spearkit cares about most, by readable name.
644
+ * This is a curated subset of discord.js' {@link RESTJSONErrorCodes} covering
645
+ * the failures bots actually hit and can recover from. Values are the numeric
646
+ * codes Discord returns on `DiscordAPIError.code`.
647
+ */
648
+ declare const DiscordErrorCode: {
649
+ /** A referenced channel no longer exists or is invisible to the bot. */
650
+ readonly UnknownChannel: RESTJSONErrorCodes.UnknownChannel;
651
+ /** The targeted guild is gone or the bot was removed from it. */
652
+ readonly UnknownGuild: RESTJSONErrorCodes.UnknownGuild;
653
+ /** The referenced member is not in the guild. */
654
+ readonly UnknownMember: RESTJSONErrorCodes.UnknownMember;
655
+ /** The message was deleted (or never existed) before the action ran. */
656
+ readonly UnknownMessage: RESTJSONErrorCodes.UnknownMessage;
657
+ /** The user could not be resolved. */
658
+ readonly UnknownUser: RESTJSONErrorCodes.UnknownUser;
659
+ /** The interaction token expired (the classic 3-second-window failure). */
660
+ readonly UnknownInteraction: RESTJSONErrorCodes.UnknownInteraction;
661
+ /** The bot lacks access to the resource entirely (not just one permission). */
662
+ readonly MissingAccess: RESTJSONErrorCodes.MissingAccess;
663
+ /** Action attempted on a DM channel that does not support it. */
664
+ readonly CannotExecuteActionOnDMChannel: RESTJSONErrorCodes.CannotExecuteActionOnDMChannel;
665
+ /** The target user has DMs closed or blocked the bot. */
666
+ readonly CannotSendMessagesToThisUser: RESTJSONErrorCodes.CannotSendMessagesToThisUser;
667
+ /** The bot is missing one or more permissions required for the action. */
668
+ readonly MissingPermissions: RESTJSONErrorCodes.MissingPermissions;
669
+ /** The request body failed Discord's validation. */
670
+ readonly InvalidFormBodyOrContentType: RESTJSONErrorCodes.InvalidFormBodyOrContentType;
671
+ /** The interaction was already acknowledged elsewhere. */
672
+ readonly InteractionHasAlreadyBeenAcknowledged: RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged;
673
+ /** The bot reached the maximum number of guilds it may join. */
674
+ readonly MaximumNumberOfGuildsReached: RESTJSONErrorCodes.MaximumNumberOfGuildsReached;
675
+ /** Too many active reactions / pins / etc. of this kind. */
676
+ readonly MaximumNumberOfReactionsReached: RESTJSONErrorCodes.MaximumNumberOfReactionsReached;
677
+ };
678
+ /** A numeric Discord JSON error code value. */
679
+ type DiscordErrorCodeValue = (typeof DiscordErrorCode)[keyof typeof DiscordErrorCode];
680
+ /**
681
+ * Narrow an unknown thrown value to a {@link DiscordAPIError}. Pass a `code`
682
+ * (or several) to also assert the specific failure — ideal for "ignore this
683
+ * one error, re-throw the rest" recovery.
684
+ *
685
+ * @example
686
+ * ```ts
687
+ * if (isDiscordError(err, [DiscordErrorCode.UnknownMessage, DiscordErrorCode.UnknownChannel])) return;
688
+ * ```
689
+ */
690
+ declare function isDiscordError(error: unknown, code?: number | string | readonly (number | string)[]): error is DiscordAPIError;
691
+ /**
692
+ * Narrow to a transport-level {@link HTTPError} (timeouts, 5xx, aborted
693
+ * requests) — failures with an HTTP status but no Discord JSON code.
694
+ */
695
+ declare function isHTTPError(error: unknown): error is HTTPError;
696
+ /** Whether the thrown value is a Discord rate-limit (HTTP 429) response. */
697
+ declare function isRateLimitError(error: unknown): boolean;
698
+ /**
699
+ * Render an end-user-appropriate sentence for a Discord error, or `null` if the
700
+ * error isn't a recognised, explainable Discord failure (in which case you
701
+ * should fall back to a generic "something went wrong" message and log it).
702
+ *
703
+ * @example
704
+ * ```ts
705
+ * catch (err) { await ctx.error(explainDiscordError(err) ?? "Something went wrong."); }
706
+ * ```
707
+ */
708
+ declare function explainDiscordError(error: unknown): string | null;
709
+
710
+ /**
711
+ * Graceful shutdown — close the bot cleanly on `SIGINT`/`SIGTERM`.
712
+ *
713
+ * A `Ctrl-C` or a container stop sends a signal; if you don't handle it the
714
+ * process dies mid-flight, leaving the gateway connection, scheduler timers and
715
+ * any open handles to be reaped abruptly. {@link gracefulShutdown} runs an
716
+ * optional hook, calls `client.destroy()` (which also stops spearkit's
717
+ * scheduler), and exits — with a hard timeout so a wedged shutdown can't hang
718
+ * forever.
719
+ *
720
+ * @example
721
+ * ```ts
722
+ * gracefulShutdown(client, { onShutdown: () => db.close() });
723
+ * // or, on a SpearClient:
724
+ * client.enableGracefulShutdown({ onShutdown: () => db.close() });
725
+ * ```
726
+ */
727
+
728
+ /** Anything with an async-or-sync `destroy()` — a discord.js `Client` qualifies. */
729
+ interface Destroyable {
730
+ destroy(): Awaitable<void>;
731
+ }
732
+ /** Minimal logger shape used for shutdown progress (your `client.logger` fits). */
733
+ interface ShutdownLogger {
734
+ info?(message: string): void;
735
+ error?(message: string, meta?: unknown): void;
736
+ }
737
+ /** Options for {@link gracefulShutdown}. */
738
+ interface GracefulShutdownOptions {
739
+ /** Signals to listen for. Default `["SIGINT", "SIGTERM"]`. */
740
+ signals?: readonly NodeJS.Signals[];
741
+ /** Force-exit if shutdown takes longer than this many ms. Default `10000`. */
742
+ timeoutMs?: number;
743
+ /** Call `process.exit()` when done. Default `true`. Set `false` in tests. */
744
+ exit?: boolean;
745
+ /** Runs before `client.destroy()` — flush databases, close connections, etc. */
746
+ onShutdown?: (signal: NodeJS.Signals) => Awaitable<void>;
747
+ /** Optional progress logger. */
748
+ logger?: ShutdownLogger;
749
+ }
750
+ /**
751
+ * Wire signal handlers that gracefully tear `client` down once, then exit.
752
+ * Returns a disposer that removes the handlers (handy for tests/hot-reload).
753
+ */
754
+ declare function gracefulShutdown(client: Destroyable, options?: GracefulShutdownOptions): () => void;
755
+
756
+ /**
757
+ * Collector ergonomics — wait for a reply, a click, or a modal without the
758
+ * boilerplate (and footguns) of raw discord.js collectors.
759
+ *
760
+ * discord.js collectors are powerful but fiddly: you wire an event emitter, set
761
+ * a `time`, write a `filter`, remember that modal submissions can be dismissed
762
+ * (so they need their own timeout), and translate the "timed out" rejection into
763
+ * something your code can branch on. These helpers collapse the common cases to
764
+ * a single `await` that resolves to the result — or `null` on timeout.
765
+ *
766
+ * @example
767
+ * ```ts
768
+ * await ctx.reply("What's your favourite colour?");
769
+ * const reply = await ctx.awaitMessageFrom(); // same user, same channel
770
+ * if (reply === null) return ctx.followUp("Timed out.");
771
+ * await ctx.followUp(`Nice — ${reply.content}!`);
772
+ * ```
773
+ */
774
+
775
+ /** Options for {@link awaitMessage}. */
776
+ interface AwaitMessageOptions {
777
+ /** Only accept messages passing this predicate. */
778
+ filter?: (message: Message) => boolean;
779
+ /** How long to wait, in ms. Default `60000`. */
780
+ time?: number;
781
+ }
782
+ /**
783
+ * A text-based channel that can collect messages — every {@link TextBasedChannel}
784
+ * except `PartialGroupDMChannel` (which has no message manager).
785
+ */
786
+ type CollectableChannel = Exclude<TextBasedChannel, PartialGroupDMChannel>;
787
+ /**
788
+ * Wait for the next message in `channel` that matches `filter`, resolving to the
789
+ * `Message` or `null` if none arrives before `time` elapses.
790
+ */
791
+ declare function awaitMessage(channel: CollectableChannel, options?: AwaitMessageOptions): Promise<Message | null>;
792
+ /** Options for {@link awaitComponent}. */
793
+ interface AwaitComponentOptions {
794
+ /** Only accept interactions passing this predicate. */
795
+ filter?: (interaction: MessageComponentInteraction) => boolean;
796
+ /** How long to wait, in ms. Default `60000`. */
797
+ time?: number;
798
+ /** Restrict to one component kind (e.g. `ComponentType.Button`). */
799
+ componentType?: ComponentType;
800
+ }
801
+ /**
802
+ * Wait for the next component interaction (button/select click) on `message`,
803
+ * resolving to it or `null` on timeout. Note: you must still acknowledge the
804
+ * returned interaction (`update`/`deferUpdate`/`reply`).
805
+ */
806
+ declare function awaitComponent(message: Message, options?: AwaitComponentOptions): Promise<MessageComponentInteraction | null>;
807
+ /** A modal in any of the forms discord.js' `showModal` accepts. */
808
+ type ModalLike = JSONEncodable<APIModalInteractionResponseCallbackData> | ModalComponentData | ModalBuilder;
809
+ /** Interactions that can open a modal and await its submission. */
810
+ type ModalShowingInteraction = ChatInputCommandInteraction | MessageComponentInteraction | UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction;
811
+ /** Options for {@link showAndAwaitModal}. */
812
+ interface AwaitModalOptions {
813
+ /** How long to wait for submission, in ms. Default `120000`. */
814
+ time?: number;
815
+ /** Extra predicate on the submitted modal (already scoped to this user + modal). */
816
+ filter?: (interaction: ModalSubmitInteraction) => boolean;
817
+ }
818
+ /**
819
+ * Show `modal` on `interaction`, then wait for its submission — scoped to the
820
+ * same user and the modal's own custom-id — resolving to the
821
+ * {@link ModalSubmitInteraction} or `null` if the user dismisses it / it times
822
+ * out. Sidesteps the "Unknown interaction after cancelling a modal" trap by
823
+ * always bounding the wait.
824
+ */
825
+ declare function showAndAwaitModal(interaction: ModalShowingInteraction, modal: ModalLike, options?: AwaitModalOptions): Promise<ModalSubmitInteraction | null>;
826
+
827
+ /**
828
+ * Auto-defer — the antidote to `DiscordAPIError[10062]: Unknown interaction`.
829
+ *
830
+ * An interaction token is only valid for 3 seconds before the *first* response.
831
+ * Any handler that awaits a database query, an HTTP call, or anything else slow
832
+ * blows past that window and the interaction dies. The fix is always the same:
833
+ * `deferReply()` early. Auto-defer does it for you — it arms a timer when the
834
+ * handler starts and, if the handler hasn't responded in time, defers
835
+ * automatically. Cancelled the instant your handler replies/defers itself.
836
+ *
837
+ * Enable per command (`command({ autoDefer: true })`) or globally
838
+ * (`new SpearClient({ autoDefer: true })`). With it on, respond via
839
+ * `ctx.send(...)` / `ctx.editReply(...)` rather than `ctx.reply(...)`, since the
840
+ * reply slot may already be taken by the auto-defer.
841
+ */
842
+
843
+ /** How a handler opts into auto-defer: `true` for defaults, or fine-tuning. */
844
+ type AutoDeferInput = boolean | {
845
+ ephemeral?: boolean;
846
+ delayMs?: number;
847
+ };
848
+ /** Resolved auto-defer settings. */
849
+ interface AutoDeferConfig {
850
+ /** Defer as an ephemeral ("thinking…" hidden) response. */
851
+ ephemeral: boolean;
852
+ /** Delay before the safety defer fires, in ms. Kept under Discord's 3s window. */
853
+ delayMs: number;
854
+ }
855
+ /** Default safety margin: defer at 2s, leaving headroom before the 3s cutoff. */
856
+ declare const DEFAULT_AUTO_DEFER_DELAY_MS = 2000;
857
+ /** Normalise an {@link AutoDeferInput} (or `undefined`) into a config, or `undefined` when disabled. */
858
+ declare function normalizeAutoDefer(input: AutoDeferInput | undefined): AutoDeferConfig | undefined;
859
+ /** Interactions auto-defer supports (those answered with `deferReply`). */
860
+ type AutoDeferrableInteraction = ChatInputCommandInteraction | UserContextMenuCommandInteraction | MessageContextMenuCommandInteraction;
861
+ /**
862
+ * Arm a one-shot timer that calls `deferReply()` if the interaction is still
863
+ * un-acknowledged when it fires. Returns a cancel function — always call it
864
+ * once your handler settles (e.g. in a `finally`) to disarm the timer.
865
+ */
866
+ declare function armAutoDefer(interaction: AutoDeferrableInteraction, config: AutoDeferConfig): () => void;
867
+
411
868
  /** Reply options with an ergonomic `ephemeral` shortcut (mapped to flags). */
412
869
  type ReplyData = InteractionReplyOptions & {
413
870
  ephemeral?: boolean;
@@ -456,6 +913,22 @@ declare abstract class BaseContext<I extends RepliableInteraction = RepliableInt
456
913
  * most handlers ever need.
457
914
  */
458
915
  send(input: ReplyInput): Promise<void>;
916
+ /** The bot's resolved permissions in the current channel. */
917
+ get botPermissions(): Readonly<PermissionsBitField>;
918
+ /**
919
+ * Permission flag names the BOT is missing in the current channel — empty when
920
+ * it has them all. Zero-fetch: reads the permissions Discord attached to the
921
+ * interaction. Use before an action that needs elevated permissions.
922
+ */
923
+ botMissing(required: PermissionResolvable): PermissionsString[];
924
+ /** Permission flag names the invoking USER is missing in the current channel. */
925
+ userMissing(required: PermissionResolvable): PermissionsString[];
926
+ /**
927
+ * Wait for the next message in this channel from `userId` (defaults to the
928
+ * invoking user), resolving to it or `null` on timeout. The "type your answer"
929
+ * flow without hand-rolling a collector.
930
+ */
931
+ awaitMessageFrom(userId?: string, options?: AwaitMessageOptions): Promise<Message | null>;
459
932
  /** Get the configured {@link Embeds} factory — `client.embeds` or the default. */
460
933
  protected getEmbeds(): Embeds;
461
934
  /** State-aware send of a red error embed. Defaults to ephemeral. */
@@ -801,6 +1274,11 @@ declare class CommandContext<O extends OptionMap = OptionMap> extends BaseContex
801
1274
  get subcommand(): string | null;
802
1275
  /** Present a modal to the user in response to this command. */
803
1276
  showModal(modal: JSONEncodable<APIModalInteractionResponseCallbackData> | ModalComponentData | ModalBuilder): Promise<void>;
1277
+ /**
1278
+ * Show a modal and wait for the user to submit it, resolving to the submission
1279
+ * or `null` if they dismiss it / it times out. Scoped to this user and modal.
1280
+ */
1281
+ awaitModal(modal: ModalLike, options?: AwaitModalOptions): Promise<ModalSubmitInteraction | null>;
804
1282
  }
805
1283
  /**
806
1284
  * The handler argument for autocomplete requests. Provides the focused value
@@ -836,6 +1314,12 @@ interface CommonMeta {
836
1314
  cooldown?: CooldownInput;
837
1315
  /** Preconditions evaluated before the handler runs. */
838
1316
  guards?: readonly Guard[];
1317
+ /**
1318
+ * Auto-`deferReply()` if the handler hasn't responded within ~2s, preventing
1319
+ * `Unknown interaction` (10062) on slow work. `true` for defaults, or
1320
+ * `{ ephemeral, delayMs }`. With it on, respond via `ctx.send`/`ctx.editReply`.
1321
+ */
1322
+ autoDefer?: AutoDeferInput;
839
1323
  }
840
1324
  /** Configuration for a leaf (non-subcommand) slash command. */
841
1325
  interface CommandConfig<O extends OptionMap, R> extends CommonMeta {
@@ -890,6 +1374,7 @@ interface SlashCommandSpec {
890
1374
  autocompleter: (interaction: AutocompleteInteraction) => Promise<void>;
891
1375
  cooldown?: CooldownConfig;
892
1376
  guards?: readonly Guard[];
1377
+ autoDefer?: AutoDeferConfig;
893
1378
  }
894
1379
  /**
895
1380
  * A registered slash command. Serialises itself for the discord REST API and
@@ -908,6 +1393,8 @@ declare class SlashCommand {
908
1393
  readonly cooldown?: CooldownConfig;
909
1394
  /** Resolved guard list for this command, if any. */
910
1395
  readonly guards?: readonly Guard[];
1396
+ /** Resolved auto-defer configuration for this command, if any. */
1397
+ readonly autoDefer?: AutoDeferConfig;
911
1398
  /** @internal */
912
1399
  constructor(spec: SlashCommandSpec);
913
1400
  /** Serialise to the discord REST chat-input command payload. */
@@ -962,6 +1449,7 @@ declare class CommandRegistry {
962
1449
  private defaultCooldown?;
963
1450
  private defaultGuards;
964
1451
  private onUsage?;
1452
+ private defaultAutoDefer?;
965
1453
  /** Register one or more commands. Later registrations override by name. */
966
1454
  add(...commands: SlashCommand[]): this;
967
1455
  /** Remove a command by name. */
@@ -982,6 +1470,8 @@ declare class CommandRegistry {
982
1470
  setCooldowns(manager: CooldownManager, defaultCooldown?: CooldownConfig): this;
983
1471
  /** Guards that run before every command's own guards. */
984
1472
  setDefaultGuards(guards: readonly Guard[]): this;
1473
+ /** Default auto-defer applied to commands that don't set their own. */
1474
+ setAutoDefer(config?: AutoDeferConfig): this;
985
1475
  /** Attach a hook called after each successful command execution. */
986
1476
  setUsageHook(hook: (event: UsageEvent) => void): this;
987
1477
  /** Serialise every command to discord REST payloads. */
@@ -1440,6 +1930,13 @@ interface PrefixOptions {
1440
1930
  ignoreBots?: boolean;
1441
1931
  /** Match command names case-insensitively. Default `true`. */
1442
1932
  caseInsensitive?: boolean;
1933
+ /**
1934
+ * Resolve extra prefix(es) per message — e.g. a custom per-guild prefix from a
1935
+ * database or {@link createSettings}. Returned prefixes are tried in addition
1936
+ * to any static `prefix`. Return `null`/`undefined` for none. Keep it fast
1937
+ * (and cached): it runs on every candidate message.
1938
+ */
1939
+ dynamic?: (message: Message) => Awaitable<string | readonly string[] | null | undefined>;
1443
1940
  }
1444
1941
  /** Configuration for a prefix command. */
1445
1942
  interface PrefixCommandConfig<TArgs extends Record<string, unknown> = Record<string, never>, R = void> {
@@ -1542,6 +2039,8 @@ declare class PrefixRegistry {
1542
2039
  list(): PrefixCommand[];
1543
2040
  /** Strip a matching prefix (or bot mention) from `content`, or return `null`. */
1544
2041
  private stripPrefix;
2042
+ /** Resolve the effective prefixes for a message: static plus any dynamic ones. */
2043
+ private resolvePrefixes;
1545
2044
  /**
1546
2045
  * Parse and dispatch a message. Returns `true` when a command ran (or was
1547
2046
  * blocked by a cooldown), `false` when the message was not a prefix command.
@@ -1587,6 +2086,12 @@ interface SpearOptions {
1587
2086
  embeds?: Embeds | EmbedsOptions;
1588
2087
  /** Default guards (preconditions) applied before every command/component/prefix handler. */
1589
2088
  guards?: readonly Guard[];
2089
+ /**
2090
+ * Default auto-defer for every slash command and context menu (each handler
2091
+ * may override). Prevents `Unknown interaction` (10062) on slow handlers by
2092
+ * deferring automatically just before Discord's 3-second window closes.
2093
+ */
2094
+ autoDefer?: AutoDeferInput;
1590
2095
  }
1591
2096
  /** Options for {@link SpearClient}: discord.js options plus {@link SpearOptions}. `intents` may be omitted. */
1592
2097
  type SpearClientOptions = Partial<ClientOptions> & SpearOptions;
@@ -1679,6 +2184,12 @@ declare class SpearClient extends Client {
1679
2184
  schedule(config: TaskConfig): ScheduledTask;
1680
2185
  /** Stop the scheduler, then tear down the discord.js client. */
1681
2186
  destroy(): Promise<void>;
2187
+ /**
2188
+ * Close the bot cleanly on `SIGINT`/`SIGTERM`: run an optional hook, then
2189
+ * `destroy()` (stopping the scheduler and gateway), then exit. Returns a
2190
+ * disposer that removes the signal handlers. Logs progress via `client.logger`.
2191
+ */
2192
+ enableGracefulShutdown(options?: GracefulShutdownOptions): () => void;
1682
2193
  private route;
1683
2194
  }
1684
2195
 
@@ -1784,6 +2295,8 @@ interface ContextMenuMeta {
1784
2295
  nameLocalizations?: LocalizationMap;
1785
2296
  cooldown?: CooldownInput;
1786
2297
  guards?: readonly Guard[];
2298
+ /** Auto-`deferReply()` if the handler is slow, preventing `Unknown interaction`. */
2299
+ autoDefer?: AutoDeferInput;
1787
2300
  }
1788
2301
  /** Configuration for {@link userCommand}. */
1789
2302
  interface UserCommandConfig<R = void> extends ContextMenuMeta {
@@ -1800,6 +2313,7 @@ interface BaseContextMenuCommand {
1800
2313
  readonly name: string;
1801
2314
  readonly cooldown?: CooldownConfig;
1802
2315
  readonly guards?: readonly Guard[];
2316
+ readonly autoDefer?: AutoDeferConfig;
1803
2317
  toJSON(): RESTPostAPIContextMenuApplicationCommandsJSONBody;
1804
2318
  }
1805
2319
  /** A user-target context-menu command. */
@@ -1839,6 +2353,7 @@ declare class ContextMenuRegistry {
1839
2353
  private defaultCooldown?;
1840
2354
  private defaultGuards;
1841
2355
  private onUsage?;
2356
+ private defaultAutoDefer?;
1842
2357
  /** Register one or more context-menu commands. */
1843
2358
  add(...commands: readonly ContextMenuCommand[]): this;
1844
2359
  /** Total number of registered context-menu commands. */
@@ -1850,6 +2365,8 @@ declare class ContextMenuRegistry {
1850
2365
  setLogger(logger: Logger): this;
1851
2366
  setCooldowns(manager: CooldownManager, defaultCooldown?: CooldownConfig): this;
1852
2367
  setDefaultGuards(guards: readonly Guard[]): this;
2368
+ /** Default auto-defer applied to menus that don't set their own. */
2369
+ setAutoDefer(config?: AutoDeferConfig): this;
1853
2370
  setUsageHook(hook: (event: UsageEvent) => void): this;
1854
2371
  /** Dispatch a user-target interaction. */
1855
2372
  handleUser(interaction: UserContextMenuCommandInteraction): Promise<void>;
@@ -2041,6 +2558,11 @@ declare class MessageComponentContext<P, I extends AnyComponentInteraction = Any
2041
2558
  deferUpdate(): Promise<void>;
2042
2559
  /** Open a modal in response to this component. */
2043
2560
  showModal(modal: JSONEncodable<APIModalInteractionResponseCallbackData> | ModalComponentData | ModalBuilder): Promise<void>;
2561
+ /**
2562
+ * Show a modal and wait for the user to submit it, resolving to the submission
2563
+ * or `null` if they dismiss it / it times out. Scoped to this user and modal.
2564
+ */
2565
+ awaitModal(modal: ModalLike, options?: AwaitModalOptions): Promise<ModalSubmitInteraction | null>;
2044
2566
  }
2045
2567
  /** Context for a button click. */
2046
2568
  declare class ButtonContext<P = Record<string, never>> extends MessageComponentContext<P, ButtonInteraction> {
@@ -2255,4 +2777,4 @@ declare function modal<const P extends string, F extends Record<string, TextInpu
2255
2777
  */
2256
2778
  declare function row<C extends MessageActionRowComponentBuilder>(...components: C[]): ActionRowBuilder<C>;
2257
2779
 
2258
- export { type AllowedChannelType, type AnyComponentInteraction, type AnyOptionDef, AutocompleteContext, type AutocompleteHandler, BaseContext, type BaseContextMenuCommand, type BuildArgs, type Button, type ButtonConfig, ButtonContext, type ButtonRoute, type ButtonStyleInput, type CacheSetOptions, type CacheStore, type ChannelSelect, ChannelSelectContext, type ChannelSelectRoute, type CommandConfig, CommandContext, type CommandErrorHandler, type CommandGroupConfig, CommandRegistry, type CompiledPattern, type ComponentDef, type ComponentErrorHandler, ComponentRegistry, type ConfirmButtonOptions, type ConfirmButtonStyle, type ConfirmOptions, type ConfirmResult, type ContextMenuCommand, ContextMenuRegistry, type CooldownActor, type CooldownConfig, type CooldownExemptions, type CooldownInput, CooldownManager, type CooldownOverrides, type CooldownResult, type CooldownScope, CronExpression, DEFAULT_EMBED_COLORS, DEFAULT_EMBED_ICONS, type DeployOptions, type DeployResult, type DiscordTimestampStyle, type EmbedColors, type EmbedIcons, type EmbedLevel, type EmbedPresetInput, Embeds, type EmbedsOptions, type EntitySelectConfig, type EnvReader, type EventConfig, type EventDef, type EventHandler, EventRegistry, type FormatDurationOptions, type Guard, type GuardContext, type GuardResult, Intents, JsonFileUsageStore, KeyedLock, type KeyedLockOptions, type LinkButtonConfig, type LoadConfigOptions, type LoadEnvOptions, type LoadOptions, type LockRelease, type LogEntry, type LogLevel, type LogOptions, type LogSink, type LogThreshold, type LogValue, Logger, type LoggerOptions, MAX_CUSTOM_ID_LENGTH, MemoryCache, MemoryUsageStore, type MentionableSelect, MentionableSelectContext, type MentionableSelectRoute, type MessageCommandConfig, MessageComponentContext, type MessageContextMenu, MessageContextMenuContext, type Modal, type ModalConfig, ModalContext, type ModalRoute, type OptionChoice, type OptionDef, type OptionMap, type OptionValue, type PaginateOptions, type PaginateRender, type ParamNames, type Params, type ParsedCustomId, type ParsedEnv, type PrefixArgError, type PrefixArgSpec, PrefixArgsBuilder, type PrefixArgsOk, type PrefixArgsParser, type PrefixCommand, type PrefixCommandConfig, PrefixContext, type PrefixErrorHandler, type PrefixOptions, PrefixRegistry, type RateLimitResult, type Registerable, type ReplyData, type ReplyInput, type ResolvedOption, type ResolvedOptions, type RoleSelect, RoleSelectContext, type RoleSelectRoute, type RunGuardsResult, type SafeFetchOptions, type ScheduledTask, SlashCommand, SpearClient, type SpearClientOptions, type SpearOptions, type SpearPlugin, type StringSelect, type StringSelectConfig, StringSelectContext, type StringSelectRoute, type Subcommand, type SubcommandConfig, type SubcommandGroup, type SubcommandGroupConfig, type TaskConfig, TaskScheduler, type TextInputDef, type TextInputStyleInput, type UsageEvent, type UsageMetaValue, type UsageOptions, type UsageOutcome, type UsageStore, UsageTracker, type UsageType, type UserCommandConfig, type UserContextMenu, UserContextMenuContext, type UserSelect, UserSelectContext, type UserSelectRoute, asEphemeral, buildCustomId, buildPaginatorPage, button, channelSelect, collectModules, command, commandGroup, compilePattern, confirm, consoleSink, createCache, cron, defaultEmbeds, definePlugin, denied, discordTimestamp, dmOnly, effectiveDuration, env, event, fetchChannel, fetchGuild, fetchMember, fetchMessage, fetchRole, fetchUser, formatCooldownMessage, formatDuration, formatUsage, guard, guildOnly, jsonlSink, linkButton, loadConfig, loadConfigAsync, loadEnv, loadInto, lookup, lookupOptional, mentionableSelect, messageCommand, modal, normalizeCooldown, normalizeReply, option, optionsHaveAutocomplete, paginate, paramsFromValues, parseCustomId, parseDuration, parseEnv, prefixArgs, prefixCommand, readOption, relativeTimestamp, requireAllRoles, requireAnyRole, requireBotPermissions, requireOwner, requireUserPermissions, roleSelect, row, runGuards, safeFetch, safeTry, stringSelect, subcommand, subcommandGroup, task, textInput, toAPIOption, toError, userCommand, userSelect, webhookSink, withSafeTimeout };
2780
+ export { type AllowedChannelType, type AnyComponentInteraction, type AnyOptionDef, type AutoDeferConfig, type AutoDeferInput, type AutoDeferrableInteraction, AutocompleteContext, type AutocompleteHandler, type AwaitComponentOptions, type AwaitMessageOptions, type AwaitModalOptions, BaseContext, type BaseContextMenuCommand, type BuildArgs, type Button, type ButtonConfig, ButtonContext, type ButtonRoute, type ButtonStyleInput, type CacheSetOptions, type CacheStore, type ChannelSelect, ChannelSelectContext, type ChannelSelectRoute, type ChunkOptions, type CollectableChannel, type CommandConfig, CommandContext, type CommandErrorHandler, type CommandGroupConfig, CommandRegistry, type CompiledPattern, type ComponentDef, type ComponentErrorHandler, ComponentRegistry, type ConfirmButtonOptions, type ConfirmButtonStyle, type ConfirmOptions, type ConfirmResult, type ContextMenuCommand, ContextMenuRegistry, type CooldownActor, type CooldownConfig, type CooldownExemptions, type CooldownInput, CooldownManager, type CooldownOverrides, type CooldownResult, type CooldownScope, type CreateSettingsOptions, CronExpression, DEFAULT_AUTO_DEFER_DELAY_MS, DEFAULT_EMBED_COLORS, DEFAULT_EMBED_ICONS, type DeployOptions, type DeployResult, type Destroyable, DiscordErrorCode, type DiscordErrorCodeValue, type DiscordTimestampStyle, type EmbedColors, type EmbedIcons, type EmbedLevel, type EmbedPresetInput, Embeds, type EmbedsOptions, type EntitySelectConfig, type EnvReader, type EventConfig, type EventDef, type EventHandler, EventRegistry, type FormatDurationOptions, type GracefulShutdownOptions, type Guard, type GuardContext, type GuardResult, Intents, JsonFileUsageStore, JsonStore, type KeyValueStore, KeyedLock, type KeyedLockOptions, type LinkButtonConfig, type LoadConfigOptions, type LoadEnvOptions, type LoadOptions, type LockRelease, type LogEntry, type LogLevel, type LogOptions, type LogSink, type LogThreshold, type LogValue, Logger, type LoggerOptions, MAX_CUSTOM_ID_LENGTH, MESSAGE_CHARACTER_LIMIT, MemoryCache, MemoryStore, MemoryUsageStore, type MentionableSelect, MentionableSelectContext, type MentionableSelectRoute, type MessageCommandConfig, MessageComponentContext, type MessageContextMenu, MessageContextMenuContext, type Modal, type ModalConfig, ModalContext, type ModalLike, type ModalRoute, type ModalShowingInteraction, type ModerationCheckOptions, type ModerationCheckResult, type OptionChoice, type OptionDef, type OptionMap, type OptionValue, type PaginateOptions, type PaginateRender, type ParamNames, type Params, type ParsedCustomId, type ParsedEnv, type PermissionHolder, type PrefixArgError, type PrefixArgSpec, PrefixArgsBuilder, type PrefixArgsOk, type PrefixArgsParser, type PrefixCommand, type PrefixCommandConfig, PrefixContext, type PrefixErrorHandler, type PrefixOptions, PrefixRegistry, type RateLimitResult, type Registerable, type ReplyData, type ReplyInput, type ResolvedOption, type ResolvedOptions, type RoleSelect, RoleSelectContext, type RoleSelectRoute, type RunGuardsResult, type SafeFetchOptions, type ScheduledTask, type SettingsManager, type ShutdownLogger, SlashCommand, SpearClient, type SpearClientOptions, type SpearOptions, type SpearPlugin, type StringSelect, type StringSelectConfig, StringSelectContext, type StringSelectRoute, type Subcommand, type SubcommandConfig, type SubcommandGroup, type SubcommandGroupConfig, type TaskConfig, TaskScheduler, type TextInputDef, type TextInputStyleInput, type UsageEvent, type UsageMetaValue, type UsageOptions, type UsageOutcome, type UsageStore, UsageTracker, type UsageType, type UserCommandConfig, type UserContextMenu, UserContextMenuContext, type UserSelect, UserSelectContext, type UserSelectRoute, armAutoDefer, asEphemeral, awaitComponent, awaitMessage, botMissingPermissions, buildCustomId, buildPaginatorPage, button, canActOn, channelSelect, chunkMessage, collectModules, command, commandGroup, compareRoles, compilePattern, confirm, consoleSink, createCache, createSettings, cron, defaultEmbeds, definePlugin, denied, discordTimestamp, dmOnly, effectiveDuration, env, event, explainDiscordError, fetchChannel, fetchGuild, fetchMember, fetchMessage, fetchRole, fetchUser, formatCooldownMessage, formatDuration, formatPermissions, formatUsage, gracefulShutdown, guard, guildOnly, hasPermissions, isDiscordError, isHTTPError, isRateLimitError, jsonlSink, linkButton, loadConfig, loadConfigAsync, loadEnv, loadInto, lookup, lookupOptional, mentionableSelect, messageCommand, missingPermissions, modal, moderationCheck, namespaced, normalizeAutoDefer, normalizeCooldown, normalizeReply, option, optionsHaveAutocomplete, paginate, paramsFromValues, parseCustomId, parseDuration, parseEnv, prefixArgs, prefixCommand, readOption, relativeTimestamp, requireAllRoles, requireAnyRole, requireBotPermissions, requireOwner, requireUserPermissions, roleSelect, row, runGuards, safeFetch, safeTry, showAndAwaitModal, stringSelect, subcommand, subcommandGroup, task, textInput, toAPIOption, toError, truncate, userCommand, userSelect, webhookSink, withSafeTimeout };