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.d.mts +1001 -14
- package/dist/index.d.ts +1001 -14
- package/dist/index.js +1121 -13
- package/dist/index.mjs +1112 -13
- package/package.json +1 -1
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/
|
|
628
|
-
var
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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,
|