novaapp-sdk 1.1.0 → 1.3.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
+ /** Slow-mode interval in seconds. `0` means disabled. */
72
+ slowMode: number;
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;
@@ -278,6 +331,22 @@ interface PollInteractionsOptions {
278
331
  limit?: number;
279
332
  since?: string;
280
333
  }
334
+ interface CreateChannelOptions {
335
+ name: string;
336
+ type?: ChannelType;
337
+ topic?: string;
338
+ position?: number;
339
+ slowMode?: number;
340
+ }
341
+ interface EditChannelOptions {
342
+ name?: string;
343
+ topic?: string;
344
+ position?: number;
345
+ slowMode?: number;
346
+ }
347
+ interface FetchChannelsOptions {
348
+ type?: ChannelType;
349
+ }
281
350
  /** A raw permission record stored for a specific scope (server, role, or channel). */
282
351
  interface BotPermissionRecord {
283
352
  id: string;
@@ -311,6 +380,29 @@ interface PermissionsQueryOptions {
311
380
  /** Narrow the scope to a specific role. */
312
381
  roleId?: string;
313
382
  }
383
+ /** A direct message sent between the bot and a user. */
384
+ interface DirectMessage {
385
+ id: string;
386
+ content: string;
387
+ /** The sender's user ID. */
388
+ fromId: string;
389
+ /** The recipient's user ID. */
390
+ toId: string;
391
+ editedAt: string | null;
392
+ createdAt: string;
393
+ }
394
+ /** Snapshot of a user's voice channel presence. */
395
+ interface VoiceState {
396
+ userId: string;
397
+ channelId: string;
398
+ serverId: string;
399
+ /** Whether the user's microphone is muted. */
400
+ muted: boolean;
401
+ /** Whether the user's audio output is deafened. */
402
+ deafened: boolean;
403
+ /** ISO timestamp when the user joined the voice channel. */
404
+ joinedAt: string;
405
+ }
314
406
  interface NovaClientOptions {
315
407
  /** Your bot token — starts with "nova_bot_" */
316
408
  token: string;
@@ -354,6 +446,64 @@ declare class MessagesAPI {
354
446
  typing(channelId: string): Promise<{
355
447
  ok: true;
356
448
  }>;
449
+ /**
450
+ * Fetch a single message by ID.
451
+ *
452
+ * @example
453
+ * const msg = await client.messages.fetchOne('message-id')
454
+ */
455
+ fetchOne(messageId: string): Promise<Message>;
456
+ /**
457
+ * Pin a message in its channel.
458
+ * The bot must have the `messages.manage` scope.
459
+ *
460
+ * @example
461
+ * await client.messages.pin('message-id')
462
+ */
463
+ pin(messageId: string): Promise<{
464
+ ok: true;
465
+ }>;
466
+ /**
467
+ * Unpin a message.
468
+ *
469
+ * @example
470
+ * await client.messages.unpin('message-id')
471
+ */
472
+ unpin(messageId: string): Promise<{
473
+ ok: true;
474
+ }>;
475
+ /**
476
+ * Fetch all pinned messages in a channel.
477
+ *
478
+ * @example
479
+ * const pins = await client.messages.fetchPinned('channel-id')
480
+ */
481
+ fetchPinned(channelId: string): Promise<Message[]>;
482
+ /**
483
+ * Add a reaction to a message.
484
+ *
485
+ * @example
486
+ * await client.messages.addReaction('message-id', '👍')
487
+ */
488
+ addReaction(messageId: string, emoji: string): Promise<{
489
+ ok: true;
490
+ }>;
491
+ /**
492
+ * Remove the bot's reaction from a message.
493
+ *
494
+ * @example
495
+ * await client.messages.removeReaction('message-id', '👍')
496
+ */
497
+ removeReaction(messageId: string, emoji: string): Promise<{
498
+ ok: true;
499
+ }>;
500
+ /**
501
+ * Fetch all reactions on a message.
502
+ *
503
+ * @example
504
+ * const reactions = await client.messages.fetchReactions('message-id')
505
+ */
506
+ fetchReactions(messageId: string): Promise<ReactionDetail[]>;
357
507
  }
358
508
 
359
509
  declare class CommandsAPI {
@@ -460,6 +610,56 @@ declare class MembersAPI {
460
610
  ban(serverId: string, userId: string, reason?: string): Promise<{
461
611
  ok: true;
462
612
  }>;
613
+ /**
614
+ * Unban a previously banned user.
615
+ * Requires the `members.ban` scope.
616
+ *
617
+ * @example
618
+ * await client.members.unban('server-id', 'user-id')
619
+ */
620
+ unban(serverId: string, userId: string): Promise<{
621
+ ok: true;
622
+ }>;
623
+ /**
624
+ * Fetch the ban list for a server.
625
+ * Requires the `members.ban` scope.
626
+ *
627
+ * @example
628
+ * const bans = await client.members.listBans('server-id')
629
+ * for (const ban of bans) {
630
+ * console.log(`${ban.username} — ${ban.reason ?? 'No reason'}`)
631
+ * }
632
+ */
633
+ listBans(serverId: string): Promise<BanEntry[]>;
634
+ /**
635
+ * Send a direct message to a user (DM).
636
+ * Requires the `messages.write` scope.
637
+ *
638
+ * @example
639
+ * await client.members.dm('user-id', { content: 'Hello!' })
640
+ * await client.members.dm('user-id', 'Hello from the bot!')
641
+ */
642
+ dm(userId: string, options: string | Omit<SendMessageOptions, 'replyToId'>): Promise<Message>;
643
+ /**
644
+ * Add a role to a member.
645
+ * Requires the `members.roles` scope.
646
+ *
647
+ * @example
648
+ * await client.members.addRole('server-id', 'user-id', 'role-id')
649
+ */
650
+ addRole(serverId: string, userId: string, roleId: string): Promise<{
651
+ ok: true;
652
+ }>;
653
+ /**
654
+ * Remove a role from a member.
655
+ * Requires the `members.roles` scope.
656
+ *
657
+ * @example
658
+ * await client.members.removeRole('server-id', 'user-id', 'role-id')
659
+ */
660
+ removeRole(serverId: string, userId: string, roleId: string): Promise<{
661
+ ok: true;
662
+ }>;
463
663
  }
464
664
 
465
665
  declare class ServersAPI {
@@ -473,6 +673,15 @@ declare class ServersAPI {
473
673
  * console.log(`Bot is in ${servers.length} servers`)
474
674
  */
475
675
  list(): Promise<NovaServer[]>;
676
+ /**
677
+ * Fetch all roles in a server.
678
+ * Results are sorted by position (lowest first).
679
+ *
680
+ * @example
681
+ * const roles = await client.servers.listRoles('server-id')
682
+ * const adminRole = roles.find(r => r.name === 'Admin')
683
+ */
684
+ listRoles(serverId: string): Promise<Role[]>;
476
685
  }
477
686
 
478
687
  declare class InteractionsAPI {
@@ -598,6 +807,144 @@ declare class PermissionsAPI {
598
807
  get(options: PermissionsQueryOptions): Promise<PermissionsResult>;
599
808
  }
600
809
 
810
+ declare class ChannelsAPI {
811
+ private readonly http;
812
+ constructor(http: HttpClient);
813
+ /**
814
+ * Fetch all channels in a server the bot is a member of.
815
+ *
816
+ * @example
817
+ * const channels = await client.channels.list('server-id')
818
+ * const textChannels = channels.filter(c => c.type === 'TEXT')
819
+ */
820
+ list(serverId: string): Promise<Channel[]>;
821
+ /**
822
+ * Fetch a single channel by ID.
823
+ *
824
+ * @example
825
+ * const channel = await client.channels.fetch('channel-id')
826
+ * console.log(channel.name, channel.type)
827
+ */
828
+ fetch(channelId: string): Promise<Channel>;
829
+ /**
830
+ * Create a new channel in a server.
831
+ * Requires the `channels.manage` scope.
832
+ *
833
+ * @example
834
+ * const channel = await client.channels.create('server-id', {
835
+ * name: 'announcements',
836
+ * type: 'ANNOUNCEMENT',
837
+ * topic: 'Official announcements only',
838
+ * })
839
+ */
840
+ create(serverId: string, options: CreateChannelOptions): Promise<Channel>;
841
+ /**
842
+ * Edit an existing channel.
843
+ * Requires the `channels.manage` scope.
844
+ *
845
+ * @example
846
+ * await client.channels.edit('channel-id', { topic: 'New topic!' })
847
+ */
848
+ edit(channelId: string, options: EditChannelOptions): Promise<Channel>;
849
+ /**
850
+ * Delete a channel.
851
+ * Requires the `channels.manage` scope.
852
+ *
853
+ * @example
854
+ * await client.channels.delete('channel-id')
855
+ */
856
+ delete(channelId: string): Promise<{
857
+ ok: true;
858
+ }>;
859
+ /**
860
+ * Fetch messages from a channel.
861
+ *
862
+ * @example
863
+ * const messages = await client.channels.fetchMessages('channel-id', { limit: 50 })
864
+ */
865
+ fetchMessages(channelId: string, options?: FetchMessagesOptions): Promise<Message[]>;
866
+ /**
867
+ * Fetch all pinned messages in a channel.
868
+ *
869
+ * @example
870
+ * const pins = await client.channels.fetchPins('channel-id')
871
+ */
872
+ fetchPins(channelId: string): Promise<Message[]>;
873
+ /**
874
+ * Send a typing indicator in a channel.
875
+ * Displayed to users for ~5 seconds.
876
+ *
877
+ * @example
878
+ * await client.channels.startTyping('channel-id')
879
+ */
880
+ startTyping(channelId: string): Promise<{
881
+ ok: true;
882
+ }>;
883
+ }
884
+
885
+ declare class ReactionsAPI {
886
+ private readonly http;
887
+ constructor(http: HttpClient);
888
+ /**
889
+ * Add a reaction to a message.
890
+ * Use a plain emoji character or a custom emoji ID.
891
+ *
892
+ * @example
893
+ * await client.reactions.add('message-id', '👍')
894
+ * await client.reactions.add('message-id', '🎉')
895
+ */
896
+ add(messageId: string, emoji: string): Promise<{
897
+ ok: true;
898
+ }>;
899
+ /**
900
+ * Remove the bot's reaction from a message.
901
+ *
902
+ * @example
903
+ * await client.reactions.remove('message-id', '👍')
904
+ */
905
+ remove(messageId: string, emoji: string): Promise<{
906
+ ok: true;
907
+ }>;
908
+ /**
909
+ * Remove all reactions from a message.
910
+ * Requires the `messages.manage` scope.
911
+ *
912
+ * @example
913
+ * await client.reactions.removeAll('message-id')
914
+ */
915
+ removeAll(messageId: string): Promise<{
916
+ ok: true;
917
+ }>;
918
+ /**
919
+ * Remove all reactions of a specific emoji from a message.
920
+ * Requires the `messages.manage` scope.
921
+ *
922
+ * @example
923
+ * await client.reactions.removeEmoji('message-id', '👍')
924
+ */
925
+ removeEmoji(messageId: string, emoji: string): Promise<{
926
+ ok: true;
927
+ }>;
928
+ /**
929
+ * Fetch all reactions on a message, broken down by emoji.
930
+ *
931
+ * @example
932
+ * const reactions = await client.reactions.fetch('message-id')
933
+ * for (const r of reactions) {
934
+ * console.log(`${r.emoji} — ${r.count} reactions from ${r.users.map(u => u.username).join(', ')}`)
935
+ * }
936
+ */
937
+ fetch(messageId: string): Promise<ReactionDetail[]>;
938
+ /**
939
+ * Fetch reactions for a specific emoji on a message.
940
+ *
941
+ * @example
942
+ * const detail = await client.reactions.fetchEmoji('message-id', '👍')
943
+ * console.log(`${detail.count} thumbs ups`)
944
+ */
945
+ fetchEmoji(messageId: string, emoji: string): Promise<ReactionDetail>;
946
+ }
947
+
601
948
  type TextInputStyle = 'short' | 'paragraph';
602
949
  /**
603
950
  * Fluent builder for a single text-input field inside a modal.
@@ -878,284 +1225,792 @@ declare class NovaInteraction {
878
1225
  toJSON(): Interaction;
879
1226
  }
880
1227
 
881
- interface NovaClientEvents {
882
- /** Fired when the bot connects and is identified by the gateway. */
883
- ready: (bot: BotApplication) => void;
884
- /** Fired for every raw `bot:event` from the gateway. */
885
- event: (event: BotEvent) => void;
886
- /**
887
- * Fired when an interaction is received (slash command, button click, etc.).
888
- * Use `client.command()`, `client.button()`, or `client.selectMenu()` for
889
- * convenient routing instead of handling everything here.
890
- */
891
- interactionCreate: (interaction: NovaInteraction) => void;
892
- /** Fired when the WebSocket connection drops. */
893
- disconnect: (reason: string) => void;
894
- /** Fired on gateway / API errors. */
895
- error: (err: Error | {
896
- code: number;
897
- message: string;
898
- }) => void;
899
- /** A new message was sent in a channel the bot can see. */
900
- messageCreate: (data: BotEvent['data']) => void;
901
- /** A message was edited. */
902
- messageUpdate: (data: BotEvent['data']) => void;
903
- /** A message was deleted. */
904
- messageDelete: (data: BotEvent['data']) => void;
905
- /** A reaction was added. */
906
- reactionAdd: (data: BotEvent['data']) => void;
907
- /** A reaction was removed. */
908
- reactionRemove: (data: BotEvent['data']) => void;
909
- /** A member joined a server the bot is in. */
910
- memberAdd: (data: BotEvent['data']) => void;
911
- /** A member left a server the bot is in. */
912
- memberRemove: (data: BotEvent['data']) => void;
913
- /** A user started typing. */
914
- typingStart: (data: BotEvent['data']) => void;
915
- }
916
1228
  /**
917
- * The main Nova bot client.
1229
+ * A rich wrapper around a raw `Message` with convenience methods.
918
1230
  *
919
- * @example
920
- * import { NovaClient } from 'nova-bot-sdk'
921
- *
922
- * const client = new NovaClient({ token: 'nova_bot_...' })
923
- *
924
- * client.on('ready', (bot) => {
925
- * console.log(`Logged in as ${bot.botUser.username}`)
926
- * })
1231
+ * Returned by `client.on('messageCreate', ...)` and from message fetch calls.
927
1232
  *
928
- * client.on('interactionCreate', async (interaction) => {
929
- * if (interaction.commandName === 'ping') {
930
- * await client.interactions.respond(interaction.id, { content: 'Pong!' })
1233
+ * @example
1234
+ * client.on('messageCreate', async (msg) => {
1235
+ * if (msg.content.toLowerCase() === '!ping') {
1236
+ * await msg.reply('Pong! 🏓')
1237
+ * await msg.react('🏓')
931
1238
  * }
932
1239
  * })
933
- *
934
- * await client.connect()
935
1240
  */
936
- declare class NovaClient extends EventEmitter {
937
- /** The authenticated bot application. Available after `ready` fires. */
938
- botUser: BotApplication | null;
939
- /** Send, edit, delete and fetch messages. */
940
- readonly messages: MessagesAPI;
941
- /** Register and manage slash, prefix, and context menu commands. */
942
- readonly commands: CommandsAPI;
943
- /** List, kick and ban server members. */
944
- readonly members: MembersAPI;
945
- /** List servers the bot is a member of. */
946
- readonly servers: ServersAPI;
947
- /** Acknowledge and respond to interactions. */
948
- readonly interactions: InteractionsAPI;
949
- /** Query the bot's effective permissions within a server, channel, or role scope. */
950
- readonly permissions: PermissionsAPI;
951
- private socket;
952
- private readonly http;
953
- private readonly options;
954
- private readonly _commandHandlers;
955
- private readonly _buttonHandlers;
956
- private readonly _selectHandlers;
957
- constructor(options: NovaClientOptions);
1241
+ declare class NovaMessage {
1242
+ /** The raw message ID. */
1243
+ readonly id: string;
1244
+ /** The message text content. */
1245
+ readonly content: string;
1246
+ /** The channel this message was sent in. */
1247
+ readonly channelId: string;
1248
+ /** The author of the message. */
1249
+ readonly author: Message['author'];
1250
+ /** Embed attached to this message, if any. */
1251
+ readonly embed: Embed | null;
1252
+ /** Interactive components (buttons, selects) attached to this message. */
1253
+ readonly components: MessageComponent[];
1254
+ /** The message this is replying to, if any. */
1255
+ readonly replyToId: string | null;
1256
+ /** Uploaded file attachments. */
1257
+ readonly attachments: Attachment[];
1258
+ /** Reactions on this message. */
1259
+ readonly reactions: Reaction[];
1260
+ /** When the message was sent. */
1261
+ readonly createdAt: Date;
1262
+ /** When the message was last edited, or `null`. */
1263
+ readonly editedAt: Date | null;
1264
+ private readonly _messages;
1265
+ private readonly _reactions;
1266
+ constructor(raw: Message, messages: MessagesAPI, reactions: ReactionsAPI);
1267
+ /** Returns true if the message was sent by a bot. */
1268
+ isFromBot(): boolean;
1269
+ /** Returns true if the message has an embed. */
1270
+ hasEmbed(): boolean;
1271
+ /** Returns true if the message has interactive components. */
1272
+ hasComponents(): boolean;
1273
+ /** Returns true if the message has been edited. */
1274
+ isEdited(): boolean;
958
1275
  /**
959
- * Register a handler for a slash or prefix command by name.
960
- * Automatically routes `interactionCreate` events whose `commandName` matches.
1276
+ * Add a reaction to this message.
961
1277
  *
962
1278
  * @example
963
- * client.command('ping', async (interaction) => {
964
- * await interaction.reply('Pong! 🏓')
965
- * })
1279
+ * await msg.react('👍')
1280
+ * await msg.react('🎉')
966
1281
  */
967
- command(name: string, handler: (interaction: NovaInteraction) => unknown): this;
1282
+ react(emoji: string): Promise<{
1283
+ ok: true;
1284
+ }>;
968
1285
  /**
969
- * Register a handler for a button by its `customId`.
1286
+ * Remove the bot's reaction from this message.
970
1287
  *
971
1288
  * @example
972
- * client.button('confirm_delete', async (interaction) => {
973
- * await interaction.replyEphemeral('Deleted.')
974
- * })
1289
+ * await msg.removeReaction('👍')
975
1290
  */
976
- button(customId: string, handler: (interaction: NovaInteraction) => unknown): this;
1291
+ removeReaction(emoji: string): Promise<{
1292
+ ok: true;
1293
+ }>;
977
1294
  /**
978
- * Register a handler for a select menu by its `customId`.
1295
+ * Reply to this message.
979
1296
  *
980
1297
  * @example
981
- * client.selectMenu('colour_pick', async (interaction) => {
982
- * const chosen = interaction.values[0]
983
- * await interaction.reply(`You picked: ${chosen}`)
984
- * })
1298
+ * await msg.reply('Got it!')
1299
+ * await msg.reply({ embed: new EmbedBuilder().setTitle('Result').toJSON() })
985
1300
  */
986
- selectMenu(customId: string, handler: (interaction: NovaInteraction) => unknown): this;
1301
+ reply(options: string | Omit<SendMessageOptions, 'replyToId'>): Promise<NovaMessage>;
987
1302
  /**
988
- * Connect to the Nova WebSocket gateway.
989
- * Resolves when the `ready` event is received.
1303
+ * Edit this message.
1304
+ * Only works if the bot is the author.
990
1305
  *
991
1306
  * @example
992
- * await client.connect()
1307
+ * await msg.edit('Updated content')
1308
+ * await msg.edit({ content: 'Updated', embed: { title: 'New embed' } })
993
1309
  */
994
- connect(): Promise<void>;
1310
+ edit(options: string | EditMessageOptions): Promise<NovaMessage>;
995
1311
  /**
996
- * Disconnect from the gateway and clean up.
1312
+ * Delete this message.
1313
+ * Only works if the bot is the author.
1314
+ *
1315
+ * @example
1316
+ * await msg.delete()
997
1317
  */
998
- disconnect(): void;
1318
+ delete(): Promise<{
1319
+ ok: true;
1320
+ }>;
999
1321
  /**
1000
- * Send a message via the WebSocket gateway (lower latency than HTTP).
1001
- * Requires the `messages.write` scope.
1322
+ * Pin this message in its channel.
1002
1323
  *
1003
1324
  * @example
1004
- * client.wsSend('channel-id', 'Hello from the gateway!')
1325
+ * await msg.pin()
1005
1326
  */
1006
- wsSend(channelId: string, content: string): void;
1327
+ pin(): Promise<{
1328
+ ok: true;
1329
+ }>;
1007
1330
  /**
1008
- * Start a typing indicator via WebSocket.
1331
+ * Unpin this message.
1332
+ *
1333
+ * @example
1334
+ * await msg.unpin()
1009
1335
  */
1010
- wsTypingStart(channelId: string): void;
1336
+ unpin(): Promise<{
1337
+ ok: true;
1338
+ }>;
1011
1339
  /**
1012
- * Stop a typing indicator via WebSocket.
1340
+ * Re-fetch the latest version of this message from the server.
1341
+ *
1342
+ * @example
1343
+ * const fresh = await msg.fetch()
1013
1344
  */
1014
- wsTypingStop(channelId: string): void;
1015
- on<K extends keyof NovaClientEvents>(event: K, listener: NovaClientEvents[K]): this;
1016
- on(event: string | symbol, listener: (...args: unknown[]) => void): this;
1017
- once<K extends keyof NovaClientEvents>(event: K, listener: NovaClientEvents[K]): this;
1018
- once(event: string | symbol, listener: (...args: unknown[]) => void): this;
1019
- off<K extends keyof NovaClientEvents>(event: K, listener: NovaClientEvents[K]): this;
1020
- off(event: string | symbol, listener: (...args: unknown[]) => void): this;
1021
- emit<K extends keyof NovaClientEvents>(event: K, ...args: Parameters<NovaClientEvents[K]>): boolean;
1022
- emit(event: string | symbol, ...args: unknown[]): boolean;
1345
+ fetch(): Promise<NovaMessage>;
1346
+ /**
1347
+ * Get a URL to this message (deep link).
1348
+ */
1349
+ get url(): string;
1350
+ /**
1351
+ * Return the raw message object.
1352
+ */
1353
+ toJSON(): Message;
1354
+ toString(): string;
1023
1355
  }
1024
1356
 
1025
1357
  /**
1026
- * Fluent builder for bot message embeds.
1358
+ * A rich wrapper around a raw `Channel` with convenience methods.
1359
+ *
1360
+ * Returned by `client.fetchChannel()` and `client.fetchChannels()`.
1027
1361
  *
1028
1362
  * @example
1029
- * const embed = new EmbedBuilder()
1030
- * .setTitle('Server Stats')
1031
- * .setDescription('Here are the latest numbers.')
1032
- * .setColor('#5865F2')
1033
- * .addField('Members', '1 234', true)
1034
- * .addField('Messages today', '567', true)
1035
- * .setFooter('Nova Bot')
1036
- * .setTimestamp()
1363
+ * const channel = await client.fetchChannel('channel-id')
1037
1364
  *
1038
- * await client.messages.send(channelId, { embed })
1365
+ * await channel.send('Hello from the bot!')
1366
+ * await channel.edit({ topic: 'New topic' })
1367
+ * const messages = await channel.fetchMessages({ limit: 20 })
1039
1368
  */
1040
- declare class EmbedBuilder {
1041
- private readonly _data;
1042
- /** Set the embed title (shown in bold at the top). */
1043
- setTitle(title: string): this;
1044
- /** Set the main description text (supports markdown). */
1045
- setDescription(description: string): this;
1369
+ declare class NovaChannel {
1370
+ /** The channel's unique ID. */
1371
+ readonly id: string;
1372
+ /** The channel's name (without #). */
1373
+ readonly name: string;
1374
+ /** The channel type — `'TEXT'`, `'VOICE'`, `'ANNOUNCEMENT'`, `'FORUM'`, or `'STAGE'`. */
1375
+ readonly type: ChannelType;
1376
+ /** The ID of the server this channel belongs to. */
1377
+ readonly serverId: string | null;
1378
+ /** Optional topic / description text. */
1379
+ readonly topic: string | null;
1380
+ /** Sorting position (lower = higher in the list). */
1381
+ readonly position: number;
1382
+ /** Slow-mode interval in seconds (`0` = disabled). */
1383
+ readonly slowMode: number;
1384
+ /** When the channel was created. */
1385
+ readonly createdAt: Date;
1386
+ private readonly _channels;
1387
+ private readonly _messages;
1388
+ constructor(raw: Channel, channels: ChannelsAPI, messages: MessagesAPI);
1389
+ /** `true` for text channels. */
1390
+ isText(): boolean;
1391
+ /** `true` for voice channels. */
1392
+ isVoice(): boolean;
1393
+ /** `true` for announcement channels. */
1394
+ isAnnouncement(): boolean;
1395
+ /** `true` for forum channels. */
1396
+ isForum(): boolean;
1397
+ /** `true` for stage channels. */
1398
+ isStage(): boolean;
1399
+ /** `true` if slow-mode is enabled on this channel. */
1400
+ hasSlowMode(): boolean;
1046
1401
  /**
1047
- * Set the accent colour.
1048
- * @param color Hex string e.g. `'#5865F2'` or `'5865F2'`
1402
+ * Send a message to this channel.
1403
+ * Accepts a plain string or a full options object.
1404
+ *
1405
+ * @example
1406
+ * await channel.send('Hello!')
1407
+ * await channel.send({ content: 'Hi', embed: { title: 'Stats' } })
1049
1408
  */
1050
- setColor(color: string): this;
1051
- /** Make the title a clickable hyperlink. */
1052
- setUrl(url: string): this;
1053
- /** Small image shown in the top-right corner. */
1054
- setThumbnail(url: string): this;
1055
- /** Large image shown below the fields. */
1056
- setImage(url: string): this;
1057
- /** Footer text shown at the very bottom of the embed. */
1058
- setFooter(text: string): this;
1409
+ send(options: string | SendMessageOptions): Promise<Message>;
1059
1410
  /**
1060
- * Add a timestamp to the footer line.
1061
- * @param date Defaults to `new Date()`.
1411
+ * Fetch recent messages from this channel.
1412
+ *
1413
+ * @example
1414
+ * const messages = await channel.fetchMessages({ limit: 50 })
1062
1415
  */
1063
- setTimestamp(date?: Date | number): this;
1064
- /** Author name + optional icon shown above the title. */
1065
- setAuthor(author: {
1066
- name: string;
1067
- iconUrl?: string;
1068
- }): this;
1416
+ fetchMessages(options?: FetchMessagesOptions): Promise<Message[]>;
1069
1417
  /**
1070
- * Add a single field.
1071
- * @param inline Pass `true` to render this field side-by-side with adjacent inline fields.
1418
+ * Fetch all pinned messages in this channel.
1419
+ *
1420
+ * @example
1421
+ * const pins = await channel.fetchPins()
1072
1422
  */
1073
- addField(name: string, value: string, inline?: boolean): this;
1074
- /** Add multiple fields at once. */
1075
- addFields(...fields: EmbedField[]): this;
1076
- /** Replace all fields. */
1077
- setFields(fields: EmbedField[]): this;
1078
- /** Serialise to a plain `Embed` object you can pass to any API call. */
1079
- toJSON(): Embed;
1423
+ fetchPins(): Promise<Message[]>;
1424
+ /**
1425
+ * Start a typing indicator (shows "Bot is typing…" for ~5 seconds).
1426
+ *
1427
+ * @example
1428
+ * await channel.startTyping()
1429
+ */
1430
+ startTyping(): Promise<{
1431
+ ok: true;
1432
+ }>;
1433
+ /**
1434
+ * Edit this channel's properties.
1435
+ * Requires the `channels.manage` scope.
1436
+ *
1437
+ * @example
1438
+ * await channel.edit({ name: 'general-2', topic: 'The second general channel' })
1439
+ */
1440
+ edit(options: EditChannelOptions): Promise<NovaChannel>;
1441
+ /**
1442
+ * Delete this channel.
1443
+ * Requires the `channels.manage` scope.
1444
+ *
1445
+ * @example
1446
+ * await channel.delete()
1447
+ */
1448
+ delete(): Promise<{
1449
+ ok: true;
1450
+ }>;
1451
+ /**
1452
+ * Returns the channel as a mention-style string: `#name`.
1453
+ */
1454
+ toString(): string;
1455
+ /** Returns the raw channel data. */
1456
+ toJSON(): Channel;
1080
1457
  }
1081
1458
 
1082
- type ButtonStyle = 'primary' | 'secondary' | 'success' | 'danger' | 'link';
1083
1459
  /**
1084
- * Fluent builder for button components.
1460
+ * A rich wrapper around a raw `Member` with convenience methods.
1461
+ *
1462
+ * Returned by `client.fetchMember()` and `client.fetchMembers()`.
1085
1463
  *
1086
1464
  * @example
1087
- * const row = new ActionRowBuilder()
1088
- * .addButton(
1089
- * new ButtonBuilder()
1090
- * .setCustomId('confirm')
1091
- * .setLabel('Confirm')
1092
- * .setStyle('success')
1093
- * )
1094
- * .addButton(
1095
- * new ButtonBuilder()
1096
- * .setCustomId('cancel')
1097
- * .setLabel('Cancel')
1098
- * .setStyle('danger')
1099
- * )
1465
+ * const member = await client.fetchMember('server-id', 'user-id')
1100
1466
  *
1101
- * await client.messages.send(channelId, { content: 'Are you sure?', components: row.toJSON() })
1467
+ * if (member.isAdmin()) {
1468
+ * await member.dm('You have admin access.')
1469
+ * } else {
1470
+ * await member.kick()
1471
+ * }
1102
1472
  */
1103
- declare class ButtonBuilder {
1104
- private _customId;
1105
- private _label;
1106
- private _style;
1107
- private _disabled;
1108
- private _emoji?;
1109
- private _url?;
1110
- /** Custom ID returned in the `BUTTON_CLICK` interaction. Not required for `link` style buttons. */
1111
- setCustomId(customId: string): this;
1112
- /** Text displayed on the button. */
1113
- setLabel(label: string): this;
1473
+ declare class NovaMember {
1474
+ /** The user's unique ID. */
1475
+ readonly userId: string;
1476
+ /** The server this membership belongs to. */
1477
+ readonly serverId: string;
1478
+ /** The user's username (login handle). */
1479
+ readonly username: string;
1480
+ /** The user's display name. */
1481
+ readonly displayName: string;
1482
+ /** URL of the user's avatar, or `null`. */
1483
+ readonly avatar: string | null;
1484
+ /** The member's server role — `'OWNER'`, `'ADMIN'`, or `'MEMBER'`. */
1485
+ readonly role: 'OWNER' | 'ADMIN' | 'MEMBER';
1486
+ /** The user's current presence status. */
1487
+ readonly status: 'ONLINE' | 'IDLE' | 'DND' | 'OFFLINE';
1488
+ /** `true` if this member is a bot account. */
1489
+ readonly isBot: boolean;
1490
+ /** When this user joined the server. */
1491
+ readonly joinedAt: Date;
1492
+ private readonly _members;
1493
+ constructor(raw: Member, serverId: string, members: MembersAPI);
1494
+ /** `true` if this member is the server owner. */
1495
+ isOwner(): boolean;
1496
+ /** `true` if this member is an admin or the server owner. */
1497
+ isAdmin(): boolean;
1498
+ /** `true` if this member is a regular (non-privileged) member. */
1499
+ isRegularMember(): boolean;
1500
+ /** `true` if the user is currently online. */
1501
+ isOnline(): boolean;
1502
+ /** `true` if the user is idle / away. */
1503
+ isIdle(): boolean;
1504
+ /** `true` if the user is set to Do Not Disturb. */
1505
+ isDND(): boolean;
1506
+ /** `true` if the user appears offline. */
1507
+ isOffline(): boolean;
1114
1508
  /**
1115
- * Visual style:
1116
- * - `primary` blurple / brand colour
1117
- * - `secondary` — grey
1118
- * - `success` — green
1119
- * - `danger` — red
1120
- * - `link` — grey, navigates to a URL instead of creating an interaction
1509
+ * Kick this member from the server.
1510
+ * Bots cannot kick owners or admins (throws 403).
1511
+ *
1512
+ * @example
1513
+ * await member.kick()
1121
1514
  */
1122
- setStyle(style: ButtonStyle): this;
1123
- /** Emoji shown to the left of the label (unicode or custom e.g. `'👋'`). */
1124
- setEmoji(emoji: string): this;
1515
+ kick(): Promise<{
1516
+ ok: true;
1517
+ }>;
1125
1518
  /**
1126
- * URL for `link` style buttons.
1127
- * Sets the style to `'link'` automatically.
1519
+ * Ban this member from the server with an optional reason.
1520
+ * Bots cannot ban owners or admins (throws 403).
1521
+ *
1522
+ * @example
1523
+ * await member.ban('Repeated rule violations')
1128
1524
  */
1129
- setUrl(url: string): this;
1130
- /** Prevent users from clicking the button. */
1131
- setDisabled(disabled?: boolean): this;
1132
- toJSON(): MessageComponent;
1525
+ ban(reason?: string): Promise<{
1526
+ ok: true;
1527
+ }>;
1528
+ /**
1529
+ * Send this user a direct message.
1530
+ * Requires the `messages.write` scope.
1531
+ *
1532
+ * @example
1533
+ * await member.dm('Welcome to the server!')
1534
+ * await member.dm({ content: 'Hello', embed: { title: 'Rules' } })
1535
+ */
1536
+ dm(options: string | Omit<SendMessageOptions, 'replyToId'>): Promise<Message>;
1537
+ /**
1538
+ * Assign a custom role to this member.
1539
+ * Requires the `members.roles` scope.
1540
+ *
1541
+ * @example
1542
+ * await member.addRole('role-id')
1543
+ */
1544
+ addRole(roleId: string): Promise<{
1545
+ ok: true;
1546
+ }>;
1547
+ /**
1548
+ * Remove a custom role from this member.
1549
+ * Requires the `members.roles` scope.
1550
+ *
1551
+ * @example
1552
+ * await member.removeRole('role-id')
1553
+ */
1554
+ removeRole(roleId: string): Promise<{
1555
+ ok: true;
1556
+ }>;
1557
+ /**
1558
+ * Returns a mention-style string: `@displayName`.
1559
+ */
1560
+ toString(): string;
1561
+ /** Returns the raw member data. */
1562
+ toJSON(): Member;
1133
1563
  }
1134
1564
 
1135
- interface SelectMenuOption {
1136
- /** Visible label shown to the user. */
1137
- label: string;
1138
- /** Value returned in the interaction's `values` array. */
1139
- value: string;
1140
- /** Optional description shown below the label. */
1141
- description?: string;
1565
+ interface NovaClientEvents {
1566
+ /** Fired when the bot connects and is identified by the gateway. */
1567
+ ready: (bot: BotApplication) => void;
1568
+ /** Fired for every raw `bot:event` from the gateway. */
1569
+ event: (event: BotEvent) => void;
1570
+ /**
1571
+ * Fired when an interaction is received (slash command, button click, etc.).
1572
+ * Use `client.command()`, `client.button()`, or `client.selectMenu()` for
1573
+ * convenient routing instead of handling everything here.
1574
+ */
1575
+ interactionCreate: (interaction: NovaInteraction) => void;
1576
+ /** Fired when the WebSocket connection drops. */
1577
+ disconnect: (reason: string) => void;
1578
+ /** Fired on gateway / API errors. */
1579
+ error: (err: Error | {
1580
+ code: number;
1581
+ message: string;
1582
+ }) => void;
1583
+ /** A new message was sent in a channel the bot can see. */
1584
+ messageCreate: (message: NovaMessage) => void;
1585
+ /** A message was edited. Returns the updated message. */
1586
+ messageUpdate: (message: NovaMessage) => void;
1587
+ /** A message was deleted. Returns partial data. */
1588
+ messageDelete: (data: {
1589
+ id: string;
1590
+ channelId: string;
1591
+ }) => void;
1592
+ /** A reaction was added to a message. */
1593
+ reactionAdd: (data: {
1594
+ messageId: string;
1595
+ channelId: string;
1596
+ userId: string;
1597
+ emoji: string;
1598
+ }) => void;
1599
+ /** A reaction was removed from a message. */
1600
+ reactionRemove: (data: {
1601
+ messageId: string;
1602
+ channelId: string;
1603
+ userId: string;
1604
+ emoji: string;
1605
+ }) => void;
1606
+ /** A member joined a server the bot is in. */
1607
+ memberAdd: (data: {
1608
+ serverId: string;
1609
+ userId: string;
1610
+ username: string;
1611
+ }) => void;
1612
+ /** A member left a server the bot is in. */
1613
+ memberRemove: (data: {
1614
+ serverId: string;
1615
+ userId: string;
1616
+ }) => void;
1617
+ /** A user started typing in a channel. */
1618
+ typingStart: (data: {
1619
+ channelId: string;
1620
+ userId: string;
1621
+ }) => void;
1622
+ /** A message was pinned. */
1623
+ messagePinned: (data: {
1624
+ messageId: string;
1625
+ channelId: string;
1626
+ pinnedBy: string;
1627
+ }) => void;
1628
+ /** A channel was created in a server. */
1629
+ channelCreate: (channel: Channel) => void;
1630
+ /** A channel was updated. */
1631
+ channelUpdate: (channel: Channel) => void;
1632
+ /** A channel was deleted. */
1633
+ channelDelete: (data: {
1634
+ id: string;
1635
+ serverId: string;
1636
+ }) => void;
1637
+ /** A role was created in a server. */
1638
+ roleCreate: (data: {
1639
+ id: string;
1640
+ name: string;
1641
+ color: string | null;
1642
+ serverId: string;
1643
+ position: number;
1644
+ hoist: boolean;
1645
+ createdAt: string;
1646
+ }) => void;
1647
+ /** A role was deleted. */
1648
+ roleDelete: (data: {
1649
+ id: string;
1650
+ serverId: string;
1651
+ }) => void;
1652
+ /** A user joined a voice channel. */
1653
+ voiceJoin: (data: {
1654
+ userId: string;
1655
+ channelId: string;
1656
+ serverId: string;
1657
+ }) => void;
1658
+ /** A user left a voice channel. */
1659
+ voiceLeave: (data: {
1660
+ userId: string;
1661
+ channelId: string;
1662
+ serverId: string;
1663
+ }) => void;
1664
+ /** A member was banned from a server. */
1665
+ memberBanned: (data: {
1666
+ userId: string;
1667
+ serverId: string;
1668
+ moderatorId: string | null;
1669
+ reason: string | null;
1670
+ }) => void;
1671
+ /** A member was unbanned. */
1672
+ memberUnbanned: (data: {
1673
+ userId: string;
1674
+ serverId: string;
1675
+ }) => void;
1676
+ /** A member's profile data was updated. */
1677
+ memberUpdate: (data: {
1678
+ userId: string;
1679
+ serverId: string;
1680
+ }) => void;
1142
1681
  }
1143
1682
  /**
1144
- * Fluent builder for select-menu (dropdown) components.
1683
+ * The main Nova bot client.
1145
1684
  *
1146
1685
  * @example
1147
- * const menu = new SelectMenuBuilder()
1148
- * .setCustomId('colour_pick')
1149
- * .setPlaceholder('Pick a colour…')
1150
- * .addOption({ label: 'Red', value: 'red' })
1151
- * .addOption({ label: 'Green', value: 'green' })
1152
- * .addOption({ label: 'Blue', value: 'blue' })
1686
+ * import { NovaClient } from 'nova-bot-sdk'
1153
1687
  *
1154
- * await client.messages.send(channelId, { content: 'Choose:', components: [menu.toJSON()] })
1155
- */
1156
- declare class SelectMenuBuilder {
1157
- private _customId;
1158
- private _placeholder?;
1688
+ * const client = new NovaClient({ token: 'nova_bot_...' })
1689
+ *
1690
+ * client.on('ready', (bot) => {
1691
+ * console.log(`Logged in as ${bot.botUser.username}`)
1692
+ * })
1693
+ *
1694
+ * client.on('interactionCreate', async (interaction) => {
1695
+ * if (interaction.commandName === 'ping') {
1696
+ * await client.interactions.respond(interaction.id, { content: 'Pong!' })
1697
+ * }
1698
+ * })
1699
+ *
1700
+ * await client.connect()
1701
+ */
1702
+ declare class NovaClient extends EventEmitter {
1703
+ /** The authenticated bot application. Available after `ready` fires. */
1704
+ botUser: BotApplication | null;
1705
+ /** Send, edit, delete and fetch messages. */
1706
+ readonly messages: MessagesAPI;
1707
+ /** Register and manage slash, prefix, and context menu commands. */
1708
+ readonly commands: CommandsAPI;
1709
+ /** List, kick and ban server members. */
1710
+ readonly members: MembersAPI;
1711
+ /** List servers the bot is a member of. */
1712
+ readonly servers: ServersAPI;
1713
+ /** Acknowledge and respond to interactions. */
1714
+ readonly interactions: InteractionsAPI;
1715
+ /** Query the bot's effective permissions within a server, channel, or role scope. */
1716
+ readonly permissions: PermissionsAPI;
1717
+ /** Create, edit, and delete channels; fetch pins and messages. */
1718
+ readonly channels: ChannelsAPI;
1719
+ /** Add, remove and fetch reactions on messages. */
1720
+ readonly reactions: ReactionsAPI;
1721
+ private socket;
1722
+ private readonly http;
1723
+ private readonly options;
1724
+ private _cronTimers;
1725
+ private readonly _commandHandlers;
1726
+ private readonly _buttonHandlers;
1727
+ private readonly _selectHandlers;
1728
+ private readonly _modalHandlers;
1729
+ constructor(options: NovaClientOptions);
1730
+ /**
1731
+ * Register a handler for a slash or prefix command by name.
1732
+ * Automatically routes `interactionCreate` events whose `commandName` matches.
1733
+ *
1734
+ * @example
1735
+ * client.command('ping', async (interaction) => {
1736
+ * await interaction.reply('Pong! 🏓')
1737
+ * })
1738
+ */
1739
+ command(name: string, handler: (interaction: NovaInteraction) => unknown): this;
1740
+ /**
1741
+ * Register a handler for a button by its `customId`.
1742
+ *
1743
+ * @example
1744
+ * client.button('confirm_delete', async (interaction) => {
1745
+ * await interaction.replyEphemeral('Deleted.')
1746
+ * })
1747
+ */
1748
+ button(customId: string, handler: (interaction: NovaInteraction) => unknown): this;
1749
+ /**
1750
+ * Register a handler for a select menu by its `customId`.
1751
+ *
1752
+ * @example
1753
+ * client.selectMenu('colour_pick', async (interaction) => {
1754
+ * const chosen = interaction.values[0]
1755
+ * await interaction.reply(`You picked: ${chosen}`)
1756
+ * })
1757
+ */
1758
+ selectMenu(customId: string, handler: (interaction: NovaInteraction) => unknown): this;
1759
+ /**
1760
+ * Register a handler for a modal submission by its `customId`.
1761
+ * Called automatically when the user submits a modal with that `customId`.
1762
+ *
1763
+ * @example
1764
+ * client.modal('report_modal', async (interaction) => {
1765
+ * const reason = interaction.modalData.reason
1766
+ * await interaction.replyEphemeral(`Report received: ${reason}`)
1767
+ * })
1768
+ */
1769
+ modal(customId: string, handler: (interaction: NovaInteraction) => unknown): this;
1770
+ /**
1771
+ * Connect to the Nova WebSocket gateway.
1772
+ * Resolves when the `ready` event is received.
1773
+ *
1774
+ * @example
1775
+ * await client.connect()
1776
+ */
1777
+ connect(): Promise<void>;
1778
+ /**
1779
+ * Register a recurring task that fires at a set interval.
1780
+ * All cron tasks are automatically cancelled on `disconnect()`.
1781
+ *
1782
+ * @param intervalMs - How often to run the task (in milliseconds).
1783
+ * @param fn - Async or sync function to call on each tick.
1784
+ * @returns A cancel function — call it to stop this specific task.
1785
+ *
1786
+ * @example
1787
+ * // Check for new announcements every 30 seconds
1788
+ * client.cron(30_000, async () => {
1789
+ * const messages = await client.messages.fetch(channelId, { limit: 5 })
1790
+ * // do something...
1791
+ * })
1792
+ */
1793
+ cron(intervalMs: number, fn: () => unknown): () => void;
1794
+ /**
1795
+ * Set the bot's presence status.
1796
+ * Broadcasts to all servers the bot is in via WebSocket.
1797
+ *
1798
+ * @example
1799
+ * client.setStatus('DND') // Do Not Disturb
1800
+ * client.setStatus('IDLE') // Away
1801
+ * client.setStatus('OFFLINE') // Appear offline
1802
+ * client.setStatus('ONLINE') // Back online
1803
+ */
1804
+ setStatus(status: BotStatus): void;
1805
+ /**
1806
+ * Fetch a single channel and return it as a rich `NovaChannel` wrapper.
1807
+ *
1808
+ * @example
1809
+ * const channel = await client.fetchChannel('channel-id')
1810
+ * await channel.send('Hello!')
1811
+ */
1812
+ fetchChannel(channelId: string): Promise<NovaChannel>;
1813
+ /**
1814
+ * Fetch all channels in a server and return them as `NovaChannel` wrappers.
1815
+ *
1816
+ * @example
1817
+ * const channels = await client.fetchChannels('server-id')
1818
+ * const textChannels = channels.filter(c => c.isText())
1819
+ */
1820
+ fetchChannels(serverId: string): Promise<NovaChannel[]>;
1821
+ /**
1822
+ * Fetch all members in a server and return them as `NovaMember` wrappers.
1823
+ *
1824
+ * @example
1825
+ * const members = await client.fetchMembers('server-id')
1826
+ * const bots = members.filter(m => m.isBot)
1827
+ */
1828
+ fetchMembers(serverId: string): Promise<NovaMember[]>;
1829
+ /**
1830
+ * Fetch a single member from a server and return them as a `NovaMember` wrapper.
1831
+ * Throws if the user is not found in the server.
1832
+ *
1833
+ * @example
1834
+ * const member = await client.fetchMember('server-id', 'user-id')
1835
+ * await member.dm('Welcome!')
1836
+ */
1837
+ fetchMember(serverId: string, userId: string): Promise<NovaMember>;
1838
+ /**
1839
+ * Wait for a specific event to be emitted, optionally filtered.
1840
+ * Rejects after `timeoutMs` (default 30 s) if the condition never fires.
1841
+ *
1842
+ * @example
1843
+ * // Wait for any message in a specific channel
1844
+ * const msg = await client.waitFor('messageCreate', m => m.channelId === channelId)
1845
+ *
1846
+ * // Wait for a button click with a timeout
1847
+ * const i = await client.waitFor('interactionCreate', i => i.isButton(), 60_000)
1848
+ */
1849
+ waitFor<K extends keyof NovaClientEvents>(event: K, filter?: (...args: Parameters<NovaClientEvents[K]>) => boolean, timeoutMs?: number): Promise<Parameters<NovaClientEvents[K]>[0]>;
1850
+ /**
1851
+ * Disconnect from the gateway and clean up.
1852
+ */
1853
+ disconnect(): void;
1854
+ /**
1855
+ * Send a message via the WebSocket gateway (lower latency than HTTP).
1856
+ * Requires the `messages.write` scope.
1857
+ *
1858
+ * @example
1859
+ * client.wsSend('channel-id', 'Hello from the gateway!')
1860
+ */
1861
+ wsSend(channelId: string, content: string): void;
1862
+ /**
1863
+ * Start a typing indicator via WebSocket.
1864
+ */
1865
+ wsTypingStart(channelId: string): void;
1866
+ /**
1867
+ * Stop a typing indicator via WebSocket.
1868
+ */
1869
+ wsTypingStop(channelId: string): void;
1870
+ on<K extends keyof NovaClientEvents>(event: K, listener: NovaClientEvents[K]): this;
1871
+ on(event: string | symbol, listener: (...args: unknown[]) => void): this;
1872
+ once<K extends keyof NovaClientEvents>(event: K, listener: NovaClientEvents[K]): this;
1873
+ once(event: string | symbol, listener: (...args: unknown[]) => void): this;
1874
+ off<K extends keyof NovaClientEvents>(event: K, listener: NovaClientEvents[K]): this;
1875
+ off(event: string | symbol, listener: (...args: unknown[]) => void): this;
1876
+ emit<K extends keyof NovaClientEvents>(event: K, ...args: Parameters<NovaClientEvents[K]>): boolean;
1877
+ emit(event: string | symbol, ...args: unknown[]): boolean;
1878
+ }
1879
+
1880
+ /**
1881
+ * Fluent builder for bot message embeds.
1882
+ *
1883
+ * @example
1884
+ * const embed = new EmbedBuilder()
1885
+ * .setTitle('Server Stats')
1886
+ * .setDescription('Here are the latest numbers.')
1887
+ * .setColor('#5865F2')
1888
+ * .addField('Members', '1 234', true)
1889
+ * .addField('Messages today', '567', true)
1890
+ * .setFooter('Nova Bot')
1891
+ * .setTimestamp()
1892
+ *
1893
+ * await client.messages.send(channelId, { embed })
1894
+ */
1895
+ declare class EmbedBuilder {
1896
+ private readonly _data;
1897
+ /** Set the embed title (shown in bold at the top). */
1898
+ setTitle(title: string): this;
1899
+ /** Set the main description text (supports markdown). */
1900
+ setDescription(description: string): this;
1901
+ /**
1902
+ * Set the accent colour.
1903
+ * @param color Hex string — e.g. `'#5865F2'` or `'5865F2'`
1904
+ */
1905
+ setColor(color: string): this;
1906
+ /** Make the title a clickable hyperlink. */
1907
+ setUrl(url: string): this;
1908
+ /** Small image shown in the top-right corner. */
1909
+ setThumbnail(url: string): this;
1910
+ /** Large image shown below the fields. */
1911
+ setImage(url: string): this;
1912
+ /** Footer text shown at the very bottom of the embed. */
1913
+ setFooter(text: string): this;
1914
+ /**
1915
+ * Add a timestamp to the footer line.
1916
+ * @param date Defaults to `new Date()`.
1917
+ */
1918
+ setTimestamp(date?: Date | number): this;
1919
+ /** Author name + optional icon shown above the title. */
1920
+ setAuthor(author: {
1921
+ name: string;
1922
+ iconUrl?: string;
1923
+ }): this;
1924
+ /**
1925
+ * Add a single field.
1926
+ * @param inline Pass `true` to render this field side-by-side with adjacent inline fields.
1927
+ */
1928
+ addField(name: string, value: string, inline?: boolean): this;
1929
+ /** Add multiple fields at once. */
1930
+ addFields(...fields: EmbedField[]): this;
1931
+ /** Replace all fields. */
1932
+ setFields(fields: EmbedField[]): this;
1933
+ /** Serialise to a plain `Embed` object you can pass to any API call. */
1934
+ toJSON(): Embed;
1935
+ }
1936
+
1937
+ type ButtonStyle = 'primary' | 'secondary' | 'success' | 'danger' | 'link';
1938
+ /**
1939
+ * Fluent builder for button components.
1940
+ *
1941
+ * @example
1942
+ * const row = new ActionRowBuilder()
1943
+ * .addButton(
1944
+ * new ButtonBuilder()
1945
+ * .setCustomId('confirm')
1946
+ * .setLabel('Confirm')
1947
+ * .setStyle('success')
1948
+ * )
1949
+ * .addButton(
1950
+ * new ButtonBuilder()
1951
+ * .setCustomId('cancel')
1952
+ * .setLabel('Cancel')
1953
+ * .setStyle('danger')
1954
+ * )
1955
+ *
1956
+ * await client.messages.send(channelId, { content: 'Are you sure?', components: row.toJSON() })
1957
+ */
1958
+ declare class ButtonBuilder {
1959
+ private _customId;
1960
+ private _label;
1961
+ private _style;
1962
+ private _disabled;
1963
+ private _emoji?;
1964
+ private _url?;
1965
+ /** Custom ID returned in the `BUTTON_CLICK` interaction. Not required for `link` style buttons. */
1966
+ setCustomId(customId: string): this;
1967
+ /** Text displayed on the button. */
1968
+ setLabel(label: string): this;
1969
+ /**
1970
+ * Visual style:
1971
+ * - `primary` — blurple / brand colour
1972
+ * - `secondary` — grey
1973
+ * - `success` — green
1974
+ * - `danger` — red
1975
+ * - `link` — grey, navigates to a URL instead of creating an interaction
1976
+ */
1977
+ setStyle(style: ButtonStyle): this;
1978
+ /** Emoji shown to the left of the label (unicode or custom e.g. `'👋'`). */
1979
+ setEmoji(emoji: string): this;
1980
+ /**
1981
+ * URL for `link` style buttons.
1982
+ * Sets the style to `'link'` automatically.
1983
+ */
1984
+ setUrl(url: string): this;
1985
+ /** Prevent users from clicking the button. */
1986
+ setDisabled(disabled?: boolean): this;
1987
+ toJSON(): MessageComponent;
1988
+ }
1989
+
1990
+ interface SelectMenuOption {
1991
+ /** Visible label shown to the user. */
1992
+ label: string;
1993
+ /** Value returned in the interaction's `values` array. */
1994
+ value: string;
1995
+ /** Optional description shown below the label. */
1996
+ description?: string;
1997
+ }
1998
+ /**
1999
+ * Fluent builder for select-menu (dropdown) components.
2000
+ *
2001
+ * @example
2002
+ * const menu = new SelectMenuBuilder()
2003
+ * .setCustomId('colour_pick')
2004
+ * .setPlaceholder('Pick a colour…')
2005
+ * .addOption({ label: 'Red', value: 'red' })
2006
+ * .addOption({ label: 'Green', value: 'green' })
2007
+ * .addOption({ label: 'Blue', value: 'blue' })
2008
+ *
2009
+ * await client.messages.send(channelId, { content: 'Choose:', components: [menu.toJSON()] })
2010
+ */
2011
+ declare class SelectMenuBuilder {
2012
+ private _customId;
2013
+ private _placeholder?;
1159
2014
  private _disabled;
1160
2015
  private readonly _options;
1161
2016
  /** Custom ID returned in the `SELECT_MENU` interaction. */
@@ -1173,92 +2028,829 @@ declare class SelectMenuBuilder {
1173
2028
  toJSON(): MessageComponent;
1174
2029
  }
1175
2030
 
1176
- type ComponentBuilder = ButtonBuilder | SelectMenuBuilder | {
1177
- toJSON(): MessageComponent;
1178
- };
2031
+ type ComponentBuilder = ButtonBuilder | SelectMenuBuilder | {
2032
+ toJSON(): MessageComponent;
2033
+ };
2034
+ /**
2035
+ * Groups buttons (and/or a select menu) into a single row of components.
2036
+ *
2037
+ * @example
2038
+ * const row = new ActionRowBuilder()
2039
+ * .addComponent(new ButtonBuilder().setCustomId('yes').setLabel('Yes').setStyle('success'))
2040
+ * .addComponent(new ButtonBuilder().setCustomId('no').setLabel('No').setStyle('danger'))
2041
+ *
2042
+ * await interaction.reply({ content: 'Confirm?', components: row.toJSON() })
2043
+ */
2044
+ declare class ActionRowBuilder {
2045
+ private readonly _components;
2046
+ /** Add a single component (button or select menu). */
2047
+ addComponent(component: ComponentBuilder): this;
2048
+ /** Add multiple components at once. */
2049
+ addComponents(...components: ComponentBuilder[]): this;
2050
+ /**
2051
+ * Serialise to a flat `MessageComponent[]` array — the format accepted by all API calls.
2052
+ */
2053
+ toJSON(): MessageComponent[];
2054
+ }
2055
+
2056
+ /**
2057
+ * Fluent builder for a single slash command option (parameter).
2058
+ * Obtain one via the callback in `SlashCommandBuilder.addStringOption()` etc.
2059
+ */
2060
+ declare class SlashCommandOptionBuilder {
2061
+ private _data;
2062
+ constructor(type: SlashCommandOption['type']);
2063
+ /** Internal option name (lowercase, no spaces). Shown after `/command ` in the client UI. */
2064
+ setName(name: string): this;
2065
+ /** Short human-readable description shown in the command picker. */
2066
+ setDescription(description: string): this;
2067
+ /** Whether users must supply this option before sending the command. */
2068
+ setRequired(required?: boolean): this;
2069
+ /**
2070
+ * Restrict the option to specific values.
2071
+ * The picker will show these as autocomplete suggestions.
2072
+ */
2073
+ addChoice(name: string, value: string | number): this;
2074
+ /** Set all allowed choices at once. */
2075
+ setChoices(choices: Array<{
2076
+ name: string;
2077
+ value: string | number;
2078
+ }>): this;
2079
+ toJSON(): SlashCommandOption;
2080
+ }
2081
+ /**
2082
+ * Fluent builder for slash commands.
2083
+ *
2084
+ * @example
2085
+ * await client.commands.setSlash([
2086
+ * new SlashCommandBuilder()
2087
+ * .setName('ban')
2088
+ * .setDescription('Ban a member from this server')
2089
+ * .addUserOption((opt) =>
2090
+ * opt.setName('user').setDescription('Who to ban').setRequired(true)
2091
+ * )
2092
+ * .addStringOption((opt) =>
2093
+ * opt.setName('reason').setDescription('Reason for the ban')
2094
+ * )
2095
+ * .toJSON(),
2096
+ * ])
2097
+ */
2098
+ declare class SlashCommandBuilder {
2099
+ private _data;
2100
+ /** Command name — lowercase, no spaces (e.g. `'ban'`, `'server-info'`). */
2101
+ setName(name: string): this;
2102
+ /** Short description shown in the command picker UI. */
2103
+ setDescription(description: string): this;
2104
+ /** Add a text (string) option. */
2105
+ addStringOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
2106
+ /** Add an integer (whole number) option. */
2107
+ addIntegerOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
2108
+ /** Add a boolean (true/false) option. */
2109
+ addBooleanOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
2110
+ /** Add a user-mention option (returns a user ID string). */
2111
+ addUserOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
2112
+ /** Add a channel-mention option (returns a channel ID string). */
2113
+ addChannelOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
2114
+ /** Add a role-mention option (returns a role ID string). */
2115
+ addRoleOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
2116
+ toJSON(): SlashCommandDefinition;
2117
+ }
2118
+
2119
+ interface PollOption {
2120
+ label: string;
2121
+ emoji?: string;
2122
+ value?: string;
2123
+ }
2124
+ interface PollDefinition {
2125
+ question: string;
2126
+ options: PollOption[];
2127
+ allowMultiple?: boolean;
2128
+ duration?: number | null;
2129
+ anonymous?: boolean;
2130
+ }
2131
+ /**
2132
+ * Fluent builder for creating polls.
2133
+ *
2134
+ * @example
2135
+ * const poll = new PollBuilder()
2136
+ * .setQuestion('What is your favourite colour?')
2137
+ * .addOption({ label: 'Red', emoji: '🔴' })
2138
+ * .addOption({ label: 'Green', emoji: '🟢' })
2139
+ * .addOption({ label: 'Blue', emoji: '🔵' })
2140
+ * .setDuration(60 * 60 * 24) // 24 hours in seconds
2141
+ * .toJSON()
2142
+ *
2143
+ * await client.messages.send(channelId, { poll })
2144
+ */
2145
+ declare class PollBuilder {
2146
+ private _question;
2147
+ private _options;
2148
+ private _allowMultiple;
2149
+ private _duration;
2150
+ private _anonymous;
2151
+ /**
2152
+ * Set the poll question.
2153
+ *
2154
+ * @example
2155
+ * builder.setQuestion('Best programming language?')
2156
+ */
2157
+ setQuestion(question: string): this;
2158
+ /**
2159
+ * Add a single answer option.
2160
+ *
2161
+ * @example
2162
+ * builder.addOption({ label: 'TypeScript', emoji: '🔷' })
2163
+ */
2164
+ addOption(option: PollOption): this;
2165
+ /**
2166
+ * Add multiple answer options at once.
2167
+ *
2168
+ * @example
2169
+ * builder.addOptions([
2170
+ * { label: 'Yes', emoji: '✅' },
2171
+ * { label: 'No', emoji: '❌' },
2172
+ * ])
2173
+ */
2174
+ addOptions(options: PollOption[]): this;
2175
+ /**
2176
+ * Allow users to select more than one option.
2177
+ * Default: `false` (single choice).
2178
+ */
2179
+ setAllowMultiple(yes?: boolean): this;
2180
+ /**
2181
+ * Set the poll duration in **seconds**. Pass `null` to make it permanent.
2182
+ *
2183
+ * @example
2184
+ * builder.setDuration(60 * 60 * 24) // expires in 24 hours
2185
+ * builder.setDuration(null) // no expiry
2186
+ */
2187
+ setDuration(seconds: number | null): this;
2188
+ /**
2189
+ * Hide which users voted for which option.
2190
+ * Default: `false` (votes are public).
2191
+ */
2192
+ setAnonymous(yes?: boolean): this;
2193
+ /** Build the poll definition object. */
2194
+ toJSON(): PollDefinition;
2195
+ }
2196
+
2197
+ /**
2198
+ * Fluent builder for composing messages.
2199
+ * Replaces passing raw option objects everywhere.
2200
+ *
2201
+ * @example
2202
+ * const msg = new MessageBuilder()
2203
+ * .setContent('Here are your stats:')
2204
+ * .setEmbed(
2205
+ * new EmbedBuilder()
2206
+ * .setTitle('📊 Stats')
2207
+ * .addField('Messages', '1 234', true)
2208
+ * .addField('Reactions', '567', true)
2209
+ * .setColor('#5865F2')
2210
+ * )
2211
+ * .addRow(
2212
+ * new ActionRowBuilder()
2213
+ * .addComponent(new ButtonBuilder().setLabel('Refresh').setCustomId('refresh').setStyle('primary'))
2214
+ * )
2215
+ * .toJSON()
2216
+ *
2217
+ * await client.messages.send(channelId, msg)
2218
+ */
2219
+ declare class MessageBuilder {
2220
+ private _content;
2221
+ private _embed;
2222
+ private _components;
2223
+ private _replyToId;
2224
+ private _poll;
2225
+ /**
2226
+ * Set the text content of the message.
2227
+ *
2228
+ * @example
2229
+ * builder.setContent('Hello world!')
2230
+ */
2231
+ setContent(content: string): this;
2232
+ /**
2233
+ * Attach an embed. Accepts an `EmbedBuilder` or raw `Embed` object.
2234
+ *
2235
+ * @example
2236
+ * builder.setEmbed(new EmbedBuilder().setTitle('Result'))
2237
+ * builder.setEmbed({ title: 'Result', color: '#57F287' })
2238
+ */
2239
+ setEmbed(embed: EmbedBuilder | Embed): this;
2240
+ /**
2241
+ * Remove any embed from this message.
2242
+ */
2243
+ clearEmbed(): this;
2244
+ /**
2245
+ * Add an `ActionRowBuilder` (or raw component array) as a component row.
2246
+ *
2247
+ * @example
2248
+ * builder.addRow(
2249
+ * new ActionRowBuilder().addComponent(btn1).addComponent(btn2)
2250
+ * )
2251
+ */
2252
+ addRow(row: ActionRowBuilder | MessageComponent[]): this;
2253
+ /**
2254
+ * Replace all existing component rows.
2255
+ */
2256
+ setComponents(components: MessageComponent[]): this;
2257
+ /**
2258
+ * Set the message this is replying to.
2259
+ *
2260
+ * @example
2261
+ * builder.setReplyTo(message.id)
2262
+ */
2263
+ setReplyTo(messageId: string): this;
2264
+ /**
2265
+ * Attach a poll to the message.
2266
+ * Accepts a `PollBuilder` or raw `PollDefinition`.
2267
+ *
2268
+ * @example
2269
+ * builder.setPoll(
2270
+ * new PollBuilder()
2271
+ * .setQuestion('Favourite language?')
2272
+ * .addOptions([{ label: 'TypeScript' }, { label: 'Python' }])
2273
+ * )
2274
+ */
2275
+ setPoll(poll: {
2276
+ toJSON: () => PollDefinition;
2277
+ } | PollDefinition): this;
2278
+ /**
2279
+ * Build the message options object, ready to pass to `client.messages.send()`.
2280
+ *
2281
+ * @throws if neither `content` nor `embed` nor `poll` is set.
2282
+ */
2283
+ toJSON(): SendMessageOptions & {
2284
+ poll?: PollDefinition;
2285
+ };
2286
+ }
2287
+
2288
+ /**
2289
+ * A supercharged Map with extra utility methods.
2290
+ * Used throughout the SDK for caches (messages, channels, members, etc.)
2291
+ *
2292
+ * Inspired by discord.js Collection — but with proper generics and async support.
2293
+ *
2294
+ * @example
2295
+ * const col = new Collection<string, User>()
2296
+ * col.set('u1', { id: 'u1', name: 'Alice' })
2297
+ * col.set('u2', { id: 'u2', name: 'Bob' })
2298
+ *
2299
+ * col.first() // { id: 'u1', name: 'Alice' }
2300
+ * col.find(u => u.name === 'Bob') // { id: 'u2', name: 'Bob' }
2301
+ * col.map(u => u.name) // ['Alice', 'Bob']
2302
+ */
2303
+ declare class Collection<K, V> extends Map<K, V> {
2304
+ /**
2305
+ * The first value stored (insertion order), or `undefined` if empty.
2306
+ */
2307
+ first(): V | undefined;
2308
+ /**
2309
+ * The first `n` values stored (insertion order).
2310
+ */
2311
+ firstN(n: number): V[];
2312
+ /**
2313
+ * The last value stored, or `undefined` if empty.
2314
+ */
2315
+ last(): V | undefined;
2316
+ /**
2317
+ * The last `n` values stored (insertion order).
2318
+ */
2319
+ lastN(n: number): V[];
2320
+ /**
2321
+ * Returns a random value from the collection, or `undefined` if empty.
2322
+ */
2323
+ random(): V | undefined;
2324
+ /**
2325
+ * Find the first value that passes the predicate.
2326
+ *
2327
+ * @example
2328
+ * const bot = members.find(m => m.user.isBot)
2329
+ */
2330
+ find(fn: (value: V, key: K, col: this) => boolean): V | undefined;
2331
+ /**
2332
+ * Find the key of the first value that passes the predicate.
2333
+ */
2334
+ findKey(fn: (value: V, key: K, col: this) => boolean): K | undefined;
2335
+ /**
2336
+ * Returns `true` if at least one value satisfies the predicate.
2337
+ */
2338
+ some(fn: (value: V, key: K, col: this) => boolean): boolean;
2339
+ /**
2340
+ * Returns `true` if every value satisfies the predicate.
2341
+ */
2342
+ every(fn: (value: V, key: K, col: this) => boolean): boolean;
2343
+ /**
2344
+ * Filter to a new Collection containing only values that pass the predicate.
2345
+ *
2346
+ * @example
2347
+ * const admins = members.filter(m => m.role === 'ADMIN')
2348
+ */
2349
+ filter(fn: (value: V, key: K, col: this) => boolean): Collection<K, V>;
2350
+ /**
2351
+ * Map each value to a new array.
2352
+ *
2353
+ * @example
2354
+ * const names = members.map(m => m.user.username)
2355
+ */
2356
+ map<T>(fn: (value: V, key: K, col: this) => T): T[];
2357
+ /**
2358
+ * Map to a new Collection with transformed values.
2359
+ *
2360
+ * @example
2361
+ * const names = channels.mapValues(c => c.name.toUpperCase())
2362
+ */
2363
+ mapValues<T>(fn: (value: V, key: K, col: this) => T): Collection<K, T>;
2364
+ /**
2365
+ * Reduce the collection to a single value.
2366
+ *
2367
+ * @example
2368
+ * const totalReactions = messages.reduce((sum, m) => sum + m.reactions.length, 0)
2369
+ */
2370
+ reduce<T>(fn: (accumulator: T, value: V, key: K, col: this) => T, initial: T): T;
2371
+ /**
2372
+ * Returns values as an array (insertion order).
2373
+ */
2374
+ toArray(): V[];
2375
+ /**
2376
+ * Returns keys as an array (insertion order).
2377
+ */
2378
+ keyArray(): K[];
2379
+ /**
2380
+ * Sort and return a new Collection.
2381
+ * Callback works like `Array.prototype.sort`.
2382
+ *
2383
+ * @example
2384
+ * const sorted = channels.sort((a, b) => a.position - b.position)
2385
+ */
2386
+ sort(fn?: (a: V, b: V, ak: K, bk: K) => number): Collection<K, V>;
2387
+ /**
2388
+ * Split into two Collections based on a predicate.
2389
+ * Returns `[passing, failing]`.
2390
+ *
2391
+ * @example
2392
+ * const [bots, humans] = members.partition(m => m.user.isBot)
2393
+ */
2394
+ partition(fn: (value: V, key: K, col: this) => boolean): [Collection<K, V>, Collection<K, V>];
2395
+ /**
2396
+ * Merge this collection with one or more others.
2397
+ * Later collections overwrite duplicate keys.
2398
+ */
2399
+ merge(...others: ReadonlyMap<K, V>[]): Collection<K, V>;
2400
+ /**
2401
+ * Serialize to a plain `Record` (requires string keys).
2402
+ */
2403
+ toJSON(): Record<string, V>;
2404
+ toString(): string;
2405
+ }
2406
+
1179
2407
  /**
1180
- * Groups buttons (and/or a select menu) into a single row of components.
2408
+ * Per-user cooldown manager.
2409
+ * Prevents commands from being spammed by individual users.
1181
2410
  *
1182
2411
  * @example
1183
- * const row = new ActionRowBuilder()
1184
- * .addComponent(new ButtonBuilder().setCustomId('yes').setLabel('Yes').setStyle('success'))
1185
- * .addComponent(new ButtonBuilder().setCustomId('no').setLabel('No').setStyle('danger'))
2412
+ * // Create once per command
2413
+ * const cooldown = new Cooldown(5_000) // 5 second cooldown
1186
2414
  *
1187
- * await interaction.reply({ content: 'Confirm?', components: row.toJSON() })
2415
+ * client.command('daily', async (interaction) => {
2416
+ * const remaining = cooldown.check(interaction.userId)
2417
+ * if (remaining > 0) {
2418
+ * return interaction.replyEphemeral(
2419
+ * `⏳ Wait **${Cooldown.format(remaining)}** before using this command again.`
2420
+ * )
2421
+ * }
2422
+ * cooldown.use(interaction.userId)
2423
+ * await interaction.reply('🎁 Daily reward collected!')
2424
+ * })
1188
2425
  */
1189
- declare class ActionRowBuilder {
1190
- private readonly _components;
1191
- /** Add a single component (button or select menu). */
1192
- addComponent(component: ComponentBuilder): this;
1193
- /** Add multiple components at once. */
1194
- addComponents(...components: ComponentBuilder[]): this;
2426
+ declare class Cooldown {
2427
+ private readonly _durations;
2428
+ private readonly _last;
2429
+ private readonly _defaultMs;
1195
2430
  /**
1196
- * Serialise to a flat `MessageComponent[]` array the format accepted by all API calls.
2431
+ * @param durationMs - Default cooldown duration in milliseconds.
1197
2432
  */
1198
- toJSON(): MessageComponent[];
2433
+ constructor(durationMs: number);
2434
+ /**
2435
+ * Check remaining cooldown for a user.
2436
+ * Returns `0` if they are not on cooldown (free to proceed),
2437
+ * or the **milliseconds remaining** if they are on cooldown.
2438
+ *
2439
+ * @example
2440
+ * const ms = cooldown.check(userId)
2441
+ * if (ms > 0) return interaction.replyEphemeral(`Wait ${Cooldown.format(ms)}!`)
2442
+ */
2443
+ check(userId: string): number;
2444
+ /**
2445
+ * Mark a user as having just used the command, starting their cooldown.
2446
+ * Optionally override the cooldown duration for this specific user.
2447
+ *
2448
+ * @example
2449
+ * cooldown.use(userId) // uses default duration
2450
+ * cooldown.use(userId, 10_000) // 10 second cooldown for this use
2451
+ */
2452
+ use(userId: string, durationMs?: number): void;
2453
+ /**
2454
+ * Immediately reset (clear) the cooldown for a user.
2455
+ *
2456
+ * @example
2457
+ * cooldown.reset(userId) // user can use the command again immediately
2458
+ */
2459
+ reset(userId: string): void;
2460
+ /**
2461
+ * Reset all active cooldowns.
2462
+ */
2463
+ resetAll(): void;
2464
+ /**
2465
+ * Returns a list of all users currently on cooldown.
2466
+ *
2467
+ * @example
2468
+ * const active = cooldown.activeCooldowns()
2469
+ * // [{ userId: 'abc', remainingMs: 3200 }, ...]
2470
+ */
2471
+ activeCooldowns(): Array<{
2472
+ userId: string;
2473
+ remainingMs: number;
2474
+ }>;
2475
+ /**
2476
+ * Format milliseconds into a human-readable string.
2477
+ *
2478
+ * @example
2479
+ * Cooldown.format(3_600_000) // '1h 0m 0s'
2480
+ * Cooldown.format(90_000) // '1m 30s'
2481
+ * Cooldown.format(4_000) // '4s'
2482
+ */
2483
+ static format(ms: number): string;
2484
+ }
2485
+ /**
2486
+ * Manages multiple named cooldowns in one place.
2487
+ * Ideal when you have many commands each with their own cooldown.
2488
+ *
2489
+ * @example
2490
+ * const cooldowns = new CooldownManager()
2491
+ *
2492
+ * client.command('ping', async (interaction) => {
2493
+ * const remaining = cooldowns.check('ping', interaction.userId, 3_000)
2494
+ * if (remaining > 0) return interaction.replyEphemeral(`Wait ${Cooldown.format(remaining)}!`)
2495
+ * await interaction.reply('Pong!')
2496
+ * })
2497
+ */
2498
+ declare class CooldownManager {
2499
+ private readonly _map;
2500
+ /**
2501
+ * Get or create a named cooldown.
2502
+ */
2503
+ get(name: string, durationMs?: number): Cooldown;
2504
+ /**
2505
+ * Check a named cooldown for a user.
2506
+ * Creates the cooldown if it doesn't exist yet.
2507
+ * Returns `0` if free, or remaining ms if on cooldown.
2508
+ *
2509
+ * @example
2510
+ * const ms = cooldowns.check('ban', userId, 30_000)
2511
+ */
2512
+ check(name: string, userId: string, durationMs?: number): number;
2513
+ /**
2514
+ * Mark a user as having used a named command.
2515
+ *
2516
+ * @example
2517
+ * cooldowns.use('ban', userId, 30_000)
2518
+ */
2519
+ use(name: string, userId: string, durationMs?: number): void;
2520
+ /**
2521
+ * Reset a user's cooldown on a named command.
2522
+ */
2523
+ reset(name: string, userId: string): void;
2524
+ /**
2525
+ * Reset all cooldowns for all commands.
2526
+ */
2527
+ resetAll(): void;
1199
2528
  }
1200
2529
 
1201
2530
  /**
1202
- * Fluent builder for a single slash command option (parameter).
1203
- * Obtain one via the callback in `SlashCommandBuilder.addStringOption()` etc.
2531
+ * Coloured, timestamped logger for Nova bots.
2532
+ * Prefix every log with a consistent `[BotName]` tag.
2533
+ *
2534
+ * @example
2535
+ * const log = new Logger('MyBot')
2536
+ * log.info('Ready!') // [MyBot] [INFO] Ready!
2537
+ * log.warn('Rate limited') // [MyBot] [WARN] Rate limited
2538
+ * log.error('DB timed out') // [MyBot] [ERROR] DB timed out
2539
+ * log.debug('Payload:', data) // [MyBot] [DEBUG] Payload: { ... }
2540
+ * log.success('Command registered') // [MyBot] [OK] Command registered
2541
+ * log.command('ping', userId) // [MyBot] [CMD] ping ← userId
2542
+ * log.event('messageCreate') // [MyBot] [EVT] messageCreate
1204
2543
  */
1205
- declare class SlashCommandOptionBuilder {
1206
- private _data;
1207
- constructor(type: SlashCommandOption['type']);
1208
- /** Internal option name (lowercase, no spaces). Shown after `/command ` in the client UI. */
1209
- setName(name: string): this;
1210
- /** Short human-readable description shown in the command picker. */
1211
- setDescription(description: string): this;
1212
- /** Whether users must supply this option before sending the command. */
1213
- setRequired(required?: boolean): this;
2544
+ declare class Logger {
2545
+ private readonly _prefix;
2546
+ private _debug;
1214
2547
  /**
1215
- * Restrict the option to specific values.
1216
- * The picker will show these as autocomplete suggestions.
2548
+ * @param name - Name shown in square brackets before every message.
2549
+ * @param enableDebug - When `false` (default), `.debug()` calls are silent.
1217
2550
  */
1218
- addChoice(name: string, value: string | number): this;
1219
- /** Set all allowed choices at once. */
1220
- setChoices(choices: Array<{
1221
- name: string;
1222
- value: string | number;
1223
- }>): this;
1224
- toJSON(): SlashCommandOption;
2551
+ constructor(name: string, enableDebug?: boolean);
2552
+ /** Enable or disable debug output at runtime. */
2553
+ setDebug(enabled: boolean): this;
2554
+ private _timestamp;
2555
+ private _fmt;
2556
+ /** General info message. */
2557
+ info(...args: unknown[]): void;
2558
+ /** Warning — something unexpected but non-fatal. */
2559
+ warn(...args: unknown[]): void;
2560
+ /** Error — something went wrong. */
2561
+ error(...args: unknown[]): void;
2562
+ /** Success / positive confirmation. */
2563
+ success(...args: unknown[]): void;
2564
+ /** Debug — only printed when `enableDebug` is true. */
2565
+ debug(...args: unknown[]): void;
2566
+ /**
2567
+ * Log an incoming command interaction.
2568
+ *
2569
+ * @example
2570
+ * log.command('ping', interaction.userId)
2571
+ * // [MyBot] [CMD] /ping ← user:abc123
2572
+ */
2573
+ command(name: string, userId: string): void;
2574
+ /**
2575
+ * Log a gateway event.
2576
+ *
2577
+ * @example
2578
+ * log.event('messageCreate')
2579
+ */
2580
+ event(type: string, extra?: string): void;
2581
+ /**
2582
+ * Log that a command handler threw an error.
2583
+ *
2584
+ * @example
2585
+ * log.commandError('ban', err)
2586
+ */
2587
+ commandError(name: string, err: unknown): void;
1225
2588
  }
2589
+
1226
2590
  /**
1227
- * Fluent builder for slash commands.
2591
+ * Async cursor-based paginator for any list API.
2592
+ *
2593
+ * Pass a `fetchFn` that takes the current cursor and returns the next
2594
+ * page of items plus the cursor to use for the next page (`null` = done).
1228
2595
  *
1229
2596
  * @example
1230
- * await client.commands.setSlash([
1231
- * new SlashCommandBuilder()
1232
- * .setName('ban')
1233
- * .setDescription('Ban a member from this server')
1234
- * .addUserOption((opt) =>
1235
- * opt.setName('user').setDescription('Who to ban').setRequired(true)
1236
- * )
1237
- * .addStringOption((opt) =>
1238
- * opt.setName('reason').setDescription('Reason for the ban')
1239
- * )
1240
- * .toJSON(),
1241
- * ])
2597
+ * // Paginate all messages in a channel
2598
+ * const paginator = new Paginator(async (cursor) => {
2599
+ * const messages = await client.messages.fetch(channelId, { limit: 50, before: cursor ?? undefined })
2600
+ * return { items: messages, cursor: messages.at(-1)?.id ?? null }
2601
+ * })
2602
+ *
2603
+ * // Lazy async iteration
2604
+ * for await (const message of paginator) {
2605
+ * console.log(message.content)
2606
+ * }
2607
+ *
2608
+ * // Or collect everything at once
2609
+ * const all = await paginator.fetchAll()
1242
2610
  */
1243
- declare class SlashCommandBuilder {
1244
- private _data;
1245
- /** Command name — lowercase, no spaces (e.g. `'ban'`, `'server-info'`). */
1246
- setName(name: string): this;
1247
- /** Short description shown in the command picker UI. */
1248
- setDescription(description: string): this;
1249
- /** Add a text (string) option. */
1250
- addStringOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1251
- /** Add an integer (whole number) option. */
1252
- addIntegerOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1253
- /** Add a boolean (true/false) option. */
1254
- addBooleanOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1255
- /** Add a user-mention option (returns a user ID string). */
1256
- addUserOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1257
- /** Add a channel-mention option (returns a channel ID string). */
1258
- addChannelOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1259
- /** Add a role-mention option (returns a role ID string). */
1260
- addRoleOption(fn: (opt: SlashCommandOptionBuilder) => SlashCommandOptionBuilder): this;
1261
- toJSON(): SlashCommandDefinition;
2611
+ declare class Paginator<T> {
2612
+ private readonly fetchFn;
2613
+ private _cursor;
2614
+ private _done;
2615
+ private _totalFetched;
2616
+ constructor(fetchFn: (cursor: string | null) => Promise<{
2617
+ items: T[];
2618
+ cursor: string | null;
2619
+ }>);
2620
+ /** Whether all pages have been consumed. */
2621
+ get done(): boolean;
2622
+ /** Total number of items fetched so far (across all pages). */
2623
+ get totalFetched(): number;
2624
+ /**
2625
+ * Fetch the next page of results.
2626
+ * Returns an empty array when there are no more pages.
2627
+ *
2628
+ * @example
2629
+ * const page1 = await paginator.fetchPage()
2630
+ * const page2 = await paginator.fetchPage()
2631
+ */
2632
+ fetchPage(): Promise<T[]>;
2633
+ /**
2634
+ * Fetch **all** remaining pages and return a flat array.
2635
+ *
2636
+ * ⚠️ Use with caution on very large collections.
2637
+ *
2638
+ * @example
2639
+ * const allMembers = await paginator.fetchAll()
2640
+ */
2641
+ fetchAll(): Promise<T[]>;
2642
+ /**
2643
+ * Fetch up to `n` items total (across however many pages are needed).
2644
+ *
2645
+ * @example
2646
+ * const first100 = await paginator.fetchN(100)
2647
+ */
2648
+ fetchN(n: number): Promise<T[]>;
2649
+ /**
2650
+ * Async-iterate over every item, one page at a time.
2651
+ *
2652
+ * @example
2653
+ * for await (const msg of paginator) {
2654
+ * process(msg)
2655
+ * }
2656
+ */
2657
+ [Symbol.asyncIterator](): AsyncGenerator<T>;
2658
+ /**
2659
+ * Reset the paginator so you can iterate from the beginning again.
2660
+ *
2661
+ * @example
2662
+ * await paginator.fetchAll()
2663
+ * paginator.reset()
2664
+ * const againFirst = await paginator.fetchPage()
2665
+ */
2666
+ reset(): void;
2667
+ }
2668
+
2669
+ /**
2670
+ * All known Nova bot permission keys.
2671
+ * Use these as string constants when calling `has()`, `grant()`, etc.
2672
+ */
2673
+ declare const Permissions: {
2674
+ /** Read messages in channels. */
2675
+ readonly MESSAGES_READ: "messages.read";
2676
+ /** Send messages in channels. */
2677
+ readonly MESSAGES_WRITE: "messages.write";
2678
+ /** Edit / delete any message (not just the bot's own). */
2679
+ readonly MESSAGES_MANAGE: "messages.manage";
2680
+ /** Create, edit and delete channels. */
2681
+ readonly CHANNELS_MANAGE: "channels.manage";
2682
+ /** Kick members from the server. */
2683
+ readonly MEMBERS_KICK: "members.kick";
2684
+ /** Ban and unban members from the server. */
2685
+ readonly MEMBERS_BAN: "members.ban";
2686
+ /** Assign and remove roles on members. */
2687
+ readonly MEMBERS_ROLES: "members.roles";
2688
+ /** Edit server settings. */
2689
+ readonly SERVERS_MANAGE: "servers.manage";
2690
+ };
2691
+ type PermissionKey = (typeof Permissions)[keyof typeof Permissions];
2692
+ /**
2693
+ * Utility class for working with Nova bot permission records.
2694
+ *
2695
+ * Unlike a Discord-style bitfield, Nova permissions are stored as a
2696
+ * `Record<string, boolean>` map. `PermissionsBitfield` wraps that
2697
+ * map with readable helpers for checking, merging, and diffing.
2698
+ *
2699
+ * @example
2700
+ * const perms = new PermissionsBitfield({
2701
+ * 'messages.read': true,
2702
+ * 'messages.write': true,
2703
+ * })
2704
+ *
2705
+ * perms.has('messages.read') // true
2706
+ * perms.has('channels.manage') // false
2707
+ * perms.hasAll('messages.read', 'messages.write') // true
2708
+ * perms.missing('messages.manage', 'members.kick') // ['messages.manage', 'members.kick']
2709
+ * perms.toArray() // ['messages.read', 'messages.write']
2710
+ */
2711
+ declare class PermissionsBitfield {
2712
+ private readonly _perms;
2713
+ constructor(perms?: Record<string, boolean>);
2714
+ /**
2715
+ * Returns `true` if the given permission is explicitly granted.
2716
+ *
2717
+ * @example
2718
+ * if (!perms.has(Permissions.MESSAGES_WRITE)) {
2719
+ * throw new Error('Bot cannot write messages here.')
2720
+ * }
2721
+ */
2722
+ has(permission: string): boolean;
2723
+ /**
2724
+ * Returns `true` if **all** listed permissions are granted.
2725
+ *
2726
+ * @example
2727
+ * perms.hasAll('messages.read', 'messages.write') // true
2728
+ */
2729
+ hasAll(...permissions: string[]): boolean;
2730
+ /**
2731
+ * Returns `true` if **at least one** of the listed permissions is granted.
2732
+ *
2733
+ * @example
2734
+ * perms.hasAny('channels.manage', 'servers.manage') // true if either is set
2735
+ */
2736
+ hasAny(...permissions: string[]): boolean;
2737
+ /**
2738
+ * Returns the list of permissions that are **not** granted.
2739
+ *
2740
+ * @example
2741
+ * const missing = perms.missing('messages.write', 'members.kick')
2742
+ * if (missing.length) throw new Error(`Missing: ${missing.join(', ')}`)
2743
+ */
2744
+ missing(...permissions: string[]): string[];
2745
+ /**
2746
+ * Return a **new** `PermissionsBitfield` with the given permission granted.
2747
+ *
2748
+ * @example
2749
+ * const updated = perms.grant('channels.manage')
2750
+ */
2751
+ grant(...permissions: string[]): PermissionsBitfield;
2752
+ /**
2753
+ * Return a **new** `PermissionsBitfield` with the given permission denied.
2754
+ *
2755
+ * @example
2756
+ * const restricted = perms.deny('members.kick')
2757
+ */
2758
+ deny(...permissions: string[]): PermissionsBitfield;
2759
+ /**
2760
+ * Merge another record or `PermissionsBitfield` on top of this one.
2761
+ * The incoming values **override** any existing ones.
2762
+ *
2763
+ * @example
2764
+ * const merged = serverPerms.merge(channelOverrides)
2765
+ */
2766
+ merge(other: Record<string, boolean> | PermissionsBitfield): PermissionsBitfield;
2767
+ /**
2768
+ * Returns an array of the permission keys that are **currently granted**.
2769
+ *
2770
+ * @example
2771
+ * perms.toArray() // ['messages.read', 'messages.write']
2772
+ */
2773
+ toArray(): string[];
2774
+ /**
2775
+ * Returns the raw `Record<string, boolean>` map.
2776
+ */
2777
+ toRecord(): Record<string, boolean>;
2778
+ /**
2779
+ * Pretty-print the granted permissions.
2780
+ *
2781
+ * @example
2782
+ * console.log(String(perms)) // 'PermissionsBitfield[messages.read, messages.write]'
2783
+ */
2784
+ toString(): string;
2785
+ /** Create a `PermissionsBitfield` from an existing record. */
2786
+ static from(perms: Record<string, boolean>): PermissionsBitfield;
2787
+ /** A `PermissionsBitfield` with all known permissions granted. */
2788
+ static readonly ALL: PermissionsBitfield;
2789
+ /** A `PermissionsBitfield` with no permissions granted. */
2790
+ static readonly NONE: PermissionsBitfield;
1262
2791
  }
1263
2792
 
1264
- export { ActionRowBuilder, type Attachment, type BotApplication, type BotEvent, type BotEventType, type BotModalDefinition, type BotModalField, type BotPermissionRecord, type BotUser, ButtonBuilder, type ButtonStyle, CommandsAPI, type ContextCommandDefinition, type EditMessageOptions, type Embed, EmbedBuilder, type EmbedField, type FetchMembersOptions, type FetchMessagesOptions, HttpClient, type Interaction, InteractionOptions, type InteractionType, InteractionsAPI, type Member, MembersAPI, type Message, type MessageComponent, MessagesAPI, ModalBuilder, NovaClient, type NovaClientEvents, type NovaClientOptions, NovaInteraction, type NovaServer, PermissionsAPI, type PermissionsQueryOptions, type PermissionsResult, type PollInteractionsOptions, type PrefixCommandDefinition, type Reaction, type RegisteredContextCommand, type RegisteredPrefixCommand, type RegisteredSlashCommand, type RespondInteractionOptions, SelectMenuBuilder, type SelectMenuOption, type SendMessageOptions, ServersAPI, SlashCommandBuilder, type SlashCommandDefinition, type SlashCommandOption, SlashCommandOptionBuilder, TextInputBuilder, type TextInputStyle };
2793
+ /**
2794
+ * Returns a promise that resolves after `ms` milliseconds.
2795
+ * Useful in retry loops, cron tasks, and staged sends.
2796
+ *
2797
+ * @example
2798
+ * await sleep(2_000) // wait 2 seconds
2799
+ */
2800
+ declare function sleep(ms: number): Promise<void>;
2801
+ /**
2802
+ * Wraps a promise with a timeout — rejects with an error if the original
2803
+ * promise does not resolve within `ms` milliseconds.
2804
+ *
2805
+ * @example
2806
+ * const result = await withTimeout(fetchData(), 5_000, 'fetchData took too long')
2807
+ */
2808
+ declare function withTimeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T>;
2809
+ /**
2810
+ * Format a duration in milliseconds to a compact human-readable string.
2811
+ *
2812
+ * @example
2813
+ * formatDuration(0) // '0s'
2814
+ * formatDuration(90_000) // '1m 30s'
2815
+ * formatDuration(3_661_000) // '1h 1m 1s'
2816
+ * formatDuration(604_800_000) // '1w'
2817
+ */
2818
+ declare function formatDuration(ms: number): string;
2819
+ /**
2820
+ * Format a timestamp as a relative human-readable string from now.
2821
+ * Accepts a `Date`, a Unix timestamp (ms), or an ISO date string.
2822
+ *
2823
+ * @example
2824
+ * formatRelative(Date.now() - 5_000) // 'just now'
2825
+ * formatRelative(Date.now() - 90_000) // '1 minute ago'
2826
+ * formatRelative(Date.now() + 60_000) // 'in 1 minute'
2827
+ * formatRelative(Date.now() - 86_400_000) // 'yesterday'
2828
+ */
2829
+ declare function formatRelative(timestamp: Date | number | string): string;
2830
+ /**
2831
+ * Safely parse any timestamp (ISO string, Unix ms, or Date) to a `Date`.
2832
+ * Returns `null` if the input is nullish or not parseable.
2833
+ *
2834
+ * @example
2835
+ * parseTimestamp('2026-01-01T00:00:00.000Z') // Date
2836
+ * parseTimestamp(null) // null
2837
+ */
2838
+ declare function parseTimestamp(value: string | number | Date | null | undefined): Date | null;
2839
+ /**
2840
+ * Returns the remaining time until a target date as an object with components.
2841
+ * All values are `0` if the target is in the past.
2842
+ *
2843
+ * @example
2844
+ * const { days, hours, minutes, seconds } = countdown(new Date('2027-01-01'))
2845
+ * console.log(`${days}d ${hours}h ${minutes}m ${seconds}s remaining`)
2846
+ */
2847
+ declare function countdown(target: Date | number | string): {
2848
+ weeks: number;
2849
+ days: number;
2850
+ hours: number;
2851
+ minutes: number;
2852
+ seconds: number;
2853
+ total: number;
2854
+ };
2855
+
2856
+ 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 DirectMessage, 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, NovaChannel, NovaClient, type NovaClientEvents, type NovaClientOptions, NovaInteraction, NovaMember, NovaMessage, type NovaServer, Paginator, type PermissionKey, Permissions, PermissionsAPI, PermissionsBitfield, 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, type VoiceState, countdown, formatDuration, formatRelative, parseTimestamp, sleep, withTimeout };