novaapp-sdk 1.1.0 → 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.js CHANGED
@@ -22,17 +22,26 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ActionRowBuilder: () => ActionRowBuilder,
24
24
  ButtonBuilder: () => ButtonBuilder,
25
+ ChannelsAPI: () => ChannelsAPI,
26
+ Collection: () => Collection,
25
27
  CommandsAPI: () => CommandsAPI,
28
+ Cooldown: () => Cooldown,
29
+ CooldownManager: () => CooldownManager,
26
30
  EmbedBuilder: () => EmbedBuilder,
27
31
  HttpClient: () => HttpClient,
28
32
  InteractionOptions: () => InteractionOptions,
29
33
  InteractionsAPI: () => InteractionsAPI,
34
+ Logger: () => Logger,
30
35
  MembersAPI: () => MembersAPI,
36
+ MessageBuilder: () => MessageBuilder,
31
37
  MessagesAPI: () => MessagesAPI,
32
38
  ModalBuilder: () => ModalBuilder,
33
39
  NovaClient: () => NovaClient,
34
40
  NovaInteraction: () => NovaInteraction,
41
+ NovaMessage: () => NovaMessage,
35
42
  PermissionsAPI: () => PermissionsAPI,
43
+ PollBuilder: () => PollBuilder,
44
+ ReactionsAPI: () => ReactionsAPI,
36
45
  SelectMenuBuilder: () => SelectMenuBuilder,
37
46
  ServersAPI: () => ServersAPI,
38
47
  SlashCommandBuilder: () => SlashCommandBuilder,
@@ -135,6 +144,70 @@ var MessagesAPI = class {
135
144
  typing(channelId) {
136
145
  return this.http.post(`/channels/${channelId}/typing`);
137
146
  }
147
+ /**
148
+ * Fetch a single message by ID.
149
+ *
150
+ * @example
151
+ * const msg = await client.messages.fetchOne('message-id')
152
+ */
153
+ fetchOne(messageId) {
154
+ return this.http.get(`/messages/${messageId}`);
155
+ }
156
+ /**
157
+ * Pin a message in its channel.
158
+ * The bot must have the `messages.manage` scope.
159
+ *
160
+ * @example
161
+ * await client.messages.pin('message-id')
162
+ */
163
+ pin(messageId) {
164
+ return this.http.post(`/messages/${messageId}/pin`);
165
+ }
166
+ /**
167
+ * Unpin a message.
168
+ *
169
+ * @example
170
+ * await client.messages.unpin('message-id')
171
+ */
172
+ unpin(messageId) {
173
+ return this.http.delete(`/messages/${messageId}/pin`);
174
+ }
175
+ /**
176
+ * Fetch all pinned messages in a channel.
177
+ *
178
+ * @example
179
+ * const pins = await client.messages.fetchPinned('channel-id')
180
+ */
181
+ fetchPinned(channelId) {
182
+ return this.http.get(`/channels/${channelId}/pins`);
183
+ }
184
+ /**
185
+ * Add a reaction to a message.
186
+ *
187
+ * @example
188
+ * await client.messages.addReaction('message-id', '👍')
189
+ */
190
+ addReaction(messageId, emoji) {
191
+ return this.http.post(`/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`);
192
+ }
193
+ /**
194
+ * Remove the bot's reaction from a message.
195
+ *
196
+ * @example
197
+ * await client.messages.removeReaction('message-id', '👍')
198
+ */
199
+ removeReaction(messageId, emoji) {
200
+ return this.http.delete(`/messages/${messageId}/reactions/${encodeURIComponent(emoji)}`);
201
+ }
202
+ /**
203
+ * Fetch all reactions on a message.
204
+ *
205
+ * @example
206
+ * const reactions = await client.messages.fetchReactions('message-id')
207
+ */
208
+ fetchReactions(messageId) {
209
+ return this.http.get(`/messages/${messageId}/reactions`);
210
+ }
138
211
  };
139
212
 
140
213
  // src/api/commands.ts
@@ -262,6 +335,61 @@ var MembersAPI = class {
262
335
  ban(serverId, userId, reason) {
263
336
  return this.http.post(`/servers/${serverId}/members/${userId}/ban`, { reason });
264
337
  }
338
+ /**
339
+ * Unban a previously banned user.
340
+ * Requires the `members.ban` scope.
341
+ *
342
+ * @example
343
+ * await client.members.unban('server-id', 'user-id')
344
+ */
345
+ unban(serverId, userId) {
346
+ return this.http.delete(`/servers/${serverId}/bans/${userId}`);
347
+ }
348
+ /**
349
+ * Fetch the ban list for a server.
350
+ * Requires the `members.ban` scope.
351
+ *
352
+ * @example
353
+ * const bans = await client.members.listBans('server-id')
354
+ * for (const ban of bans) {
355
+ * console.log(`${ban.username} — ${ban.reason ?? 'No reason'}`)
356
+ * }
357
+ */
358
+ listBans(serverId) {
359
+ return this.http.get(`/servers/${serverId}/bans`);
360
+ }
361
+ /**
362
+ * Send a direct message to a user (DM).
363
+ * Requires the `messages.write` scope.
364
+ *
365
+ * @example
366
+ * await client.members.dm('user-id', { content: 'Hello!' })
367
+ * await client.members.dm('user-id', 'Hello from the bot!')
368
+ */
369
+ dm(userId, options) {
370
+ const body = typeof options === "string" ? { content: options } : options;
371
+ return this.http.post(`/users/${userId}/dm`, body);
372
+ }
373
+ /**
374
+ * Add a role to a member.
375
+ * Requires the `members.roles` scope.
376
+ *
377
+ * @example
378
+ * await client.members.addRole('server-id', 'user-id', 'role-id')
379
+ */
380
+ addRole(serverId, userId, roleId) {
381
+ return this.http.post(`/servers/${serverId}/members/${userId}/roles/${roleId}`);
382
+ }
383
+ /**
384
+ * Remove a role from a member.
385
+ * Requires the `members.roles` scope.
386
+ *
387
+ * @example
388
+ * await client.members.removeRole('server-id', 'user-id', 'role-id')
389
+ */
390
+ removeRole(serverId, userId, roleId) {
391
+ return this.http.delete(`/servers/${serverId}/members/${userId}/roles/${roleId}`);
392
+ }
265
393
  };
