novaapp-sdk 1.0.10 → 1.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/dist/index.d.mts CHANGED
@@ -60,6 +60,59 @@ interface Member {
60
60
  isBot: boolean;
61
61
  };
62
62
  }
63
+ type ChannelType = 'TEXT' | 'VOICE' | 'ANNOUNCEMENT' | 'FORUM' | 'STAGE';
64
+ interface Channel {
65
+ id: string;
66
+ name: string;
67
+ type: ChannelType;
68
+ serverId: string | null;
69
+ topic: string | null;
70
+ position: number;
71
+ isNsfw: boolean;
72
+ slowMode: number | null;
73
+ createdAt: string;
74
+ }
75
+ interface Role {
76
+ id: string;
77
+ name: string;
78
+ color: string | null;
79
+ position: number;
80
+ serverId: string;
81
+ hoist: boolean;
82
+ permissions: Record<string, boolean>;
83
+ createdAt: string;
84
+ }
85
+ interface BanEntry {
86
+ userId: string;
87
+ username: string;
88
+ displayName: string;
89
+ avatar: string | null;
90
+ reason: string | null;
91
+ bannedAt: string;
92
+ moderatorId: string | null;
93
+ }
94
+ interface Invite {
95
+ code: string;
96
+ serverId: string;
97
+ creatorId: string;
98
+ uses: number;
99
+ maxUses: number | null;
100
+ expiresAt: string | null;
101
+ createdAt: string;
102
+ }
103
+ /** A message with all reaction data fetched. */
104
+ interface ReactionDetail {
105
+ emoji: string;
106
+ count: number;
107
+ users: Array<{
108
+ id: string;
109
+ username: string;
110
+ displayName: string;
111
+ avatar: string | null;
112
+ }>;
113
+ }
114
+ /** Bot status that can be set via `client.setStatus()`. */
115
+ type BotStatus = 'ONLINE' | 'IDLE' | 'DND' | 'OFFLINE';
63
116
  interface Attachment {
64
117
  id: string;
65
118
  url: string;
@@ -80,25 +133,35 @@ interface EmbedField {
80
133
  interface Embed {
81
134
  title?: string;
82
135
  description?: string;
136
+ /** Makes the title a clickable link. */
83
137
  url?: string;
84
- color?: number;
138
+ /** Hex colour string — e.g. `'#5865F2'`. */
139
+ color?: string;
85
140
  thumbnail?: string;
86
141
  image?: string;
87
142
  footer?: string;
88
143
  fields?: EmbedField[];
89
144
  timestamp?: string;
145
+ author?: {
146
+ name: string;
147
+ iconUrl?: string;
148
+ };
90
149
  }
91
150
  interface MessageComponent {
92
151
  type: 'button' | 'select';
93
- customId: string;
152
+ customId?: string;
94
153
  label?: string;
95
- style?: 'primary' | 'secondary' | 'success' | 'danger';
154
+ style?: 'primary' | 'secondary' | 'success' | 'danger' | 'link';
155
+ emoji?: string;
156
+ url?: string;
96
157
  placeholder?: string;
97
158
  options?: Array<{
98
159
  label: string;
99
160
  value: string;
100
161
  description?: string;
101
162
  }>;
163
+ minValues?: number;
164
+ maxValues?: number;
102
165
  disabled?: boolean;
103
166
  }
104
167
  interface Message {
@@ -268,6 +331,24 @@ interface PollInteractionsOptions {
268
331
  limit?: number;
269
332
  since?: string;
270
333
  }
334
+ interface CreateChannelOptions {
335
+ name: string;
336
+ type?: ChannelType;
337
+ topic?: string;
338
+ position?: number;
339
+ isNsfw?: boolean;
340
+ slowMode?: number;
341
+ }
342
+ interface EditChannelOptions {
343
+ name?: string;
344
+ topic?: string;
345
+ position?: number;
346
+ isNsfw?: boolean;
347
+ slowMode?: number;
348
+ }
349
+ interface FetchChannelsOptions {
350
+ type?: ChannelType;
351
+ }
271
352
  /** A raw permission record stored for a specific scope (server, role, or channel). */
272
353
  interface BotPermissionRecord {
273
354
  id: string;
@@ -306,41 +387,10 @@ interface NovaClientOptions {
306
387
  token: string;
307
388
  /**
308
389
  * Base URL of the Nova server.
309
- * @default "https://api.nova.chat"
390
+ * @default "https://novachatapp.com"
310
391
  */
311
392
  baseUrl?: string;
312
393
  }
313
- interface NovaClientEvents {
314
- /** Fired when the bot successfully connects and is identified. */
315
- ready: (bot: BotApplication) => void;
316
- /** Fired for every bot:event from the gateway. */
317
- event: (event: BotEvent) => void;
318
- /** Fired when an interaction is created (slash command, button, etc.). */
319
- interactionCreate: (interaction: Interaction) => void;
320
- /** Fired when the gateway connection is lost. */
321
- disconnect: (reason: string) => void;
322
- /** Fired on gateway/API errors. */
323
- error: (err: {
324
- code: number;
325
- message: string;
326
- }) => void;
327
- /** A message was sent in a channel the bot is in. */
328
- messageCreate: (data: BotEvent['data']) => void;
329
- /** A message was edited. */
330
- messageUpdate: (data: BotEvent['data']) => void;
331
- /** A message was deleted. */
332
- messageDelete: (data: BotEvent['data']) => void;
333
- /** A reaction was added to a message. */
334
- reactionAdd: (data: BotEvent['data']) => void;
335
- /** A reaction was removed from a message. */
336
- reactionRemove: (data: BotEvent['data']) => void;
337
- /** A user joined a server the bot is in. */
338
- memberAdd: (data: BotEvent['data']) => void;
339
- /** A user left a server the bot is in. */
340
- memberRemove: (data: BotEvent['data']) => void;
341
- /** A user started typing. */
342
- typingStart: (data: BotEvent['data']) => void;
343
- }
344
394
 
345
395
  declare class MessagesAPI {
346
396
  private readonly http;
@@ -375,6 +425,64 @@ declare class MessagesAPI {
375
425
  typing(channelId: string): Promise<{
376
426
  ok: true;
377
427
  }>;
428
+ /**
429
+ * Fetch a single message by ID.
430
+ *
431
+ * @example
432
+ * const msg = await client.messages.fetchOne('message-id')
433
+ */
434
+ fetchOne(messageId: string): Promise<Message>;
435
+ /**
436
+ * Pin a message in its channel.
437
+ * The bot must have the `messages.manage` scope.
438
+ *
439
+ * @example
440
+ * await client.messages.pin('message-id')
441
+ */
442
+ pin(messageId: string): Promise<{
443
+ ok: true;
444
+ }>;
445
+ /**
446
+ * Unpin a message.
447
+ *
448
+ * @example
449
+ * await client.messages.unpin('message-id')
450
+ */
451
+ unpin(messageId: string): Promise<{
452
+ ok: true;
453
+ }>;
454
+ /**
455
+ * Fetch all pinned messages in a channel.
456
+ *
457
+ * @example
458
+ * const pins = await client.messages.fetchPinned('channel-id')
459
+ */
460
+ fetchPinned(channelId: string): Promise<Message[]>;
461
+ /**
462
+ * Add a reaction to a message.
463
+ *
464
+ * @example
465
+ * await client.messages.addReaction('message-id', '👍')
466
+ */
467
+ addReaction(messageId: string, emoji: string): Promise<{
468
+ ok: true;
469
+ }>;
470
+ /**
471
+ * Remove the bot's reaction from a message.
472
+ *
473
+ * @example
474
+ * await client.messages.removeReaction('message-id', '👍')
475
+ */
476
+ removeReaction(messageId: string, emoji: string): Promise<{
477
+ ok: true;
478
+ }>;
479
+ /**
480
+ * Fetch all reactions on a message.
481
+ *
482
+ * @example
483
+ * const reactions = await client.messages.fetchReactions('message-id')
484
+ */
485
+ fetchReactions(messageId: string): Promise<ReactionDetail[]>;
378
486
  }
379
487
 
380
488
  declare class CommandsAPI {
@@ -481,6 +589,56 @@ declare class MembersAPI {
481
589
  ban(serverId: string, userId: string, reason?: string): Promise<{
482
590
  ok: true;
483
591
  }>;
592
+ /**
593
+ * Unban a previously banned user.
594
+ * Requires the `members.ban` scope.
595
+ *
596
+ * @example
597
+ * await client.members.unban('server-id', 'user-id')
598
+ */
599
+ unban(serverId: string, userId: string): Promise<{
600
+ ok: true;
601
+ }>;
602
+ /**
603
+ * Fetch the ban list for a server.
604
+ * Requires the `members.ban` scope.
605
+ *
606
+ * @example
607
+ * const bans = await client.members.listBans('server-id')
608
+ * for (const ban of bans) {
609
+ * console.log(`${ban.username} — ${ban.reason ?? 'No reason'}`)
610
+ * }
611
+ */
612
+ listBans(serverId: string): Promise<BanEntry[]>;
613
+ /**
614
+ * Send a direct message to a user (DM).
615
+ * Requires the `messages.write` scope.
616
+ *
617
+ * @example
618
+ * await client.members.dm('user-id', { content: 'Hello!' })
619
+ * await client.members.dm('user-id', 'Hello from the bot!')
620
+ */
621
+ dm(userId: string, options: string | Omit<SendMessageOptions, 'replyToId'>): Promise<Message>;
622
+ /**
623
+ * Add a role to a member.
624
+ * Requires the `members.roles` scope.
625
+ *
626
+ * @example
627
+ * await client.members.addRole('server-id', 'user-id', 'role-id')
628
+ */
629
+ addRole(serverId: string, userId: string, roleId: string): Promise<{
630
+ ok: true;
631
+ }>;
632
+ /**
633
+ * Remove a role from a member.
634
+ * Requires the `members.roles` scope.
635
+ *
636
+ * @example
637
+ * await client.members.removeRole('server-id', 'user-id', 'role-id')
638
+ */
639
+ removeRole(serverId: string, userId: string, roleId: string): Promise<{
640
+ ok: true;
641
+ }>;
484
642
  }
485
643
 
486
644
  declare class ServersAPI {
@@ -494,6 +652,15 @@ declare class ServersAPI {
494
652
  * console.log(`Bot is in ${servers.length} servers`)
495
653
  */
496
654
  list(): Promise<NovaServer[]>;
655
+ /**
656
+ * Fetch all roles in a server.
657
+ * Results are sorted by position (lowest first).
658
+ *
659
+ * @example
660
+ * const roles = await client.servers.listRoles('server-id')
661
+ * const adminRole = roles.find(r => r.name === 'Admin')
662
+ */
663
+ listRoles(serverId: string): Promise<Role[]>;
497
664
  }
498
665
 
499
666
  declare class InteractionsAPI {
@@ -619,67 +786,742 @@ declare class PermissionsAPI {
619
786
  get(options: PermissionsQueryOptions): Promise<PermissionsResult>;
620
787
  }
621
788
 
789
+ declare class ChannelsAPI {
790
+ private readonly http;
791
+ constructor(http: HttpClient);
792
+ /**
793
+ * Fetch all channels in a server the bot is a member of.
794
+ *
795
+ * @example
796
+ * const channels = await client.channels.list('server-id')
797
+ * const textChannels = channels.filter(c => c.type === 'TEXT')
798
+ */
799
+ list(serverId: string): Promise<Channel[]>;
800
+ /**
801
+ * Fetch a single channel by ID.
802
+ *
803
+ * @example
804
+ * const channel = await client.channels.fetch('channel-id')
805
+ * console.log(channel.name, channel.type)
806
+ */
807
+ fetch(channelId: string): Promise<Channel>;
808
+ /**
809
+ * Create a new channel in a server.
810
+ * Requires the `channels.manage` scope.
811
+ *
812
+ * @example
813
+ * const channel = await client.channels.create('server-id', {
814
+ * name: 'announcements',
815
+ * type: 'ANNOUNCEMENT',
816
+ * topic: 'Official announcements only',
817
+ * })
818
+ */
819
+ create(serverId: string, options: CreateChannelOptions): Promise<Channel>;
820
+ /**
821
+ * Edit an existing channel.
822
+ * Requires the `channels.manage` scope.
823
+ *
824
+ * @example
825
+ * await client.channels.edit('channel-id', { topic: 'New topic!' })
826
+ */
827
+ edit(channelId: string, options: EditChannelOptions): Promise<Channel>;
828
+ /**
829
+ * Delete a channel.
830
+ * Requires the `channels.manage` scope.
831
+ *
832
+ * @example
833
+ * await client.channels.delete('channel-id')
834
+ */
835
+ delete(channelId: string): Promise<{
836
+ ok: true;
837
+ }>;
838
+ /**
839
+ * Fetch messages from a channel.
840
+ *
841
+ * @example
842
+ * const messages = await client.channels.fetchMessages('channel-id', { limit: 50 })
843
+ */
844
+ fetchMessages(channelId: string, options?: FetchMessagesOptions): Promise<Message[]>;
845
+ /**
846
+ * Fetch all pinned messages in a channel.
847
+ *
848
+ * @example
849
+ * const pins = await client.channels.fetchPins('channel-id')
850
+ */
851
+ fetchPins(channelId: string): Promise<Message[]>;
852
+ /**
853
+ * Send a typing indicator in a channel.
854
+ * Displayed to users for ~5 seconds.
855
+ *
856
+ * @example
857
+ * await client.channels.startTyping('channel-id')
858
+ */
859
+ startTyping(channelId: string): Promise<{
860
+ ok: true;
861
+ }>;
862
+ }
863
+
864
+ declare class ReactionsAPI {
865
+ private readonly http;
866
+ constructor(http: HttpClient);
867
+ /**
868
+ * Add a reaction to a message.
869
+ * Use a plain emoji character or a custom emoji ID.
870
+ *
871
+ * @example
872
+ * await client.reactions.add('message-id', '👍')
873
+ * await client.reactions.add('message-id', '🎉')
874
+ */
875
+ add(messageId: string, emoji: string): Promise<{
876
+ ok: true;
877
+ }>;
878
+ /**
879
+ * Remove the bot's reaction from a message.
880
+ *
881
+ * @example
882
+ * await client.reactions.remove('message-id', '👍')
883
+ */
884
+ remove(messageId: string, emoji: string): Promise<{
885
+ ok: true;
886
+ }>;
887
+ /**
888
+ * Remove all reactions from a message.
889
+ * Requires the `messages.manage` scope.
890
+ *
891
+ * @example
892
+ * await client.reactions.removeAll('message-id')
893
+ */
894
+ removeAll(messageId: string): Promise<{
895
+ ok: true;
896
+ }>;
897
+ /**
898
+ * Remove all reactions of a specific emoji from a message.
899
+ * Requires the `messages.manage` scope.
900
+ *
901
+ * @example
902
+ * await client.reactions.removeEmoji('message-id', '👍')
903
+ */
904
+ removeEmoji(messageId: string, emoji: string): Promise<{
905
+ ok: true;
906
+ }>;
907
+ /**
908
+ * Fetch all reactions on a message, broken down by emoji.
909
+ *
910
+ * @example
911
+ * const reactions = await client.reactions.fetch('message-id')
912
+ * for (const r of reactions) {
913
+ * console.log(`${r.emoji} — ${r.count} reactions from ${r.users.map(u => u.username).join(', ')}`)
914
+ * }
915
+ */
916
+ fetch(messageId: string): Promise<ReactionDetail[]>;
917
+ /**
918
+ * Fetch reactions for a specific emoji on a message.
919
+ *
920
+ * @example
921
+ * const detail = await client.reactions.fetchEmoji('message-id', '👍')
922
+ * console.log(`${detail.count} thumbs ups`)
923
+ */
924
+ fetchEmoji(messageId: string, emoji: string): Promise<ReactionDetail>;
925
+ }
926
+
927
+ type TextInputStyle = 'short' | 'paragraph';
622
928
  /**
623
- * The main Nova bot client.
929
+ * Fluent builder for a single text-input field inside a modal.
624
930
  *
625
931
  * @example
626
- * import { NovaClient } from 'nova-bot-sdk'
932
+ * new TextInputBuilder()
933
+ * .setCustomId('reason')
934
+ * .setLabel('Reason')
935
+ * .setStyle('paragraph')
936
+ * .setPlaceholder('Describe the issue in detail…')
937
+ * .setRequired(true)
938
+ * .setMaxLength(1000)
939
+ */
940
+ declare class TextInputBuilder {
941
+ private _data;
942
+ /** Unique ID for this field — the key in `interaction.modalData` on submit. */
943
+ setCustomId(customId: string): this;
944
+ /** Label shown above the input inside the modal. */
945
+ setLabel(label: string): this;
946
+ /**
947
+ * Input style:
948
+ * - `'short'` — single-line text input
949
+ * - `'paragraph'` — multi-line textarea
950
+ */
951
+ setStyle(style: TextInputStyle): this;
952
+ /** Greyed-out hint text shown when the field is empty. */
953
+ setPlaceholder(placeholder: string): this;
954
+ /** Whether the user must fill in this field before submitting. */
955
+ setRequired(required?: boolean): this;
956
+ /** Minimum number of characters required. */
957
+ setMinLength(min: number): this;
958
+ /** Maximum number of characters allowed. */
959
+ setMaxLength(max: number): this;
960
+ /** Pre-filled default value. */
961
+ setValue(value: string): this;
962
+ toJSON(): BotModalField;
963
+ }
964
+
965
+ type FieldLike = TextInputBuilder | BotModalField;
966
+ /**
967
+ * Fluent builder for bot modal dialogs.
627
968
  *
628
- * const client = new NovaClient({ token: 'nova_bot_...' })
969
+ * @example
970
+ * const modal = new ModalBuilder()
971
+ * .setTitle('Submit a report')
972
+ * .setCustomId('report_modal')
973
+ * .addField(
974
+ * new TextInputBuilder()
975
+ * .setCustomId('reason')
976
+ * .setLabel('Reason')
977
+ * .setStyle('paragraph')
978
+ * .setRequired(true)
979
+ * .setMaxLength(1000)
980
+ * )
981
+ * .addField(
982
+ * new TextInputBuilder()
983
+ * .setCustomId('proof')
984
+ * .setLabel('Evidence (optional URL)')
985
+ * .setStyle('short')
986
+ * )
629
987
  *
630
- * client.on('ready', (bot) => {
631
- * console.log(`Logged in as ${bot.botUser.username}`)
988
+ * const submitted = await interaction.openModal(modal)
989
+ * if (submitted) {
990
+ * const reason = submitted.modalData.reason
991
+ * }
992
+ */
993
+ declare class ModalBuilder {
994
+ private _title;
995
+ private _customId;
996
+ private readonly _fields;
997
+ /** Title shown at the top of the modal dialog. */
998
+ setTitle(title: string): this;
999
+ /** Custom ID passed back with the `MODAL_SUBMIT` interaction. */
1000
+ setCustomId(customId: string): this;
1001
+ /** Add a single text-input field. */
1002
+ addField(field: FieldLike): this;
1003
+ /** Add multiple fields at once. */
1004
+ addFields(...fields: FieldLike[]): this;
1005
+ toJSON(): BotModalDefinition;
1006
+ }
1007
+
1008
+ /**
1009
+ * Typed accessor for slash- and prefix-command options.
1010
+ * Available via `interaction.options`.
1011
+ *
1012
+ * @example
1013
+ * client.command('ban', async (interaction) => {
1014
+ * const userId = interaction.options.getString('user', true)
1015
+ * const reason = interaction.options.getString('reason') ?? 'No reason given'
632
1016
  * })
1017
+ */
1018
+ declare class InteractionOptions {
1019
+ private readonly _map;
1020
+ constructor(data: unknown);
1021
+ /** Whether this option was supplied by the user. */
1022
+ has(name: string): boolean;
1023
+ /** @overload Required — never returns `null`. */
1024
+ getString(name: string, required: true): string;
1025
+ /** @overload Optional — returns `null` when not supplied. */
1026
+ getString(name: string, required?: false): string | null;
1027
+ /** @overload Required — never returns `null`. */
1028
+ getInteger(name: string, required: true): number;
1029
+ /** @overload Optional — returns `null` when not supplied. */
1030
+ getInteger(name: string, required?: false): number | null;
1031
+ /** @overload Required — never returns `null`. */
1032
+ getNumber(name: string, required: true): number;
1033
+ /** @overload Optional — returns `null` when not supplied. */
1034
+ getNumber(name: string, required?: false): number | null;
1035
+ /** Returns `true` or `false`, or `null` if not supplied. */
1036
+ getBoolean(name: string): boolean | null;
1037
+ /**
1038
+ * Returns the mentioned **user ID** string (type `USER` option).
1039
+ * @overload Required.
1040
+ */
1041
+ getUser(name: string, required: true): string;
1042
+ getUser(name: string, required?: false): string | null;
1043
+ /**
1044
+ * Returns the mentioned **channel ID** string (type `CHANNEL` option).
1045
+ * @overload Required.
1046
+ */
1047
+ getChannel(name: string, required: true): string;
1048
+ getChannel(name: string, required?: false): string | null;
1049
+ /**
1050
+ * Returns the mentioned **role ID** string (type `ROLE` option).
1051
+ * @overload Required.
1052
+ */
1053
+ getRole(name: string, required: true): string;
1054
+ getRole(name: string, required?: false): string | null;
1055
+ }
1056
+ /**
1057
+ * Rich wrapper around a raw bot interaction.
1058
+ * Returned by `interactionCreate`, `client.command()`, `client.button()`, `client.selectMenu()`.
633
1059
  *
1060
+ * @example
634
1061
  * client.on('interactionCreate', async (interaction) => {
635
- * if (interaction.commandName === 'ping') {
636
- * await client.interactions.respond(interaction.id, { content: 'Pong!' })
1062
+ * if (interaction.isSlashCommand() && interaction.commandName === 'ping') {
1063
+ * await interaction.reply('Pong! 🏓')
637
1064
  * }
638
1065
  * })
639
1066
  *
640
- * await client.connect()
1067
+ * // Or use built-in routing:
1068
+ * client.command('ping', async (interaction) => {
1069
+ * await interaction.reply('Pong! 🏓')
1070
+ * })
641
1071
  */
642
- declare class NovaClient extends EventEmitter {
643
- /** The authenticated bot application. Available after `ready` fires. */
644
- botUser: BotApplication | null;
645
- /** Send, edit, delete and fetch messages. */
646
- readonly messages: MessagesAPI;
647
- /** Register and manage slash, prefix, and context menu commands. */
648
- readonly commands: CommandsAPI;
649
- /** List, kick and ban server members. */
650
- readonly members: MembersAPI;
651
- /** List servers the bot is a member of. */
652
- readonly servers: ServersAPI;
653
- /** Acknowledge and respond to interactions. */
654
- readonly interactions: InteractionsAPI;
655
- /** Query the bot's effective permissions within a server, channel, or role scope. */
656
- readonly permissions: PermissionsAPI;
657
- private socket;
658
- private readonly http;
659
- private readonly options;
660
- constructor(options: NovaClientOptions);
1072
+ declare class NovaInteraction {
1073
+ private readonly _raw;
1074
+ private readonly _api;
1075
+ /** Unique interaction ID. */
1076
+ readonly id: string;
1077
+ /** Interaction type. Use type guards (`.isSlashCommand()` etc.) for narrowing. */
1078
+ readonly type: InteractionType;
1079
+ /** Command name — set for `SLASH_COMMAND` and `PREFIX_COMMAND` interactions. */
1080
+ readonly commandName: string | null;
1081
+ /** Component custom ID set for `BUTTON_CLICK`, `SELECT_MENU`, and `MODAL_SUBMIT` interactions. */
1082
+ readonly customId: string | null;
1083
+ /** ID of the user who triggered this interaction. */
1084
+ readonly userId: string;
1085
+ /** ID of the channel where this interaction occurred. */
1086
+ readonly channelId: string;
1087
+ /** ID of the server where this interaction occurred (`null` in DMs). */
1088
+ readonly serverId: string | null;
1089
+ /** ID of the message that contained the button/select component (if any). */
1090
+ readonly triggerMsgId: string | null;
1091
+ /** Selected option values for `SELECT_MENU` interactions. */
1092
+ readonly values: string[];
661
1093
  /**
662
- * Connect to the Nova WebSocket gateway.
663
- * Resolves when the `ready` event is received.
1094
+ * Field values submitted in a `MODAL_SUBMIT` interaction.
1095
+ * Keys are the `customId`s of the `TextInputBuilder` fields.
664
1096
  *
665
1097
  * @example
666
- * await client.connect()
1098
+ * const reason = interaction.modalData.reason
667
1099
  */
668
- connect(): Promise<void>;
1100
+ readonly modalData: Record<string, string>;
1101
+ /** ISO timestamp of when the interaction was created. */
1102
+ readonly createdAt: string;
669
1103
  /**
670
- * Disconnect from the gateway and clean up.
1104
+ * Typed accessor for slash/prefix command options.
1105
+ *
1106
+ * @example
1107
+ * const userId = interaction.options.getUser('user', true)
1108
+ * const reason = interaction.options.getString('reason') ?? 'No reason given'
671
1109
  */
672
- disconnect(): void;
1110
+ readonly options: InteractionOptions;
1111
+ constructor(_raw: Interaction, _api: InteractionsAPI);
1112
+ /** `true` when triggered by a `/slash` command. */
1113
+ isSlashCommand(): boolean;
1114
+ /** `true` when triggered by a `!prefix` command. */
1115
+ isPrefixCommand(): boolean;
1116
+ /** `true` for both slash and prefix commands. */
1117
+ isCommand(): boolean;
1118
+ /** `true` when a button component was clicked. */
1119
+ isButton(): boolean;
1120
+ /** `true` when a select-menu option was chosen. */
1121
+ isSelectMenu(): boolean;
1122
+ /** `true` when the user submitted a modal form. */
1123
+ isModalSubmit(): boolean;
1124
+ /** `true` during autocomplete suggestion requests. */
1125
+ isAutocomplete(): boolean;
1126
+ /** `true` when triggered via a context-menu command. */
1127
+ isContextMenu(): boolean;
673
1128
  /**
674
- * Send a message via the WebSocket gateway (lower latency than HTTP).
675
- * Requires the `messages.write` scope.
1129
+ * Respond with a message.
1130
+ * Accepts a plain string or a full options object.
676
1131
  *
677
1132
  * @example
678
- * client.wsSend('channel-id', 'Hello from the gateway!')
1133
+ * await interaction.reply('Pong! 🏓')
1134
+ * await interaction.reply({ content: 'Done!', ephemeral: true })
1135
+ * await interaction.reply({ embed: new EmbedBuilder().setTitle('Stats').toJSON() })
679
1136
  */
680
- wsSend(channelId: string, content: string): void;
681
- /**
682
- * Start a typing indicator via WebSocket.
1137
+ reply(options: RespondInteractionOptions | string): Promise<Message | {
1138
+ ok: true;
1139
+ ephemeral: true;
1140
+ }>;
1141
+ /**
1142
+ * Respond with a message **only visible to the user** who triggered the interaction.
1143
+ *
1144
+ * @example
1145
+ * await interaction.replyEphemeral('Only you can see this!')
1146
+ */
1147
+ replyEphemeral(options: string | Omit<RespondInteractionOptions, 'ephemeral'>): Promise<{
1148
+ ok: true;
1149
+ ephemeral: true;
1150
+ }>;
1151
+ /**
1152
+ * Acknowledge the interaction without sending a response yet.
1153
+ * Shows a loading indicator to the user.
1154
+ * Follow up with `interaction.editReply()` when you're done.
1155
+ *
1156
+ * @example
1157
+ * await interaction.defer()
1158
+ * const data = await fetchSomeSlow()
1159
+ * await interaction.editReply({ content: `Result: ${data}` })
1160
+ */
1161
+ defer(): Promise<{
1162
+ ok: true;
1163
+ }>;
1164
+ /**
1165
+ * Edit the previous reply (e.g. after `defer()`).
1166
+ *
1167
+ * @example
1168
+ * await interaction.defer()
1169
+ * await interaction.editReply(`Done — processed ${count} items.`)
1170
+ */
1171
+ editReply(options: RespondInteractionOptions | string): Promise<Message | {
1172
+ ok: true;
1173
+ ephemeral: true;
1174
+ }>;
1175
+ /**
1176
+ * Open a **modal dialog** and return the user's submission as a new `NovaInteraction`.
1177
+ * Returns `null` if the user closes the modal or the timeout expires (default: 5 min).
1178
+ *
1179
+ * **This is the recommended way to open modals.**
1180
+ *
1181
+ * @example
1182
+ * const submitted = await interaction.openModal(
1183
+ * new ModalBuilder()
1184
+ * .setTitle('Submit a report')
1185
+ * .setCustomId('report_modal')
1186
+ * .addField(
1187
+ * new TextInputBuilder()
1188
+ * .setCustomId('reason')
1189
+ * .setLabel('Reason')
1190
+ * .setStyle('paragraph')
1191
+ * .setRequired(true)
1192
+ * )
1193
+ * )
1194
+ *
1195
+ * if (!submitted) return // user dismissed or timed out
1196
+ *
1197
+ * const reason = submitted.modalData.reason
1198
+ * await submitted.replyEphemeral(`Report received: ${reason}`)
1199
+ */
1200
+ openModal(modal: ModalBuilder | BotModalDefinition, options?: {
1201
+ timeout?: number;
1202
+ }): Promise<NovaInteraction | null>;
1203
+ /** Returns the raw interaction data from the gateway. */
1204
+ toJSON(): Interaction;
1205
+ }
1206
+
1207
+ /**
1208
+ * A rich wrapper around a raw `Message` with convenience methods.
1209
+ *
1210
+ * Returned by `client.on('messageCreate', ...)` and from message fetch calls.
1211
+ *
1212
+ * @example
1213
+ * client.on('messageCreate', async (msg) => {
1214
+ * if (msg.content.toLowerCase() === '!ping') {
1215
+ * await msg.reply('Pong! 🏓')
1216
+ * await msg.react('🏓')
1217
+ * }
1218
+ * })
1219
+ */
1220
+ declare class NovaMessage {
1221
+ /** The raw message ID. */
1222
+ readonly id: string;
1223
+ /** The message text content. */
1224
+ readonly content: string;
1225
+ /** The channel this message was sent in. */
1226
+ readonly channelId: string;
1227
+ /** The author of the message. */
1228
+ readonly author: Message['author'];
1229
+ /** Embed attached to this message, if any. */
1230
+ readonly embed: Embed | null;
1231
+ /** Interactive components (buttons, selects) attached to this message. */
1232
+ readonly components: MessageComponent[];
1233
+ /** The message this is replying to, if any. */
1234
+ readonly replyToId: string | null;
1235
+ /** Uploaded file attachments. */
1236
+ readonly attachments: Attachment[];
1237
+ /** Reactions on this message. */
1238
+ readonly reactions: Reaction[];
1239
+ /** When the message was sent. */
1240
+ readonly createdAt: Date;
1241
+ /** When the message was last edited, or `null`. */
1242
+ readonly editedAt: Date | null;
1243
+ private readonly _messages;
1244
+ private readonly _reactions;
1245
+ constructor(raw: Message, messages: MessagesAPI, reactions: ReactionsAPI);
1246
+ /** Returns true if the message was sent by a bot. */
1247
+ isFromBot(): boolean;
1248
+ /** Returns true if the message has an embed. */
1249
+ hasEmbed(): boolean;
1250
+ /** Returns true if the message has interactive components. */
1251
+ hasComponents(): boolean;
1252
+ /** Returns true if the message has been edited. */
1253
+ isEdited(): boolean;
1254
+ /**
1255
+ * Add a reaction to this message.
1256
+ *
1257
+ * @example
1258
+ * await msg.react('👍')
1259
+ * await msg.react('🎉')
1260
+ */
1261
+ react(emoji: string): Promise<{
1262
+ ok: true;
1263
+ }>;
1264
+ /**
1265
+ * Remove the bot's reaction from this message.
1266
+ *
1267
+ * @example
1268
+ * await msg.removeReaction('👍')
1269
+ */
1270
+ removeReaction(emoji: string): Promise<{
1271
+ ok: true;
1272
+ }>;
1273
+ /**
1274
+ * Reply to this message.
1275
+ *
1276
+ * @example
1277
+ * await msg.reply('Got it!')
1278
+ * await msg.reply({ embed: new EmbedBuilder().setTitle('Result').toJSON() })
1279
+ */
1280
+ reply(options: string | Omit<SendMessageOptions, 'replyToId'>): Promise<NovaMessage>;
1281
+ /**
1282
+ * Edit this message.
1283
+ * Only works if the bot is the author.
1284
+ *
1285
+ * @example
1286
+ * await msg.edit('Updated content')
1287
+ * await msg.edit({ content: 'Updated', embed: { title: 'New embed' } })
1288
+ */
1289
+ edit(options: string | EditMessageOptions): Promise<NovaMessage>;
1290
+ /**
1291
+ * Delete this message.
1292
+ * Only works if the bot is the author.
1293
+ *
1294
+ * @example
1295
+ * await msg.delete()
1296
+ */
1297
+ delete(): Promise<{
1298
+ ok: true;
1299
+ }>;
1300
+ /**
1301
+ * Pin this message in its channel.
1302
+ *
1303
+ * @example
1304
+ * await msg.pin()
1305
+ */
1306
+ pin(): Promise<{
1307
+ ok: true;
1308
+ }>;
1309
+ /**
1310
+ * Unpin this message.
1311
+ *
1312
+ * @example
1313
+ * await msg.unpin()
1314
+ */
1315
+ unpin(): Promise<{
1316
+ ok: true;
1317
+ }>;
1318
+ /**
1319
+ * Re-fetch the latest version of this message from the server.
1320
+ *
1321
+ * @example
1322
+ * const fresh = await msg.fetch()
1323
+ */
1324
+ fetch(): Promise<NovaMessage>;
1325
+ /**
1326
+ * Get a URL to this message (deep link).
1327
+ */
1328
+ get url(): string;
1329
+ /**
1330
+ * Return the raw message object.
1331
+ */
1332
+ toJSON(): Message;
1333
+ toString(): string;
1334
+ }
1335
+
1336
+ interface NovaClientEvents {
1337
+ /** Fired when the bot connects and is identified by the gateway. */
1338
+ ready: (bot: BotApplication) => void;
1339
+ /** Fired for every raw `bot:event` from the gateway. */
1340
+ event: (event: BotEvent) => void;
1341
+ /**
1342
+ * Fired when an interaction is received (slash command, button click, etc.).
1343
+ * Use `client.command()`, `client.button()`, or `client.selectMenu()` for
1344
+ * convenient routing instead of handling everything here.
1345
+ */
1346
+ interactionCreate: (interaction: NovaInteraction) => void;
1347
+ /** Fired when the WebSocket connection drops. */
1348
+ disconnect: (reason: string) => void;
1349
+ /** Fired on gateway / API errors. */
1350
+ error: (err: Error | {
1351
+ code: number;
1352
+ message: string;
1353
+ }) => void;
1354
+ /** A new message was sent in a channel the bot can see. */
1355
+ messageCreate: (message: NovaMessage) => void;
1356
+ /** A message was edited. Returns the updated message. */
1357
+ messageUpdate: (message: NovaMessage) => void;
1358
+ /** A message was deleted. Returns partial data. */
1359
+ messageDelete: (data: {
1360
+ id: string;
1361
+ channelId: string;
1362
+ }) => void;
1363
+ /** A reaction was added to a message. */
1364
+ reactionAdd: (data: {
1365
+ messageId: string;
1366
+ channelId: string;
1367
+ userId: string;
1368
+ emoji: string;
1369
+ }) => void;
1370
+ /** A reaction was removed from a message. */
1371
+ reactionRemove: (data: {
1372
+ messageId: string;
1373
+ channelId: string;
1374
+ userId: string;
1375
+ emoji: string;
1376
+ }) => void;
1377
+ /** A member joined a server the bot is in. */
1378
+ memberAdd: (data: {
1379
+ serverId: string;
1380
+ userId: string;
1381
+ username: string;
1382
+ }) => void;
1383
+ /** A member left a server the bot is in. */
1384
+ memberRemove: (data: {
1385
+ serverId: string;
1386
+ userId: string;
1387
+ }) => void;
1388
+ /** A user started typing in a channel. */
1389
+ typingStart: (data: {
1390
+ channelId: string;
1391
+ userId: string;
1392
+ }) => void;
1393
+ /** A message was pinned. */
1394
+ messagePinned: (data: {
1395
+ messageId: string;
1396
+ channelId: string;
1397
+ pinnedBy: string;
1398
+ }) => void;
1399
+ }
1400
+ /**
1401
+ * The main Nova bot client.
1402
+ *
1403
+ * @example
1404
+ * import { NovaClient } from 'nova-bot-sdk'
1405
+ *
1406
+ * const client = new NovaClient({ token: 'nova_bot_...' })
1407
+ *
1408
+ * client.on('ready', (bot) => {
1409
+ * console.log(`Logged in as ${bot.botUser.username}`)
1410
+ * })
1411
+ *
1412
+ * client.on('interactionCreate', async (interaction) => {
1413
+ * if (interaction.commandName === 'ping') {
1414
+ * await client.interactions.respond(interaction.id, { content: 'Pong!' })
1415
+ * }
1416
+ * })
1417
+ *
1418
+ * await client.connect()
1419
+ */
1420
+ declare class NovaClient extends EventEmitter {
1421
+ /** The authenticated bot application. Available after `ready` fires. */
1422
+ botUser: BotApplication | null;
1423
+ /** Send, edit, delete and fetch messages. */
1424
+ readonly messages: MessagesAPI;
1425
+ /** Register and manage slash, prefix, and context menu commands. */
1426
+ readonly commands: CommandsAPI;
1427
+ /** List, kick and ban server members. */
1428
+ readonly members: MembersAPI;
1429
+ /** List servers the bot is a member of. */
1430
+ readonly servers: ServersAPI;
1431
+ /** Acknowledge and respond to interactions. */
1432
+ readonly interactions: InteractionsAPI;
1433
+ /** Query the bot's effective permissions within a server, channel, or role scope. */
1434
+ readonly permissions: PermissionsAPI;
1435
+ /** Create, edit, and delete channels; fetch pins and messages. */
1436
+ readonly channels: ChannelsAPI;
1437
+ /** Add, remove and fetch reactions on messages. */
1438
+ readonly reactions: ReactionsAPI;
1439
+ private socket;
1440
+ private readonly http;
1441
+ private readonly options;
1442
+ private _cronTimers;
1443
+ private readonly _commandHandlers;
1444
+ private readonly _buttonHandlers;
1445
+ private readonly _selectHandlers;
1446
+ constructor(options: NovaClientOptions);
1447
+ /**
1448
+ * Register a handler for a slash or prefix command by name.
1449
+ * Automatically routes `interactionCreate` events whose `commandName` matches.
1450
+ *
1451
+ * @example
1452
+ * client.command('ping', async (interaction) => {
1453
+ * await interaction.reply('Pong! 🏓')
1454
+ * })
1455
+ */
1456
+ command(name: string, handler: (interaction: NovaInteraction) => unknown): this;
1457
+ /**
1458
+ * Register a handler for a button by its `customId`.
1459
+ *
1460
+ * @example
1461
+ * client.button('confirm_delete', async (interaction) => {
1462
+ * await interaction.replyEphemeral('Deleted.')
1463
+ * })
1464
+ */
1465
+ button(customId: string, handler: (interaction: NovaInteraction) => unknown): this;
1466
+ /**
1467
+ * Register a handler for a select menu by its `customId`.
1468
+ *
1469
+ * @example
1470
+ * client.selectMenu('colour_pick', async (interaction) => {
1471
+ * const chosen = interaction.values[0]
1472
+ * await interaction.reply(`You picked: ${chosen}`)
1473
+ * })
1474
+ */
1475
+ selectMenu(customId: string, handler: (interaction: NovaInteraction) => unknown): this;
1476
+ /**
1477
+ * Connect to the Nova WebSocket gateway.
1478
+ * Resolves when the `ready` event is received.
1479
+ *
1480
+ * @example
1481
+ * await client.connect()
1482
+ */
1483
+ connect(): Promise<void>;
1484
+ /**
1485
+ * Register a recurring task that fires at a set interval.
1486
+ * All cron tasks are automatically cancelled on `disconnect()`.
1487
+ *
1488
+ * @param intervalMs - How often to run the task (in milliseconds).
1489
+ * @param fn - Async or sync function to call on each tick.
1490
+ * @returns A cancel function — call it to stop this specific task.
1491
+ *
1492
+ * @example
1493
+ * // Check for new announcements every 30 seconds
1494
+ * client.cron(30_000, async () => {
1495
+ * const messages = await client.messages.fetch(channelId, { limit: 5 })
1496
+ * // do something...
1497
+ * })
1498
+ */
1499
+ cron(intervalMs: number, fn: () => unknown): () => void;
1500
+ /**
1501
+ * Set the bot's presence status.
1502
+ * Broadcasts to all servers the bot is in via WebSocket.
1503
+ *
1504
+ * @example
1505
+ * client.setStatus('DND') // Do Not Disturb
1506
+ * client.setStatus('IDLE') // Away
1507
+ * client.setStatus('OFFLINE') // Appear offline
1508
+ * client.setStatus('ONLINE') // Back online
1509
+ */
1510
+ setStatus(status: BotStatus): void;
1511
+ /**
1512
+ * Disconnect from the gateway and clean up.
1513
+ */
1514
+ disconnect(): void;
1515
+ /**
1516
+ * Send a message via the WebSocket gateway (lower latency than HTTP).
1517
+ * Requires the `messages.write` scope.
1518
+ *
1519
+ * @example
1520
+ * client.wsSend('channel-id', 'Hello from the gateway!')
1521
+ */
1522
+ wsSend(channelId: string, content: string): void;
1523
+ /**
1524
+ * Start a typing indicator via WebSocket.
683
1525
  */
684
1526
  wsTypingStart(channelId: string): void;
685
1527
  /**
@@ -696,4 +1538,714 @@ declare class NovaClient extends EventEmitter {
696
1538
  emit(event: string | symbol, ...args: unknown[]): boolean;
697
1539
  }
698
1540
 
699
- export { type Attachment, type BotApplication, type BotEvent, type BotEventType, type BotModalDefinition, type BotModalField, type BotPermissionRecord, type BotUser, CommandsAPI, type ContextCommandDefinition, type EditMessageOptions, type Embed, type EmbedField, type FetchMembersOptions, type FetchMessagesOptions, HttpClient, type Interaction, type InteractionType, InteractionsAPI, type Member, MembersAPI, type Message, type MessageComponent, MessagesAPI, NovaClient, type NovaClientEvents, type NovaClientOptions, type NovaServer, PermissionsAPI, type PermissionsQueryOptions, type PermissionsResult, type PollInteractionsOptions, type PrefixCommandDefinition, type Reaction, type RegisteredContextCommand, type RegisteredPrefixCommand, type RegisteredSlashCommand, type RespondInteractionOptions, type SendMessageOptions, ServersAPI, type SlashCommandDefinition, type SlashCommandOption };
1541
+ /**
1542
+ * Fluent builder for bot message embeds.
1543
+ *
1544
+ * @example
1545
+ * const embed = new EmbedBuilder()
1546
+ * .setTitle('Server Stats')
1547
+ * .setDescription('Here are the latest numbers.')
1548
+ * .setColor('#5865F2')
1549
+ * .addField('Members', '1 234', true)
1550
+ * .addField('Messages today', '567', true)
1551
+ * .setFooter('Nova Bot')
1552
+ * .setTimestamp()
1553
+ *
1554
+ * await client.messages.send(channelId, { embed })
1555
+ */
1556
+ declare class EmbedBuilder {
1557
+ private readonly _data;
1558
+ /** Set the embed title (shown in bold at the top). */
1559
+ setTitle(title: string): this;
1560
+ /** Set the main description text (supports markdown). */
1561
+ setDescription(description: string): this;
1562
+ /**
1563
+ * Set the accent colour.
1564
+ * @param color Hex string — e.g. `'#5865F2'` or `'5865F2'`
1565
+ */
1566
+ setColor(color: string): this;
1567
+ /** Make the title a clickable hyperlink. */
1568
+ setUrl(url: string): this;
1569
+ /** Small image shown in the top-right corner. */
1570
+ setThumbnail(url: string): this;
1571
+ /** Large image shown below the fields. */
1572
+ setImage(url: string): this;
1573
+ /** Footer text shown at the very bottom of the embed. */
1574
+ setFooter(text: string): this;
1575
+ /**
1576
+ * Add a timestamp to the footer line.
1577
+ * @param date Defaults to `new Date()`.
1578
+ */
1579
+ setTimestamp(date?: Date | number): this;
1580
+ /** Author name + optional icon shown above the title. */
1581
+ setAuthor(author: {
1582
+ name: string;
1583
+ iconUrl?: string;
1584
+ }): this;
1585
+ /**
1586
+ * Add a single field.
1587
+ * @param inline Pass `true` to render this field side-by-side with adjacent inline fields.
1588
+ */
1589
+ addField(name: string, value: string, inline?: boolean): this;
1590
+ /** Add multiple fields at once. */
1591
+ addFields(...fields: EmbedField[]): this;
1592
+ /** Replace all fields. */
1593
+ setFields(fields: EmbedField[]): this;
1594
+ /** Serialise to a plain `Embed` object you can pass to any API call. */
1595
+ toJSON(): Embed;
1596
+ }
1597
+
1598
+ type ButtonStyle = 'primary' | 'secondary' | 'success' | 'danger' | 'link';
1599
+ /**
1600
+ * Fluent builder for button components.
1601
+ *
1602
+ * @example
1603
+ * const row = new ActionRowBuilder()
1604
+ * .addButton(
1605
+ * new ButtonBuilder()
1606
+ * .setCustomId('confirm')
1607
+ * .setLabel('Confirm')
1608
+ * .setStyle('success')
1609
+ * )
1610
+ * .addButton(
1611
+ * new ButtonBuilder()
1612
+ * .setCustomId('cancel')
1613
+ * .setLabel('Cancel')
1614
+ * .setStyle('danger')
1615
+ * )
1616
+ *
1617
+ * await client.messages.send(channelId, { content: 'Are you sure?', components: row.toJSON() })
1618
+ */
1619
+ declare class ButtonBuilder {
1620
+ private _customId;
1621
+ private _label;
1622
+ private _style;
1623
+ private _disabled;
1624
+ private _emoji?;
1625
+ private _url?;
1626
+ /** Custom ID returned in the `BUTTON_CLICK` interaction. Not required for `link` style buttons. */
1627
+ setCustomId(customId: string): this;
1628
+ /** Text displayed on the button. */
1629
+ setLabel(label: string): this;
1630
+ /**
1631
+ * Visual style:
1632
+ * - `primary` — blurple / brand colour
1633
+ * - `secondary` — grey
1634
+ * - `success` — green
1635
+ * - `danger` — red
1636
+ * - `link` — grey, navigates to a URL instead of creating an interaction
1637
+ */
1638
+ setStyle(style: ButtonStyle): this;
1639
+ /** Emoji shown to the left of the label (unicode or custom e.g. `'👋'`). */
1640
+ setEmoji(emoji: string): this;
1641
+ /**
1642
+ * URL for `link` style buttons.
1643
+ * Sets the style to `'link'` automatically.
1644
+ */
1645
+ setUrl(url: string): this;
1646
+ /** Prevent users from clicking the button. */
1647
+ setDisabled(disabled?: boolean): this;
1648
+ toJSON(): MessageComponent;
1649
+ }
1650
+
1651
+ interface SelectMenuOption {
1652
+ /** Visible label shown to the user. */
1653
+ label: string;
1654
+ /** Value returned in the interaction's `values` array. */
1655
+ value: string;
1656
+ /** Optional description shown below the label. */
1657
+ description?: string;
1658
+ }
1659
+ /**
1660
+ * Fluent builder for select-menu (dropdown) components.
1661
+ *
1662
+ * @example
1663
+ * const menu = new SelectMenuBuilder()
1664
+ * .setCustomId('colour_pick')
1665
+ * .setPlaceholder('Pick a colour…')
1666
+ * .addOption({ label: 'Red', value: 'red' })
1667
+ * .addOption({ label: 'Green', value: 'green' })
1668
+ * .addOption({ label: 'Blue', value: 'blue' })
1669
+ *
1670
+ * await client.messages.send(channelId, { content: 'Choose:', components: [menu.toJSON()] })
1671
+ */
1672
+ declare class SelectMenuBuilder {
1673
+ private _customId;
1674
+ private _placeholder?;
1675
+ private _disabled;
1676
+ private readonly _options;
1677
+ /** Custom ID returned in the `SELECT_MENU` interaction. */
1678
+ setCustomId(customId: string): this;
1679
+ /** Greyed-out hint shown when nothing is selected. */
1680
+ setPlaceholder(placeholder: string): this;
1681
+ /** Prevent users from interacting with the menu. */
1682
+ setDisabled(disabled?: boolean): this;
1683
+ /** Add a single option. */
1684
+ addOption(option: SelectMenuOption): this;
1685
+ /** Add multiple options at once. */
1686
+ addOptions(...options: SelectMenuOption[]): this;
1687
+ /** Replace all options. */
1688
+ setOptions(options: SelectMenuOption[]): this;
1689
+ toJSON(): MessageComponent;
1690
+ }
1691
+
1692
+ type ComponentBuilder = ButtonBuilder | SelectMenuBuilder | {
1693
+ toJSON(): MessageComponent;
1694
+ };
1695
+ /**
1696
+ * Groups buttons (and/or a select menu) into a single row of components.
1697
+ *
1698
+ * @example
1699
+ * const row = new ActionRowBuilder()
1700
+ * .addComponent(new ButtonBuilder().setCustomId('yes').setLabel('Yes').setStyle('success'))
1701
+ * .addComponent(new ButtonBuilder().setCustomId('no').setLabel('No').setStyle('danger'))
1702
+ *
1703
+ * await interaction.reply({ content: 'Confirm?', components: row.toJSON() })
1704
+ */
1705
+ declare class ActionRowBuilder {
1706
+ private readonly _components;
1707
+ /** Add a single component (button or select menu). */
1708
+ addComponent(component: ComponentBuilder): this;
1709
+ /** Add multiple components at once. */
1710
+ addComponents(...components: ComponentBuilder[]): this;
1711
+ /**
1712
+ * Serialise to a flat `MessageComponent[]` array — the format accepted by all API calls.
1713
+ */
1714
+ toJSON(): MessageComponent[];
1715
+ }
1716
+
1717
+ /**
1718
+ * Fluent builder for a single slash command option (parameter).
1719
+ * Obtain one via the callback in `SlashCommandBuilder.addStringOption()` etc.
1720
+ */
1721
+ declare class SlashCommandOptionBuilder {
1722
+ private _data;
1723
+ constructor(type: SlashCommandOption['type']);
1724
+ /** Internal option name (lowercase, no spaces). Shown after `/command ` in the client UI. */
1725
+ setName(name: string): this;
1726
+ /** Short human-readable description shown in the command picker. */
1727
+ setDescription(description: string): this;
1728
+ /** Whether users must supply this option before sending the command. */
1729
+ setRequired(required?: boolean): this;
1730
+ /**
1731
+ * Restrict the option to specific values.
1732
+ * The picker will show these as autocomplete suggestions.
1733
+ */
1734
+ addChoice(name: string, value: string | number): this;
1735
+ /** Set all allowed choices at once. */
1736
+ setChoices(choices: Array<{
1737
+ name: string;
1738
+ value: string | number;
1739
+ }>): this;
1740
+ toJSON(): SlashCommandOption;
1741
+ }
1742
+ /**
1743
+ * Fluent builder for slash commands.
1744
+ *
1745
+ * @example
1746
+ * await client.commands.setSlash([
1747
+ * new SlashCommandBuilder()
1748
+ * .setName('ban')
1749
+ * .setDescription('Ban a member from this server')
1750
+ * .addUserOption((opt) =>
1751
+ * opt.setName('user').setDescription('Who to ban').setRequired(true)
1752
+ * )
1753
+ * .addStringOption((opt) =>
1754
+ * opt.setName('reason').setDescription('Reason for the ban')
1755
+ * )
1756
+ * .toJSON(),
1757
+ * ])
1758
+ */
1759
+ declare class SlashCommandBuilder {
1760
+ private _data;
1761
+ /** Command name — lowercase, no spaces (e.g. `'ban'`, `'server-info'`). */
1762
+ setName(name: string): this;
1763
+ /** Short description shown in the command picker UI. */
1764
+ setDescription(description: string): this;
1765
+ /** Add a text (string) option. */
1766
+ addStringOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1767
+ /** Add an integer (whole number) option. */
1768
+ addIntegerOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1769
+ /** Add a boolean (true/false) option. */
1770
+ addBooleanOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1771
+ /** Add a user-mention option (returns a user ID string). */
1772
+ addUserOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1773
+ /** Add a channel-mention option (returns a channel ID string). */
1774
+ addChannelOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1775
+ /** Add a role-mention option (returns a role ID string). */
1776
+ addRoleOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1777
+ toJSON(): SlashCommandDefinition;
1778
+ }
1779
+
1780
+ interface PollOption {
1781
+ label: string;
1782
+ emoji?: string;
1783
+ value?: string;
1784
+ }
1785
+ interface PollDefinition {
1786
+ question: string;
1787
+ options: PollOption[];
1788
+ allowMultiple?: boolean;
1789
+ duration?: number | null;
1790
+ anonymous?: boolean;
1791
+ }
1792
+ /**
1793
+ * Fluent builder for creating polls.
1794
+ *
1795
+ * @example
1796
+ * const poll = new PollBuilder()
1797
+ * .setQuestion('What is your favourite colour?')
1798
+ * .addOption({ label: 'Red', emoji: '🔴' })
1799
+ * .addOption({ label: 'Green', emoji: '🟢' })
1800
+ * .addOption({ label: 'Blue', emoji: '🔵' })
1801
+ * .setDuration(60 * 60 * 24) // 24 hours in seconds
1802
+ * .toJSON()
1803
+ *
1804
+ * await client.messages.send(channelId, { poll })
1805
+ */
1806
+ declare class PollBuilder {
1807
+ private _question;
1808
+ private _options;
1809
+ private _allowMultiple;
1810
+ private _duration;
1811
+ private _anonymous;
1812
+ /**
1813
+ * Set the poll question.
1814
+ *
1815
+ * @example
1816
+ * builder.setQuestion('Best programming language?')
1817
+ */
1818
+ setQuestion(question: string): this;
1819
+ /**
1820
+ * Add a single answer option.
1821
+ *
1822
+ * @example
1823
+ * builder.addOption({ label: 'TypeScript', emoji: '🔷' })
1824
+ */
1825
+ addOption(option: PollOption): this;
1826
+ /**
1827
+ * Add multiple answer options at once.
1828
+ *
1829
+ * @example
1830
+ * builder.addOptions([
1831
+ * { label: 'Yes', emoji: '✅' },
1832
+ * { label: 'No', emoji: '❌' },
1833
+ * ])
1834
+ */
1835
+ addOptions(options: PollOption[]): this;
1836
+ /**
1837
+ * Allow users to select more than one option.
1838
+ * Default: `false` (single choice).
1839
+ */
1840
+ setAllowMultiple(yes?: boolean): this;
1841
+ /**
1842
+ * Set the poll duration in **seconds**. Pass `null` to make it permanent.
1843
+ *
1844
+ * @example
1845
+ * builder.setDuration(60 * 60 * 24) // expires in 24 hours
1846
+ * builder.setDuration(null) // no expiry
1847
+ */
1848
+ setDuration(seconds: number | null): this;
1849
+ /**
1850
+ * Hide which users voted for which option.
1851
+ * Default: `false` (votes are public).
1852
+ */
1853
+ setAnonymous(yes?: boolean): this;
1854
+ /** Build the poll definition object. */
1855
+ toJSON(): PollDefinition;
1856
+ }
1857
+
1858
+ /**
1859
+ * Fluent builder for composing messages.
1860
+ * Replaces passing raw option objects everywhere.
1861
+ *
1862
+ * @example
1863
+ * const msg = new MessageBuilder()
1864
+ * .setContent('Here are your stats:')
1865
+ * .setEmbed(
1866
+ * new EmbedBuilder()
1867
+ * .setTitle('📊 Stats')
1868
+ * .addField('Messages', '1 234', true)
1869
+ * .addField('Reactions', '567', true)
1870
+ * .setColor('#5865F2')
1871
+ * )
1872
+ * .addRow(
1873
+ * new ActionRowBuilder()
1874
+ * .addComponent(new ButtonBuilder().setLabel('Refresh').setCustomId('refresh').setStyle('primary'))
1875
+ * )
1876
+ * .toJSON()
1877
+ *
1878
+ * await client.messages.send(channelId, msg)
1879
+ */
1880
+ declare class MessageBuilder {
1881
+ private _content;
1882
+ private _embed;
1883
+ private _components;
1884
+ private _replyToId;
1885
+ private _poll;
1886
+ /**
1887
+ * Set the text content of the message.
1888
+ *
1889
+ * @example
1890
+ * builder.setContent('Hello world!')
1891
+ */
1892
+ setContent(content: string): this;
1893
+ /**
1894
+ * Attach an embed. Accepts an `EmbedBuilder` or raw `Embed` object.
1895
+ *
1896
+ * @example
1897
+ * builder.setEmbed(new EmbedBuilder().setTitle('Result'))
1898
+ * builder.setEmbed({ title: 'Result', color: '#57F287' })
1899
+ */
1900
+ setEmbed(embed: EmbedBuilder | Embed): this;
1901
+ /**
1902
+ * Remove any embed from this message.
1903
+ */
1904
+ clearEmbed(): this;
1905
+ /**
1906
+ * Add an `ActionRowBuilder` (or raw component array) as a component row.
1907
+ *
1908
+ * @example
1909
+ * builder.addRow(
1910
+ * new ActionRowBuilder().addComponent(btn1).addComponent(btn2)
1911
+ * )
1912
+ */
1913
+ addRow(row: ActionRowBuilder | MessageComponent[]): this;
1914
+ /**
1915
+ * Replace all existing component rows.
1916
+ */
1917
+ setComponents(components: MessageComponent[]): this;
1918
+ /**
1919
+ * Set the message this is replying to.
1920
+ *
1921
+ * @example
1922
+ * builder.setReplyTo(message.id)
1923
+ */
1924
+ setReplyTo(messageId: string): this;
1925
+ /**
1926
+ * Attach a poll to the message.
1927
+ * Accepts a `PollBuilder` or raw `PollDefinition`.
1928
+ *
1929
+ * @example
1930
+ * builder.setPoll(
1931
+ * new PollBuilder()
1932
+ * .setQuestion('Favourite language?')
1933
+ * .addOptions([{ label: 'TypeScript' }, { label: 'Python' }])
1934
+ * )
1935
+ */
1936
+ setPoll(poll: {
1937
+ toJSON: () => PollDefinition;
1938
+ } | PollDefinition): this;
1939
+ /**
1940
+ * Build the message options object, ready to pass to `client.messages.send()`.
1941
+ *
1942
+ * @throws if neither `content` nor `embed` nor `poll` is set.
1943
+ */
1944
+ toJSON(): SendMessageOptions & {
1945
+ poll?: PollDefinition;
1946
+ };
1947
+ }
1948
+
1949
+ /**
1950
+ * A supercharged Map with extra utility methods.
1951
+ * Used throughout the SDK for caches (messages, channels, members, etc.)
1952
+ *
1953
+ * Inspired by discord.js Collection — but with proper generics and async support.
1954
+ *
1955
+ * @example
1956
+ * const col = new Collection<string, User>()
1957
+ * col.set('u1', { id: 'u1', name: 'Alice' })
1958
+ * col.set('u2', { id: 'u2', name: 'Bob' })
1959
+ *
1960
+ * col.first() // { id: 'u1', name: 'Alice' }
1961
+ * col.find(u => u.name === 'Bob') // { id: 'u2', name: 'Bob' }
1962
+ * col.map(u => u.name) // ['Alice', 'Bob']
1963
+ */
1964
+ declare class Collection<K, V> extends Map<K, V> {
1965
+ /**
1966
+ * The first value stored (insertion order), or `undefined` if empty.
1967
+ */
1968
+ first(): V | undefined;
1969
+ /**
1970
+ * The first `n` values stored (insertion order).
1971
+ */
1972
+ firstN(n: number): V[];
1973
+ /**
1974
+ * The last value stored, or `undefined` if empty.
1975
+ */
1976
+ last(): V | undefined;
1977
+ /**
1978
+ * The last `n` values stored (insertion order).
1979
+ */
1980
+ lastN(n: number): V[];
1981
+ /**
1982
+ * Returns a random value from the collection, or `undefined` if empty.
1983
+ */
1984
+ random(): V | undefined;
1985
+ /**
1986
+ * Find the first value that passes the predicate.
1987
+ *
1988
+ * @example
1989
+ * const bot = members.find(m => m.user.isBot)
1990
+ */
1991
+ find(fn: (value: V, key: K, col: this) => boolean): V | undefined;
1992
+ /**
1993
+ * Find the key of the first value that passes the predicate.
1994
+ */
1995
+ findKey(fn: (value: V, key: K, col: this) => boolean): K | undefined;
1996
+ /**
1997
+ * Returns `true` if at least one value satisfies the predicate.
1998
+ */
1999
+ some(fn: (value: V, key: K, col: this) => boolean): boolean;
2000
+ /**
2001
+ * Returns `true` if every value satisfies the predicate.
2002
+ */
2003
+ every(fn: (value: V, key: K, col: this) => boolean): boolean;
2004
+ /**
2005
+ * Filter to a new Collection containing only values that pass the predicate.
2006
+ *
2007
+ * @example
2008
+ * const admins = members.filter(m => m.role === 'ADMIN')
2009
+ */
2010
+ filter(fn: (value: V, key: K, col: this) => boolean): Collection<K, V>;
2011
+ /**
2012
+ * Map each value to a new array.
2013
+ *
2014
+ * @example
2015
+ * const names = members.map(m => m.user.username)
2016
+ */
2017
+ map<T>(fn: (value: V, key: K, col: this) => T): T[];
2018
+ /**
2019
+ * Map to a new Collection with transformed values.
2020
+ *
2021
+ * @example
2022
+ * const names = channels.mapValues(c => c.name.toUpperCase())
2023
+ */
2024
+ mapValues<T>(fn: (value: V, key: K, col: this) => T): Collection<K, T>;
2025
+ /**
2026
+ * Reduce the collection to a single value.
2027
+ *
2028
+ * @example
2029
+ * const totalReactions = messages.reduce((sum, m) => sum + m.reactions.length, 0)
2030
+ */
2031
+ reduce<T>(fn: (accumulator: T, value: V, key: K, col: this) => T, initial: T): T;
2032
+ /**
2033
+ * Returns values as an array (insertion order).
2034
+ */
2035
+ toArray(): V[];
2036
+ /**
2037
+ * Returns keys as an array (insertion order).
2038
+ */
2039
+ keyArray(): K[];
2040
+ /**
2041
+ * Sort and return a new Collection.
2042
+ * Callback works like `Array.prototype.sort`.
2043
+ *
2044
+ * @example
2045
+ * const sorted = channels.sort((a, b) => a.position - b.position)
2046
+ */
2047
+ sort(fn?: (a: V, b: V, ak: K, bk: K) => number): Collection<K, V>;
2048
+ /**
2049
+ * Split into two Collections based on a predicate.
2050
+ * Returns `[passing, failing]`.
2051
+ *
2052
+ * @example
2053
+ * const [bots, humans] = members.partition(m => m.user.isBot)
2054
+ */
2055
+ partition(fn: (value: V, key: K, col: this) => boolean): [Collection<K, V>, Collection<K, V>];
2056
+ /**
2057
+ * Merge this collection with one or more others.
2058
+ * Later collections overwrite duplicate keys.
2059
+ */
2060
+ merge(...others: ReadonlyMap<K, V>[]): Collection<K, V>;
2061
+ /**
2062
+ * Serialize to a plain `Record` (requires string keys).
2063
+ */
2064
+ toJSON(): Record<string, V>;
2065
+ toString(): string;
2066
+ }
2067
+
2068
+ /**
2069
+ * Per-user cooldown manager.
2070
+ * Prevents commands from being spammed by individual users.
2071
+ *
2072
+ * @example
2073
+ * // Create once per command
2074
+ * const cooldown = new Cooldown(5_000) // 5 second cooldown
2075
+ *
2076
+ * client.command('daily', async (interaction) => {
2077
+ * const remaining = cooldown.check(interaction.userId)
2078
+ * if (remaining > 0) {
2079
+ * return interaction.replyEphemeral(
2080
+ * `⏳ Wait **${Cooldown.format(remaining)}** before using this command again.`
2081
+ * )
2082
+ * }
2083
+ * cooldown.use(interaction.userId)
2084
+ * await interaction.reply('🎁 Daily reward collected!')
2085
+ * })
2086
+ */
2087
+ declare class Cooldown {
2088
+ private readonly _durations;
2089
+ private readonly _last;
2090
+ private readonly _defaultMs;
2091
+ /**
2092
+ * @param durationMs - Default cooldown duration in milliseconds.
2093
+ */
2094
+ constructor(durationMs: number);
2095
+ /**
2096
+ * Check remaining cooldown for a user.
2097
+ * Returns `0` if they are not on cooldown (free to proceed),
2098
+ * or the **milliseconds remaining** if they are on cooldown.
2099
+ *
2100
+ * @example
2101
+ * const ms = cooldown.check(userId)
2102
+ * if (ms > 0) return interaction.replyEphemeral(`Wait ${Cooldown.format(ms)}!`)
2103
+ */
2104
+ check(userId: string): number;
2105
+ /**
2106
+ * Mark a user as having just used the command, starting their cooldown.
2107
+ * Optionally override the cooldown duration for this specific user.
2108
+ *
2109
+ * @example
2110
+ * cooldown.use(userId) // uses default duration
2111
+ * cooldown.use(userId, 10_000) // 10 second cooldown for this use
2112
+ */
2113
+ use(userId: string, durationMs?: number): void;
2114
+ /**
2115
+ * Immediately reset (clear) the cooldown for a user.
2116
+ *
2117
+ * @example
2118
+ * cooldown.reset(userId) // user can use the command again immediately
2119
+ */
2120
+ reset(userId: string): void;
2121
+ /**
2122
+ * Reset all active cooldowns.
2123
+ */
2124
+ resetAll(): void;
2125
+ /**
2126
+ * Returns a list of all users currently on cooldown.
2127
+ *
2128
+ * @example
2129
+ * const active = cooldown.activeCooldowns()
2130
+ * // [{ userId: 'abc', remainingMs: 3200 }, ...]
2131
+ */
2132
+ activeCooldowns(): Array<{
2133
+ userId: string;
2134
+ remainingMs: number;
2135
+ }>;
2136
+ /**
2137
+ * Format milliseconds into a human-readable string.
2138
+ *
2139
+ * @example
2140
+ * Cooldown.format(3_600_000) // '1h 0m 0s'
2141
+ * Cooldown.format(90_000) // '1m 30s'
2142
+ * Cooldown.format(4_000) // '4s'
2143
+ */
2144
+ static format(ms: number): string;
2145
+ }
2146
+ /**
2147
+ * Manages multiple named cooldowns in one place.
2148
+ * Ideal when you have many commands each with their own cooldown.
2149
+ *
2150
+ * @example
2151
+ * const cooldowns = new CooldownManager()
2152
+ *
2153
+ * client.command('ping', async (interaction) => {
2154
+ * const remaining = cooldowns.check('ping', interaction.userId, 3_000)
2155
+ * if (remaining > 0) return interaction.replyEphemeral(`Wait ${Cooldown.format(remaining)}!`)
2156
+ * await interaction.reply('Pong!')
2157
+ * })
2158
+ */
2159
+ declare class CooldownManager {
2160
+ private readonly _map;
2161
+ /**
2162
+ * Get or create a named cooldown.
2163
+ */
2164
+ get(name: string, durationMs?: number): Cooldown;
2165
+ /**
2166
+ * Check a named cooldown for a user.
2167
+ * Creates the cooldown if it doesn't exist yet.
2168
+ * Returns `0` if free, or remaining ms if on cooldown.
2169
+ *
2170
+ * @example
2171
+ * const ms = cooldowns.check('ban', userId, 30_000)
2172
+ */
2173
+ check(name: string, userId: string, durationMs?: number): number;
2174
+ /**
2175
+ * Mark a user as having used a named command.
2176
+ *
2177
+ * @example
2178
+ * cooldowns.use('ban', userId, 30_000)
2179
+ */
2180
+ use(name: string, userId: string, durationMs?: number): void;
2181
+ /**
2182
+ * Reset a user's cooldown on a named command.
2183
+ */
2184
+ reset(name: string, userId: string): void;
2185
+ /**
2186
+ * Reset all cooldowns for all commands.
2187
+ */
2188
+ resetAll(): void;
2189
+ }
2190
+
2191
+ /**
2192
+ * Coloured, timestamped logger for Nova bots.
2193
+ * Prefix every log with a consistent `[BotName]` tag.
2194
+ *
2195
+ * @example
2196
+ * const log = new Logger('MyBot')
2197
+ * log.info('Ready!') // [MyBot] [INFO] Ready!
2198
+ * log.warn('Rate limited') // [MyBot] [WARN] Rate limited
2199
+ * log.error('DB timed out') // [MyBot] [ERROR] DB timed out
2200
+ * log.debug('Payload:', data) // [MyBot] [DEBUG] Payload: { ... }
2201
+ * log.success('Command registered') // [MyBot] [OK] Command registered
2202
+ * log.command('ping', userId) // [MyBot] [CMD] ping ← userId
2203
+ * log.event('messageCreate') // [MyBot] [EVT] messageCreate
2204
+ */
2205
+ declare class Logger {
2206
+ private readonly _prefix;
2207
+ private _debug;
2208
+ /**
2209
+ * @param name - Name shown in square brackets before every message.
2210
+ * @param enableDebug - When `false` (default), `.debug()` calls are silent.
2211
+ */
2212
+ constructor(name: string, enableDebug?: boolean);
2213
+ /** Enable or disable debug output at runtime. */
2214
+ setDebug(enabled: boolean): this;
2215
+ private _timestamp;
2216
+ private _fmt;
2217
+ /** General info message. */
2218
+ info(...args: unknown[]): void;
2219
+ /** Warning — something unexpected but non-fatal. */
2220
+ warn(...args: unknown[]): void;
2221
+ /** Error — something went wrong. */
2222
+ error(...args: unknown[]): void;
2223
+ /** Success / positive confirmation. */
2224
+ success(...args: unknown[]): void;
2225
+ /** Debug — only printed when `enableDebug` is true. */
2226
+ debug(...args: unknown[]): void;
2227
+ /**
2228
+ * Log an incoming command interaction.
2229
+ *
2230
+ * @example
2231
+ * log.command('ping', interaction.userId)
2232
+ * // [MyBot] [CMD] /ping ← user:abc123
2233
+ */
2234
+ command(name: string, userId: string): void;
2235
+ /**
2236
+ * Log a gateway event.
2237
+ *
2238
+ * @example
2239
+ * log.event('messageCreate')
2240
+ */
2241
+ event(type: string, extra?: string): void;
2242
+ /**
2243
+ * Log that a command handler threw an error.
2244
+ *
2245
+ * @example
2246
+ * log.commandError('ban', err)
2247
+ */
2248
+ commandError(name: string, err: unknown): void;
2249
+ }
2250
+
2251
+ export { ActionRowBuilder, type Attachment, type BanEntry, type BotApplication, type BotEvent, type BotEventType, type BotModalDefinition, type BotModalField, type BotPermissionRecord, type BotStatus, type BotUser, ButtonBuilder, type ButtonStyle, type Channel, type ChannelType, ChannelsAPI, Collection, CommandsAPI, type ContextCommandDefinition, Cooldown, CooldownManager, type CreateChannelOptions, type EditChannelOptions, type EditMessageOptions, type Embed, EmbedBuilder, type EmbedField, type FetchChannelsOptions, type FetchMembersOptions, type FetchMessagesOptions, HttpClient, type Interaction, InteractionOptions, type InteractionType, InteractionsAPI, type Invite, Logger, type Member, MembersAPI, type Message, MessageBuilder, type MessageComponent, MessagesAPI, ModalBuilder, NovaClient, type NovaClientEvents, type NovaClientOptions, NovaInteraction, NovaMessage, type NovaServer, PermissionsAPI, type PermissionsQueryOptions, type PermissionsResult, PollBuilder, type PollDefinition, type PollInteractionsOptions, type PollOption, type PrefixCommandDefinition, type Reaction, type ReactionDetail, ReactionsAPI, type RegisteredContextCommand, type RegisteredPrefixCommand, type RegisteredSlashCommand, type RespondInteractionOptions, type Role, SelectMenuBuilder, type SelectMenuOption, type SendMessageOptions, ServersAPI, SlashCommandBuilder, type SlashCommandDefinition, type SlashCommandOption, SlashCommandOptionBuilder, TextInputBuilder, type TextInputStyle };