266
394
 
267
395
  // src/api/servers.ts
@@ -279,6 +407,17 @@ var ServersAPI = class {
279
407
  list() {
280
408
  return this.http.get("/servers");
281
409
  }
410
+ /**
411
+ * Fetch all roles in a server.
412
+ * Results are sorted by position (lowest first).
413
+ *
414
+ * @example
415
+ * const roles = await client.servers.listRoles('server-id')
416
+ * const adminRole = roles.find(r => r.name === 'Admin')
417
+ */
418
+ listRoles(serverId) {
419
+ return this.http.get(`/servers/${serverId}/roles`);
420
+ }
282
421
  };
283
422
 
284
423
  // src/api/interactions.ts
@@ -445,6 +584,172 @@ var PermissionsAPI = class {
445
584
  }
446
585
  };
447
586
 
587
+ // src/api/channels.ts
588
+ var ChannelsAPI = class {
589
+ constructor(http) {
590
+ this.http = http;
591
+ }
592
+ /**
593
+ * Fetch all channels in a server the bot is a member of.
594
+ *
595
+ * @example
596
+ * const channels = await client.channels.list('server-id')
597
+ * const textChannels = channels.filter(c => c.type === 'TEXT')
598
+ */
599
+ list(serverId) {
600
+ return this.http.get(`/servers/${serverId}/channels`);
601
+ }
602
+ /**
603
+ * Fetch a single channel by ID.
604
+ *
605
+ * @example
606
+ * const channel = await client.channels.fetch('channel-id')
607
+ * console.log(channel.name, channel.type)
608
+ */
609
+ fetch(channelId) {
610
+ return this.http.get(`/channels/${channelId}`);
611
+ }
612
+ /**
613
+ * Create a new channel in a server.
614
+ * Requires the `channels.manage` scope.
615
+ *
616
+ * @example
617
+ * const channel = await client.channels.create('server-id', {
618
+ * name: 'announcements',
619
+ * type: 'ANNOUNCEMENT',
620
+ * topic: 'Official announcements only',
621
+ * })
622
+ */
623
+ create(serverId, options) {
624
+ return this.http.post(`/servers/${serverId}/channels`, options);
625
+ }
626
+ /**
627
+ * Edit an existing channel.
628
+ * Requires the `channels.manage` scope.
629
+ *
630
+ * @example
631
+ * await client.channels.edit('channel-id', { topic: 'New topic!' })
632
+ */
633
+ edit(channelId, options) {
634
+ return this.http.patch(`/channels/${channelId}`, options);
635
+ }
636
+ /**
637
+ * Delete a channel.
638
+ * Requires the `channels.manage` scope.
639
+ *
640
+ * @example
641
+ * await client.channels.delete('channel-id')
642
+ */
643
+ delete(channelId) {
644
+ return this.http.delete(`/channels/${channelId}`);
645
+ }
646
+ /**
647
+ * Fetch messages from a channel.
648
+ *
649
+ * @example
650
+ * const messages = await client.channels.fetchMessages('channel-id', { limit: 50 })
651
+ */
652
+ fetchMessages(channelId, options = {}) {
653
+ const params = new URLSearchParams();
654
+ if (options.limit) params.set("limit", String(options.limit));
655
+ if (options.before) params.set("before", options.before);
656
+ const qs = params.toString();
657
+ return this.http.get(`/channels/${channelId}/messages${qs ? `?${qs}` : ""}`);
658
+ }
659
+ /**
660
+ * Fetch all pinned messages in a channel.
661
+ *
662
+ * @example
663
+ * const pins = await client.channels.fetchPins('channel-id')
664
+ */
665
+ fetchPins(channelId) {
666
+ return this.http.get(`/channels/${channelId}/pins`);
667
+ }
668
+ /**
669
+ * Send a typing indicator in a channel.
670
+ * Displayed to users for ~5 seconds.
671
+ *
672
+ * @example
673
+ * await client.channels.startTyping('channel-id')
674
+ */
675
+ startTyping(channelId) {
676
+ return this.http.post(`/channels/${channelId}/typing`);
677
+ }
678
+ };
679
+
680
+ // src/api/reactions.ts
681
+ var ReactionsAPI = class {
682
+ constructor(http) {
683
+ this.http = http;
684
+ }
685
+ /**
686
+ * Add a reaction to a message.
687
+ * Use a plain emoji character or a custom emoji ID.
688
+ *
689
+ * @example
690
+ * await client.reactions.add('message-id', '👍')
691
+ * await client.reactions.add('message-id', '🎉')
692
+ */
693
+ add(messageId, emoji) {
694
+ const encoded = encodeURIComponent(emoji);
695
+ return this.http.post(`/messages/${messageId}/reactions/${encoded}`);
696
+ }
697
+ /**
698
+ * Remove the bot's reaction from a message.
699
+ *
700
+ * @example
701
+ * await client.reactions.remove('message-id', '👍')
702
+ */
703
+ remove(messageId, emoji) {
704
+ const encoded = encodeURIComponent(emoji);
705
+ return this.http.delete(`/messages/${messageId}/reactions/${encoded}`);
706
+ }
707
+ /**
708
+ * Remove all reactions from a message.
709
+ * Requires the `messages.manage` scope.
710
+ *
711
+ * @example
712
+ * await client.reactions.removeAll('message-id')
713
+ */
714
+ removeAll(messageId) {
715
+ return this.http.delete(`/messages/${messageId}/reactions`);
716
+ }
717
+ /**
718
+ * Remove all reactions of a specific emoji from a message.
719
+ * Requires the `messages.manage` scope.
720
+ *
721
+ * @example
722
+ * await client.reactions.removeEmoji('message-id', '👍')
723
+ */
724
+ removeEmoji(messageId, emoji) {
725
+ const encoded = encodeURIComponent(emoji);
726
+ return this.http.delete(`/messages/${messageId}/reactions/${encoded}/all`);
727
+ }
728
+ /**
729
+ * Fetch all reactions on a message, broken down by emoji.
730
+ *
731
+ * @example
732
+ * const reactions = await client.reactions.fetch('message-id')
733
+ * for (const r of reactions) {
734
+ * console.log(`${r.emoji} — ${r.count} reactions from ${r.users.map(u => u.username).join(', ')}`)
735
+ * }
736
+ */
737
+ fetch(messageId) {
738
+ return this.http.get(`/messages/${messageId}/reactions`);
739
+ }
740
+ /**
741
+ * Fetch reactions for a specific emoji on a message.
742
+ *
743
+ * @example
744
+ * const detail = await client.reactions.fetchEmoji('message-id', '👍')
745
+ * console.log(`${detail.count} thumbs ups`)
746
+ */
747
+ fetchEmoji(messageId, emoji) {
748
+ const encoded = encodeURIComponent(emoji);
749
+ return this.http.get(`/messages/${messageId}/reactions/${encoded}`);
750
+ }
751
+ };
752
+
448
753
  // src/structures/NovaInteraction.ts
449
754
  var InteractionOptions = class {
450
755
  constructor(data) {
@@ -624,23 +929,159 @@ var NovaInteraction = class _NovaInteraction {
624
929
  }
625
930
  };
626
931
 
627
- // src/client.ts
628
- var EVENT_MAP = {
629
- "message.created": "messageCreate",
630
- "message.edited": "messageUpdate",
631
- "message.deleted": "messageDelete",
632
- "message.reaction_added": "reactionAdd",
633
- "message.reaction_removed": "reactionRemove",
634
- "user.joined_server": "memberAdd",
635
- "user.left_server": "memberRemove",
636
- "user.started_typing": "typingStart"
932
+ // src/structures/NovaMessage.ts
933
+ var NovaMessage = class _NovaMessage {
934
+ constructor(raw, messages, reactions) {
935
+ this.id = raw.id;
936
+ this.content = raw.content;
937
+ this.channelId = raw.channelId;
938
+ this.author = raw.author;
939
+ this.embed = raw.embed ?? null;
940
+ this.components = raw.components ?? [];
941
+ this.replyToId = raw.replyToId ?? null;
942
+ this.attachments = raw.attachments ?? [];
943
+ this.reactions = raw.reactions ?? [];
944
+ this.createdAt = new Date(raw.createdAt);
945
+ this.editedAt = raw.editedAt ? new Date(raw.editedAt) : null;
946
+ this._messages = messages;
947
+ this._reactions = reactions;
948
+ }
949
+ // ─── Type guards ─────────────────────────────────────────────────────────────
950
+ /** Returns true if the message was sent by a bot. */
951
+ isFromBot() {
952
+ return this.author.isBot === true;
953
+ }
954
+ /** Returns true if the message has an embed. */
955
+ hasEmbed() {
956
+ return this.embed !== null;
957
+ }
958
+ /** Returns true if the message has interactive components. */
959
+ hasComponents() {
960
+ return this.components.length > 0;
961
+ }
962
+ /** Returns true if the message has been edited. */
963
+ isEdited() {
964
+ return this.editedAt !== null;
965
+ }
966
+ // ─── Reactions ────────────────────────────────────────────────────────────────
967
+ /**
968
+ * Add a reaction to this message.
969
+ *
970
+ * @example
971
+ * await msg.react('👍')
972
+ * await msg.react('🎉')
973
+ */
974
+ react(emoji) {
975
+ return this._reactions.add(this.id, emoji);
976
+ }
977
+ /**
978
+ * Remove the bot's reaction from this message.
979
+ *
980
+ * @example
981
+ * await msg.removeReaction('👍')
982
+ */
983
+ removeReaction(emoji) {
984
+ return this._reactions.remove(this.id, emoji);
985
+ }
986
+ // ─── Messaging ────────────────────────────────────────────────────────────────
987
+ /**
988
+ * Reply to this message.
989
+ *
990
+ * @example
991
+ * await msg.reply('Got it!')
992
+ * await msg.reply({ embed: new EmbedBuilder().setTitle('Result').toJSON() })
993
+ */
994
+ reply(options) {
995
+ const opts = typeof options === "string" ? { content: options, replyToId: this.id } : { ...options, replyToId: this.id };
996
+ return this._messages.send(this.channelId, opts).then((raw) => new _NovaMessage(raw, this._messages, this._reactions));
997
+ }
998
+ /**
999
+ * Edit this message.
1000
+ * Only works if the bot is the author.
1001
+ *
1002
+ * @example
1003
+ * await msg.edit('Updated content')
1004
+ * await msg.edit({ content: 'Updated', embed: { title: 'New embed' } })
1005
+ */
1006
+ edit(options) {
1007
+ const opts = typeof options === "string" ? { content: options } : options;
1008
+ return this._messages.edit(this.id, opts).then((raw) => new _NovaMessage(raw, this._messages, this._reactions));
1009
+ }
1010
+ /**
1011
+ * Delete this message.
1012
+ * Only works if the bot is the author.
1013
+ *
1014
+ * @example
1015
+ * await msg.delete()
1016
+ */
1017
+ delete() {
1018
+ return this._messages.delete(this.id);
1019
+ }
1020
+ /**
1021
+ * Pin this message in its channel.
1022
+ *
1023
+ * @example
1024
+ * await msg.pin()
1025
+ */
1026
+ pin() {
1027
+ return this._messages.pin(this.id);
1028
+ }
1029
+ /**
1030
+ * Unpin this message.
1031
+ *
1032
+ * @example
1033
+ * await msg.unpin()
1034
+ */
1035
+ unpin() {
1036
+ return this._messages.unpin(this.id);
1037
+ }
1038
+ /**
1039
+ * Re-fetch the latest version of this message from the server.
1040
+ *
1041
+ * @example
1042
+ * const fresh = await msg.fetch()
1043
+ */
1044
+ async fetch() {
1045
+ const raw = await this._messages.fetchOne(this.id);
1046
+ return new _NovaMessage(raw, this._messages, this._reactions);
1047
+ }
1048
+ /**
1049
+ * Get a URL to this message (deep link).
1050
+ */
1051
+ get url() {
1052
+ return `/channels/${this.channelId}/messages/${this.id}`;
1053
+ }
1054
+ /**
1055
+ * Return the raw message object.
1056
+ */
1057
+ toJSON() {
1058
+ return {
1059
+ id: this.id,
1060
+ content: this.content,
1061
+ channelId: this.channelId,
1062
+ author: this.author,
1063
+ embed: this.embed,
1064
+ components: this.components,
1065
+ replyToId: this.replyToId,
1066
+ attachments: this.attachments,
1067
+ reactions: this.reactions,
1068
+ createdAt: this.createdAt.toISOString(),
1069
+ editedAt: this.editedAt?.toISOString() ?? null
1070
+ };
1071
+ }
1072
+ toString() {
1073
+ return `NovaMessage(${this.id}): ${this.content.slice(0, 50)}${this.content.length > 50 ? "\u2026" : ""}`;
1074
+ }
637
1075
  };
1076
+
1077
+ // src/client.ts
638
1078
  var NovaClient = class extends import_node_events.EventEmitter {
639
1079
  constructor(options) {
640
1080
  super();
641
1081
  /** The authenticated bot application. Available after `ready` fires. */
642
1082
  this.botUser = null;
643
1083
  this.socket = null;
1084
+ this._cronTimers = [];
644
1085
  // ── Command / component routing ───────────────────────────────────────────────────
645
1086
  this._commandHandlers = /* @__PURE__ */ new Map();
646
1087
  this._buttonHandlers = /* @__PURE__ */ new Map();
@@ -662,6 +1103,8 @@ var NovaClient = class extends import_node_events.EventEmitter {
662
1103
  this.servers = new ServersAPI(this.http);
663
1104
  this.interactions = new InteractionsAPI(this.http, this);
664
1105
  this.permissions = new PermissionsAPI(this.http);
1106
+ this.channels = new ChannelsAPI(this.http);
1107
+ this.reactions = new ReactionsAPI(this.http);
665
1108
  this.on("error", () => {
666
1109
  });
667
1110
  const cleanup = () => this.disconnect();
@@ -766,9 +1209,36 @@ var NovaClient = class extends import_node_events.EventEmitter {
766
1209
  });
767
1210
  this.socket.on("bot:event", (event) => {
768
1211
  this.emit("event", event);
769
- const shorthand = EVENT_MAP[event.type];
770
- if (shorthand) {
771
- this.emit(shorthand, event.data);
1212
+ switch (event.type) {
1213
+ case "message.created":
1214
+ case "message.edited": {
1215
+ const raw = event.data;
1216
+ const wrapped = new NovaMessage(raw, this.messages, this.reactions);
1217
+ if (event.type === "message.created") this.emit("messageCreate", wrapped);
1218
+ else this.emit("messageUpdate", wrapped);
1219
+ break;
1220
+ }
1221
+ case "message.deleted":
1222
+ this.emit("messageDelete", event.data);
1223
+ break;
1224
+ case "message.reaction_added":
1225
+ this.emit("reactionAdd", event.data);
1226
+ break;
1227
+ case "message.reaction_removed":
1228
+ this.emit("reactionRemove", event.data);
1229
+ break;
1230
+ case "user.joined_server":
1231
+ this.emit("memberAdd", event.data);
1232
+ break;
1233
+ case "user.left_server":
1234
+ this.emit("memberRemove", event.data);
1235
+ break;
1236
+ case "user.started_typing":
1237
+ this.emit("typingStart", event.data);
1238
+ break;
1239
+ case "message.pinned":
1240
+ this.emit("messagePinned", event.data);
1241
+ break;
772
1242
  }
773
1243
  });
774
1244
  this.socket.on("bot:error", (err) => {
@@ -779,10 +1249,58 @@ var NovaClient = class extends import_node_events.EventEmitter {
779
1249
  });
780
1250
  });
781
1251
  }
1252
+ /**
1253
+ * Register a recurring task that fires at a set interval.
1254
+ * All cron tasks are automatically cancelled on `disconnect()`.
1255
+ *
1256
+ * @param intervalMs - How often to run the task (in milliseconds).
1257
+ * @param fn - Async or sync function to call on each tick.
1258
+ * @returns A cancel function — call it to stop this specific task.
1259
+ *
1260
+ * @example
1261
+ * // Check for new announcements every 30 seconds
1262
+ * client.cron(30_000, async () => {
1263
+ * const messages = await client.messages.fetch(channelId, { limit: 5 })
1264
+ * // do something...
1265
+ * })
1266
+ */
1267
+ cron(intervalMs, fn) {
1268
+ const id = setInterval(() => {
1269
+ try {
1270
+ const result = fn();
1271
+ if (result && typeof result.catch === "function") {
1272
+ ;
1273
+ result.catch((e) => this.emit("error", e));
1274
+ }
1275
+ } catch (e) {
1276
+ this.emit("error", e);
1277
+ }
1278
+ }, intervalMs);
1279
+ this._cronTimers.push(id);
1280
+ return () => {
1281
+ clearInterval(id);
1282
+ this._cronTimers = this._cronTimers.filter((t) => t !== id);
1283
+ };
1284
+ }
1285
+ /**
1286
+ * Set the bot's presence status.
1287
+ * Broadcasts to all servers the bot is in via WebSocket.
1288
+ *
1289
+ * @example
1290
+ * client.setStatus('DND') // Do Not Disturb
1291
+ * client.setStatus('IDLE') // Away
1292
+ * client.setStatus('OFFLINE') // Appear offline
1293
+ * client.setStatus('ONLINE') // Back online
1294
+ */
1295
+ setStatus(status) {
1296
+ this.socket?.emit("bot:status", { status });
1297
+ }
782
1298
  /**
783
1299
  * Disconnect from the gateway and clean up.
784
1300
  */
785
1301
  disconnect() {
1302
+ for (const id of this._cronTimers) clearInterval(id);
1303
+ this._cronTimers = [];
786
1304
  if (this.socket) {
787
1305
  const sock = this.socket;
788
1306
  this.socket = null;
@@ -1251,21 +1769,611 @@ var SlashCommandBuilder = class {
1251
1769
  return { ...this._data, options: [...this._data.options ?? []] };
1252
1770
  }
1253
1771
  };
1772
+
1773
+ // src/builders/PollBuilder.ts
1774
+ var PollBuilder = class {
1775
+ constructor() {
1776
+ this._question = "";
1777
+ this._options = [];
1778
+ this._allowMultiple = false;
1779
+ this._duration = null;
1780
+ this._anonymous = false;
1781
+ }
1782
+ /**
1783
+ * Set the poll question.
1784
+ *
1785
+ * @example
1786
+ * builder.setQuestion('Best programming language?')
1787
+ */
1788
+ setQuestion(question) {
1789
+ this._question = question;
1790
+ return this;
1791
+ }
1792
+ /**
1793
+ * Add a single answer option.
1794
+ *
1795
+ * @example
1796
+ * builder.addOption({ label: 'TypeScript', emoji: '🔷' })
1797
+ */
1798
+ addOption(option) {
1799
+ if (this._options.length >= 10) throw new Error("Polls can have at most 10 options");
1800
+ this._options.push(option);
1801
+ return this;
1802
+ }
1803
+ /**
1804
+ * Add multiple answer options at once.
1805
+ *
1806
+ * @example
1807
+ * builder.addOptions([
1808
+ * { label: 'Yes', emoji: '✅' },
1809
+ * { label: 'No', emoji: '❌' },
1810
+ * ])
1811
+ */
1812
+ addOptions(options) {
1813
+ for (const o of options) this.addOption(o);
1814
+ return this;
1815
+ }
1816
+ /**
1817
+ * Allow users to select more than one option.
1818
+ * Default: `false` (single choice).
1819
+ */
1820
+ setAllowMultiple(yes = true) {
1821
+ this._allowMultiple = yes;
1822
+ return this;
1823
+ }
1824
+ /**
1825
+ * Set the poll duration in **seconds**. Pass `null` to make it permanent.
1826
+ *
1827
+ * @example
1828
+ * builder.setDuration(60 * 60 * 24) // expires in 24 hours
1829
+ * builder.setDuration(null) // no expiry
1830
+ */
1831
+ setDuration(seconds) {
1832
+ this._duration = seconds;
1833
+ return this;
1834
+ }
1835
+ /**
1836
+ * Hide which users voted for which option.
1837
+ * Default: `false` (votes are public).
1838
+ */
1839
+ setAnonymous(yes = true) {
1840
+ this._anonymous = yes;
1841
+ return this;
1842
+ }
1843
+ /** Build the poll definition object. */
1844
+ toJSON() {
1845
+ if (!this._question.trim()) throw new Error("PollBuilder: question is required");
1846
+ if (this._options.length < 2) throw new Error("PollBuilder: at least 2 options are required");
1847
+ return {
1848
+ question: this._question,
1849
+ options: this._options,
1850
+ allowMultiple: this._allowMultiple,
1851
+ duration: this._duration,
1852
+ anonymous: this._anonymous
1853
+ };
1854
+ }
1855
+ };
1856
+
1857
+ // src/builders/MessageBuilder.ts
1858
+ var MessageBuilder = class {
1859
+ constructor() {
1860
+ this._components = [];
1861
+ }
1862
+ /**
1863
+ * Set the text content of the message.
1864
+ *
1865
+ * @example
1866
+ * builder.setContent('Hello world!')
1867
+ */
1868
+ setContent(content) {
1869
+ this._content = content;
1870
+ return this;
1871
+ }
1872
+ /**
1873
+ * Attach an embed. Accepts an `EmbedBuilder` or raw `Embed` object.
1874
+ *
1875
+ * @example
1876
+ * builder.setEmbed(new EmbedBuilder().setTitle('Result'))
1877
+ * builder.setEmbed({ title: 'Result', color: '#57F287' })
1878
+ */
1879
+ setEmbed(embed) {
1880
+ this._embed = "toJSON" in embed && typeof embed.toJSON === "function" ? embed.toJSON() : embed;
1881
+ return this;
1882
+ }
1883
+ /**
1884
+ * Remove any embed from this message.
1885
+ */
1886
+ clearEmbed() {
1887
+ this._embed = void 0;
1888
+ return this;
1889
+ }
1890
+ /**
1891
+ * Add an `ActionRowBuilder` (or raw component array) as a component row.
1892
+ *
1893
+ * @example
1894
+ * builder.addRow(
1895
+ * new ActionRowBuilder().addComponent(btn1).addComponent(btn2)
1896
+ * )
1897
+ */
1898
+ addRow(row) {
1899
+ const components = Array.isArray(row) ? row : row.toJSON();
1900
+ this._components.push(...components);
1901
+ return this;
1902
+ }
1903
+ /**
1904
+ * Replace all existing component rows.
1905
+ */
1906
+ setComponents(components) {
1907
+ this._components = components;
1908
+ return this;
1909
+ }
1910
+ /**
1911
+ * Set the message this is replying to.
1912
+ *
1913
+ * @example
1914
+ * builder.setReplyTo(message.id)
1915
+ */
1916
+ setReplyTo(messageId) {
1917
+ this._replyToId = messageId;
1918
+ return this;
1919
+ }
1920
+ /**
1921
+ * Attach a poll to the message.
1922
+ * Accepts a `PollBuilder` or raw `PollDefinition`.
1923
+ *
1924
+ * @example
1925
+ * builder.setPoll(
1926
+ * new PollBuilder()
1927
+ * .setQuestion('Favourite language?')
1928
+ * .addOptions([{ label: 'TypeScript' }, { label: 'Python' }])
1929
+ * )
1930
+ */
1931
+ setPoll(poll) {
1932
+ this._poll = "toJSON" in poll && typeof poll.toJSON === "function" ? poll.toJSON() : poll;
1933
+ return this;
1934
+ }
1935
+ /**
1936
+ * Build the message options object, ready to pass to `client.messages.send()`.
1937
+ *
1938
+ * @throws if neither `content` nor `embed` nor `poll` is set.
1939
+ */
1940
+ toJSON() {
1941
+ if (!this._content && !this._embed && !this._poll) {
1942
+ throw new Error("MessageBuilder: message must have content, an embed, or a poll");
1943
+ }
1944
+ return {
1945
+ ...this._content !== void 0 ? { content: this._content } : {},
1946
+ ...this._embed !== void 0 ? { embed: this._embed } : {},
1947
+ ...this._components.length > 0 ? { components: this._components } : {},
1948
+ ...this._replyToId !== void 0 ? { replyToId: this._replyToId } : {},
1949
+ ...this._poll !== void 0 ? { poll: this._poll } : {}
1950
+ };
1951
+ }
1952
+ };
1953
+
1954
+ // src/utils/Collection.ts
1955
+ var Collection = class _Collection extends Map {
1956
+ /**
1957
+ * The first value stored (insertion order), or `undefined` if empty.
1958
+ */
1959
+ first() {
1960
+ return this.values().next().value;
1961
+ }
1962
+ /**
1963
+ * The first `n` values stored (insertion order).
1964
+ */
1965
+ firstN(n) {
1966
+ const result = [];
1967
+ for (const v of this.values()) {
1968
+ if (result.length >= n) break;
1969
+ result.push(v);
1970
+ }
1971
+ return result;
1972
+ }
1973
+ /**
1974
+ * The last value stored, or `undefined` if empty.
1975
+ */
1976
+ last() {
1977
+ let last;
1978
+ for (const v of this.values()) last = v;
1979
+ return last;
1980
+ }
1981
+ /**
1982
+ * The last `n` values stored (insertion order).
1983
+ */
1984
+ lastN(n) {
1985
+ return this.toArray().slice(-n);
1986
+ }
1987
+ /**
1988
+ * Returns a random value from the collection, or `undefined` if empty.
1989
+ */
1990
+ random() {
1991
+ const arr = this.toArray();
1992
+ return arr[Math.floor(Math.random() * arr.length)];
1993
+ }
1994
+ /**
1995
+ * Find the first value that passes the predicate.
1996
+ *
1997
+ * @example
1998
+ * const bot = members.find(m => m.user.isBot)
1999
+ */
2000
+ find(fn) {
2001
+ for (const [k, v] of this) {
2002
+ if (fn(v, k, this)) return v;
2003
+ }
2004
+ return void 0;
2005
+ }
2006
+ /**
2007
+ * Find the key of the first value that passes the predicate.
2008
+ */
2009
+ findKey(fn) {
2010
+ for (const [k, v] of this) {
2011
+ if (fn(v, k, this)) return k;
2012
+ }
2013
+ return void 0;
2014
+ }
2015
+ /**
2016
+ * Returns `true` if at least one value satisfies the predicate.
2017
+ */
2018
+ some(fn) {
2019
+ for (const [k, v] of this) {
2020
+ if (fn(v, k, this)) return true;
2021
+ }
2022
+ return false;
2023
+ }
2024
+ /**
2025
+ * Returns `true` if every value satisfies the predicate.
2026
+ */
2027
+ every(fn) {
2028
+ for (const [k, v] of this) {
2029
+ if (!fn(v, k, this)) return false;
2030
+ }
2031
+ return true;
2032
+ }
2033
+ /**
2034
+ * Filter to a new Collection containing only values that pass the predicate.
2035
+ *
2036
+ * @example
2037
+ * const admins = members.filter(m => m.role === 'ADMIN')
2038
+ */
2039
+ filter(fn) {
2040
+ const result = new _Collection();
2041
+ for (const [k, v] of this) {
2042
+ if (fn(v, k, this)) result.set(k, v);
2043
+ }
2044
+ return result;
2045
+ }
2046
+ /**
2047
+ * Map each value to a new array.
2048
+ *
2049
+ * @example
2050
+ * const names = members.map(m => m.user.username)
2051
+ */
2052
+ map(fn) {
2053
+ const result = [];
2054
+ for (const [k, v] of this) result.push(fn(v, k, this));
2055
+ return result;
2056
+ }
2057
+ /**
2058
+ * Map to a new Collection with transformed values.
2059
+ *
2060
+ * @example
2061
+ * const names = channels.mapValues(c => c.name.toUpperCase())
2062
+ */
2063
+ mapValues(fn) {
2064
+ const result = new _Collection();
2065
+ for (const [k, v] of this) result.set(k, fn(v, k, this));
2066
+ return result;
2067
+ }
2068
+ /**
2069
+ * Reduce the collection to a single value.
2070
+ *
2071
+ * @example
2072
+ * const totalReactions = messages.reduce((sum, m) => sum + m.reactions.length, 0)
2073
+ */
2074
+ reduce(fn, initial) {
2075
+ let acc = initial;
2076
+ for (const [k, v] of this) acc = fn(acc, v, k, this);
2077
+ return acc;
2078
+ }
2079
+ /**
2080
+ * Returns values as an array (insertion order).
2081
+ */
2082
+ toArray() {
2083
+ return [...this.values()];
2084
+ }
2085
+ /**
2086
+ * Returns keys as an array (insertion order).
2087
+ */
2088
+ keyArray() {
2089
+ return [...this.keys()];
2090
+ }
2091
+ /**
2092
+ * Sort and return a new Collection.
2093
+ * Callback works like `Array.prototype.sort`.
2094
+ *
2095
+ * @example
2096
+ * const sorted = channels.sort((a, b) => a.position - b.position)
2097
+ */
2098
+ sort(fn) {
2099
+ const entries = [...this.entries()].sort(
2100
+ ([ak, av], [bk, bv]) => fn ? fn(av, bv, ak, bk) : 0
2101
+ );
2102
+ return new _Collection(entries);
2103
+ }
2104
+ /**
2105
+ * Split into two Collections based on a predicate.
2106
+ * Returns `[passing, failing]`.
2107
+ *
2108
+ * @example
2109
+ * const [bots, humans] = members.partition(m => m.user.isBot)
2110
+ */
2111
+ partition(fn) {
2112
+ const pass = new _Collection();
2113
+ const fail = new _Collection();
2114
+ for (const [k, v] of this) {
2115
+ if (fn(v, k, this)) pass.set(k, v);
2116
+ else fail.set(k, v);
2117
+ }
2118
+ return [pass, fail];
2119
+ }
2120
+ /**
2121
+ * Merge this collection with one or more others.
2122
+ * Later collections overwrite duplicate keys.
2123
+ */
2124
+ merge(...others) {
2125
+ const result = new _Collection(this);
2126
+ for (const other of others) {
2127
+ for (const [k, v] of other) result.set(k, v);
2128
+ }
2129
+ return result;
2130
+ }
2131
+ /**
2132
+ * Serialize to a plain `Record` (requires string keys).
2133
+ */
2134
+ toJSON() {
2135
+ const obj = {};
2136
+ for (const [k, v] of this) obj[String(k)] = v;
2137
+ return obj;
2138
+ }
2139
+ toString() {
2140
+ return `Collection(${this.size})`;
2141
+ }
2142
+ };
2143
+
2144
+ // src/utils/Cooldown.ts
2145
+ var Cooldown = class {
2146
+ /**
2147
+ * @param durationMs - Default cooldown duration in milliseconds.
2148
+ */
2149
+ constructor(durationMs) {
2150
+ this._durations = /* @__PURE__ */ new Map();
2151
+ this._last = /* @__PURE__ */ new Map();
2152
+ this._defaultMs = durationMs;
2153
+ }
2154
+ /**
2155
+ * Check remaining cooldown for a user.
2156
+ * Returns `0` if they are not on cooldown (free to proceed),
2157
+ * or the **milliseconds remaining** if they are on cooldown.
2158
+ *
2159
+ * @example
2160
+ * const ms = cooldown.check(userId)
2161
+ * if (ms > 0) return interaction.replyEphemeral(`Wait ${Cooldown.format(ms)}!`)
2162
+ */
2163
+ check(userId) {
2164
+ const last = this._last.get(userId);
2165
+ if (last === void 0) return 0;
2166
+ const duration = this._durations.get(userId) ?? this._defaultMs;
2167
+ const remaining = duration - (Date.now() - last);
2168
+ return remaining > 0 ? remaining : 0;
2169
+ }
2170
+ /**
2171
+ * Mark a user as having just used the command, starting their cooldown.
2172
+ * Optionally override the cooldown duration for this specific user.
2173
+ *
2174
+ * @example
2175
+ * cooldown.use(userId) // uses default duration
2176
+ * cooldown.use(userId, 10_000) // 10 second cooldown for this use
2177
+ */
2178
+ use(userId, durationMs) {
2179
+ this._last.set(userId, Date.now());
2180
+ if (durationMs !== void 0) this._durations.set(userId, durationMs);
2181
+ }
2182
+ /**
2183
+ * Immediately reset (clear) the cooldown for a user.
2184
+ *
2185
+ * @example
2186
+ * cooldown.reset(userId) // user can use the command again immediately
2187
+ */
2188
+ reset(userId) {
2189
+ this._last.delete(userId);
2190
+ this._durations.delete(userId);
2191
+ }
2192
+ /**
2193
+ * Reset all active cooldowns.
2194
+ */
2195
+ resetAll() {
2196
+ this._last.clear();
2197
+ this._durations.clear();
2198
+ }
2199
+ /**
2200
+ * Returns a list of all users currently on cooldown.
2201
+ *
2202
+ * @example
2203
+ * const active = cooldown.activeCooldowns()
2204
+ * // [{ userId: 'abc', remainingMs: 3200 }, ...]
2205
+ */
2206
+ activeCooldowns() {
2207
+ const result = [];
2208
+ for (const [userId] of this._last) {
2209
+ const ms = this.check(userId);
2210
+ if (ms > 0) result.push({ userId, remainingMs: ms });
2211
+ }
2212
+ return result;
2213
+ }
2214
+ /**
2215
+ * Format milliseconds into a human-readable string.
2216
+ *
2217
+ * @example
2218
+ * Cooldown.format(3_600_000) // '1h 0m 0s'
2219
+ * Cooldown.format(90_000) // '1m 30s'
2220
+ * Cooldown.format(4_000) // '4s'
2221
+ */
2222
+ static format(ms) {
2223
+ const totalSecs = Math.ceil(ms / 1e3);
2224
+ const hours = Math.floor(totalSecs / 3600);
2225
+ const mins = Math.floor(totalSecs % 3600 / 60);
2226
+ const secs = totalSecs % 60;
2227
+ if (hours > 0) return `${hours}h ${mins}m ${secs}s`;
2228
+ if (mins > 0) return `${mins}m ${secs}s`;
2229
+ return `${secs}s`;
2230
+ }
2231
+ };
2232
+ var CooldownManager = class {
2233
+ constructor() {
2234
+ this._map = /* @__PURE__ */ new Map();
2235
+ }
2236
+ /**
2237
+ * Get or create a named cooldown.
2238
+ */
2239
+ get(name, durationMs = 3e3) {
2240
+ if (!this._map.has(name)) this._map.set(name, new Cooldown(durationMs));
2241
+ return this._map.get(name);
2242
+ }
2243
+ /**
2244
+ * Check a named cooldown for a user.
2245
+ * Creates the cooldown if it doesn't exist yet.
2246
+ * Returns `0` if free, or remaining ms if on cooldown.
2247
+ *
2248
+ * @example
2249
+ * const ms = cooldowns.check('ban', userId, 30_000)
2250
+ */
2251
+ check(name, userId, durationMs = 3e3) {
2252
+ return this.get(name, durationMs).check(userId);
2253
+ }
2254
+ /**
2255
+ * Mark a user as having used a named command.
2256
+ *
2257
+ * @example
2258
+ * cooldowns.use('ban', userId, 30_000)
2259
+ */
2260
+ use(name, userId, durationMs) {
2261
+ this.get(name, durationMs).use(userId, durationMs);
2262
+ }
2263
+ /**
2264
+ * Reset a user's cooldown on a named command.
2265
+ */
2266
+ reset(name, userId) {
2267
+ this._map.get(name)?.reset(userId);
2268
+ }
2269
+ /**
2270
+ * Reset all cooldowns for all commands.
2271
+ */
2272
+ resetAll() {
2273
+ for (const c of this._map.values()) c.resetAll();
2274
+ }
2275
+ };
2276
+
2277
+ // src/utils/Logger.ts
2278
+ var Logger = class {
2279
+ /**
2280
+ * @param name - Name shown in square brackets before every message.
2281
+ * @param enableDebug - When `false` (default), `.debug()` calls are silent.
2282
+ */
2283
+ constructor(name, enableDebug = false) {
2284
+ this._prefix = name;
2285
+ this._debug = enableDebug;
2286
+ }
2287
+ /** Enable or disable debug output at runtime. */
2288
+ setDebug(enabled) {
2289
+ this._debug = enabled;
2290
+ return this;
2291
+ }
2292
+ _timestamp() {
2293
+ return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
2294
+ }
2295
+ _fmt(level, color, ...args) {
2296
+ const ts = this._timestamp();
2297
+ const tag = `\x1B[90m${ts}\x1B[0m ${color}[${level}]\x1B[0m \x1B[36m[${this._prefix}]\x1B[0m`;
2298
+ return `${tag} ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}`;
2299
+ }
2300
+ /** General info message. */
2301
+ info(...args) {
2302
+ console.log(this._fmt("INFO ", "\x1B[34m", ...args));
2303
+ }
2304
+ /** Warning — something unexpected but non-fatal. */
2305
+ warn(...args) {
2306
+ console.warn(this._fmt("WARN ", "\x1B[33m", ...args));
2307
+ }
2308
+ /** Error — something went wrong. */
2309
+ error(...args) {
2310
+ console.error(this._fmt("ERROR", "\x1B[31m", ...args));
2311
+ }
2312
+ /** Success / positive confirmation. */
2313
+ success(...args) {
2314
+ console.log(this._fmt("OK ", "\x1B[32m", ...args));
2315
+ }
2316
+ /** Debug — only printed when `enableDebug` is true. */
2317
+ debug(...args) {
2318
+ if (this._debug) console.log(this._fmt("DEBUG", "\x1B[35m", ...args));
2319
+ }
2320
+ /**
2321
+ * Log an incoming command interaction.
2322
+ *
2323
+ * @example
2324
+ * log.command('ping', interaction.userId)
2325
+ * // [MyBot] [CMD] /ping ← user:abc123
2326
+ */
2327
+ command(name, userId) {
2328
+ console.log(this._fmt("CMD ", "\x1B[36m", `/${name} \x1B[90m\u2190 user:${userId}`));
2329
+ }
2330
+ /**
2331
+ * Log a gateway event.
2332
+ *
2333
+ * @example
2334
+ * log.event('messageCreate')
2335
+ */
2336
+ event(type, extra = "") {
2337
+ console.log(this._fmt("EVT ", "\x1B[35m", type + (extra ? ` \x1B[90m${extra}` : "")));
2338
+ }
2339
+ /**
2340
+ * Log that a command handler threw an error.
2341
+ *
2342
+ * @example
2343
+ * log.commandError('ban', err)
2344
+ */
2345
+ commandError(name, err) {
2346
+ const msg = err instanceof Error ? err.message : String(err);
2347
+ console.error(this._fmt("ERROR", "\x1B[31m", `${name} threw: ${msg}`));
2348
+ if (err instanceof Error && err.stack) {
2349
+ console.error("\x1B[90m" + err.stack + "\x1B[0m");
2350
+ }
2351
+ }
2352
+ };
1254
2353
  // Annotate the CommonJS export names for ESM import in node:
1255
2354
  0 && (module.exports = {
1256
2355
  ActionRowBuilder,
1257
2356
  ButtonBuilder,
2357
+ ChannelsAPI,
2358
+ Collection,
1258
2359
  CommandsAPI,
2360
+ Cooldown,
2361
+ CooldownManager,
1259
2362
  EmbedBuilder,
1260
2363
  HttpClient,
1261
2364
  InteractionOptions,
1262
2365
  InteractionsAPI,
2366
+ Logger,
1263
2367
  MembersAPI,
2368
+ MessageBuilder,
1264
2369
  MessagesAPI,
1265
2370
  ModalBuilder,
1266
2371
  NovaClient,
1267
2372
  NovaInteraction,
2373
+ NovaMessage,
1268
2374
  PermissionsAPI,
2375
+ PollBuilder,
2376
+ ReactionsAPI,
1269
2377
  SelectMenuBuilder,
1270
2378
  ServersAPI,
1271
2379
  SlashCommandBuilder,