novaapp-sdk 1.4.0 → 1.4.1

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.mjs CHANGED
@@ -355,6 +355,74 @@ var MembersAPI = class {
355
355
  removeRole(serverId, userId, roleId) {
356
356
  return this.http.delete(`/servers/${serverId}/members/${userId}/roles/${roleId}`);
357
357
  }
358
+ /**
359
+ * Issue a warning to a member.
360
+ * Requires the `members.moderate` scope.
361
+ *
362
+ * @example
363
+ * await client.members.warn('server-id', 'user-id', 'Spamming')
364
+ */
365
+ warn(serverId, userId, reason) {
366
+ return this.http.post(`/servers/${serverId}/members/${userId}/warn`, { reason });
367
+ }
368
+ /**
369
+ * Fetch warnings for a server (or optionally a specific user).
370
+ * Requires the `members.moderate` scope.
371
+ *
372
+ * @example
373
+ * const all = await client.members.fetchWarnings('server-id')
374
+ * const userWarnings = await client.members.fetchWarnings('server-id', { userId: 'user-id' })
375
+ */
376
+ fetchWarnings(serverId, options = {}) {
377
+ const params = new URLSearchParams();
378
+ if (options.userId) params.set("userId", options.userId);
379
+ const qs = params.toString() ? `?${params.toString()}` : "";
380
+ return this.http.get(`/servers/${serverId}/warnings${qs}`);
381
+ }
382
+ /**
383
+ * Remove a warning by its ID.
384
+ * Requires the `members.moderate` scope.
385
+ *
386
+ * @example
387
+ * await client.members.removeWarning('warning-id')
388
+ */
389
+ removeWarning(warningId) {
390
+ return this.http.delete(`/warnings/${warningId}`);
391
+ }
392
+ /**
393
+ * Get a member's XP and level.
394
+ * Requires the `members.read` scope.
395
+ *
396
+ * @example
397
+ * const xpData = await client.members.getXP('server-id', 'user-id')
398
+ * console.log(`Level ${xpData.level} — ${xpData.xp} XP`)
399
+ */
400
+ getXP(serverId, userId) {
401
+ return this.http.get(`/servers/${serverId}/members/${userId}/xp`);
402
+ }
403
+ /**
404
+ * Set or add XP to a member.
405
+ * Requires the `members.manage` scope.
406
+ *
407
+ * @example
408
+ * // Add 50 XP
409
+ * await client.members.setXP('server-id', 'user-id', { add: 50 })
410
+ * // Set XP to a specific value
411
+ * await client.members.setXP('server-id', 'user-id', { xp: 1000 })
412
+ */
413
+ setXP(serverId, userId, options) {
414
+ return this.http.patch(`/servers/${serverId}/members/${userId}/xp`, options);
415
+ }
416
+ /**
417
+ * Get the XP leaderboard for a server.
418
+ * Requires the `members.read` scope.
419
+ *
420
+ * @example
421
+ * const top10 = await client.members.leaderboard('server-id', 10)
422
+ */
423
+ leaderboard(serverId, limit = 10) {
424
+ return this.http.get(`/servers/${serverId}/leaderboard?limit=${limit}`);
425
+ }
358
426
  };
359
427
 
360
428
  // src/api/servers.ts
@@ -404,6 +472,34 @@ var ServersAPI = class {
404
472
  listRoles(serverId) {
405
473
  return this.http.get(`/servers/${serverId}/roles`);
406
474
  }
475
+ /**
476
+ * Fetch all channel categories in a server.
477
+ *
478
+ * @example
479
+ * const categories = await client.servers.listCategories('server-id')
480
+ */
481
+ listCategories(serverId) {
482
+ return this.http.get(`/servers/${serverId}/categories`);
483
+ }
484
+ /**
485
+ * Get server statistics (member count, message count, etc.).
486
+ *
487
+ * @example
488
+ * const stats = await client.servers.getStats('server-id')
489
+ * console.log(`${stats.onlineCount} online / ${stats.memberCount} total`)
490
+ */
491
+ getStats(serverId) {
492
+ return this.http.get(`/servers/${serverId}/stats`);
493
+ }
494
+ /**
495
+ * Fetch all soundboard clips uploaded to a server.
496
+ *
497
+ * @example
498
+ * const clips = await client.servers.listSoundboard('server-id')
499
+ */
500
+ listSoundboard(serverId) {
501
+ return this.http.get(`/servers/${serverId}/soundboard`);
502
+ }
407
503
  };
408
504
 
409
505
  // src/api/interactions.ts
@@ -681,6 +777,18 @@ var ChannelsAPI = class {
681
777
  startTyping(channelId) {
682
778
  return this.http.post(`/channels/${channelId}/typing`);
683
779
  }
780
+ /**
781
+ * Bulk delete up to 100 messages from a channel at once.
782
+ * Requires the `messages.manage` scope.
783
+ * Soft-deletes all specified messages in a single operation.
784
+ *
785
+ * @example
786
+ * const result = await client.channels.bulkDelete('channel-id', ['msg1', 'msg2', 'msg3'])
787
+ * console.log(`Deleted ${result.deleted} messages`)
788
+ */
789
+ bulkDelete(channelId, messageIds) {
790
+ return this.http.post(`/channels/${channelId}/bulk-delete`, { messageIds });
791
+ }
684
792
  };
685
793
 
686
794
  // src/api/reactions.ts
@@ -989,6 +1097,224 @@ var AuditLogAPI = class {
989
1097
  }
990
1098
  };
991
1099
 
1100
+ // src/api/forum.ts
1101
+ var ForumAPI = class {
1102
+ constructor(http) {
1103
+ this.http = http;
1104
+ }
1105
+ /**
1106
+ * List forum posts in a FORUM channel.
1107
+ *
1108
+ * @example
1109
+ * const posts = await client.forum.list('channel-id')
1110
+ */
1111
+ async list(channelId, options = {}) {
1112
+ const params = new URLSearchParams();
1113
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
1114
+ if (options.before) params.set("before", options.before);
1115
+ const qs = params.toString() ? `?${params.toString()}` : "";
1116
+ return this.http.get(`/channels/${channelId}/forum-posts${qs}`);
1117
+ }
1118
+ /**
1119
+ * Create a forum post in a FORUM channel.
1120
+ *
1121
+ * @example
1122
+ * const post = await client.forum.create('channel-id', {
1123
+ * title: 'Ideas thread',
1124
+ * body: 'Post your ideas here!',
1125
+ * tags: ['idea', 'discussion'],
1126
+ * })
1127
+ */
1128
+ async create(channelId, options) {
1129
+ return this.http.post(`/channels/${channelId}/forum-posts`, options);
1130
+ }
1131
+ /**
1132
+ * Edit a forum post.
1133
+ * The bot must be the author of the post.
1134
+ *
1135
+ * @example
1136
+ * await client.forum.edit('post-id', { closed: true })
1137
+ */
1138
+ async edit(postId, options) {
1139
+ return this.http.patch(`/forum-posts/${postId}`, options);
1140
+ }
1141
+ /**
1142
+ * Delete a forum post.
1143
+ * The bot must be the author of the post.
1144
+ *
1145
+ * @example
1146
+ * await client.forum.delete('post-id')
1147
+ */
1148
+ async delete(postId) {
1149
+ await this.http.delete(`/forum-posts/${postId}`);
1150
+ }
1151
+ };
1152
+
1153
+ // src/api/events.ts
1154
+ var EventsAPI = class {
1155
+ constructor(http) {
1156
+ this.http = http;
1157
+ }
1158
+ /**
1159
+ * List server events.
1160
+ *
1161
+ * @example
1162
+ * const events = await client.events.list('server-id', { upcoming: true })
1163
+ */
1164
+ async list(serverId, options = {}) {
1165
+ const params = new URLSearchParams();
1166
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
1167
+ if (options.upcoming) params.set("upcoming", "true");
1168
+ const qs = params.toString() ? `?${params.toString()}` : "";
1169
+ return this.http.get(`/servers/${serverId}/events${qs}`);
1170
+ }
1171
+ /**
1172
+ * Fetch a single server event by ID.
1173
+ * Returns full attendee list.
1174
+ *
1175
+ * @example
1176
+ * const event = await client.events.fetch('event-id')
1177
+ */
1178
+ async fetch(eventId) {
1179
+ return this.http.get(`/events/${eventId}`);
1180
+ }
1181
+ /**
1182
+ * Create a server event.
1183
+ *
1184
+ * @example
1185
+ * const event = await client.events.create('server-id', {
1186
+ * title: 'Game Night',
1187
+ * description: 'Friday fun!',
1188
+ * startAt: new Date(Date.now() + 86400_000).toISOString(),
1189
+ * })
1190
+ */
1191
+ async create(serverId, options) {
1192
+ return this.http.post(`/servers/${serverId}/events`, options);
1193
+ }
1194
+ /**
1195
+ * Edit a server event.
1196
+ *
1197
+ * @example
1198
+ * await client.events.edit('event-id', { title: 'Game Night v2' })
1199
+ */
1200
+ async edit(eventId, options) {
1201
+ return this.http.patch(`/events/${eventId}`, options);
1202
+ }
1203
+ /**
1204
+ * Delete a server event.
1205
+ *
1206
+ * @example
1207
+ * await client.events.delete('event-id')
1208
+ */
1209
+ async delete(eventId) {
1210
+ await this.http.delete(`/events/${eventId}`);
1211
+ }
1212
+ };
1213
+
1214
+ // src/api/categories.ts
1215
+ var CategoriesAPI = class {
1216
+ constructor(http) {
1217
+ this.http = http;
1218
+ }
1219
+ /**
1220
+ * List all channel categories in a server, ordered by position.
1221
+ *
1222
+ * @example
1223
+ * const cats = await client.categories.list('server-id')
1224
+ */
1225
+ async list(serverId) {
1226
+ return this.http.get(`/servers/${serverId}/categories`);
1227
+ }
1228
+ /**
1229
+ * Create a new channel category.
1230
+ *
1231
+ * @example
1232
+ * const cat = await client.categories.create('server-id', { name: 'General', position: 0 })
1233
+ */
1234
+ async create(serverId, options) {
1235
+ return this.http.post(`/servers/${serverId}/categories`, options);
1236
+ }
1237
+ /**
1238
+ * Edit an existing channel category.
1239
+ *
1240
+ * @example
1241
+ * await client.categories.edit('cat-id', { name: 'Renamed' })
1242
+ */
1243
+ async edit(categoryId, options) {
1244
+ return this.http.patch(`/categories/${categoryId}`, options);
1245
+ }
1246
+ /**
1247
+ * Delete a channel category.
1248
+ *
1249
+ * @example
1250
+ * await client.categories.delete('cat-id')
1251
+ */
1252
+ async delete(categoryId) {
1253
+ await this.http.delete(`/categories/${categoryId}`);
1254
+ }
1255
+ };
1256
+
1257
+ // src/api/automod.ts
1258
+ var AutoModAPI = class {
1259
+ constructor(http) {
1260
+ this.http = http;
1261
+ }
1262
+ /**
1263
+ * List all automod rules for a server.
1264
+ *
1265
+ * @example
1266
+ * const rules = await client.automod.list('server-id')
1267
+ */
1268
+ async list(serverId) {
1269
+ return this.http.get(`/servers/${serverId}/automod`);
1270
+ }
1271
+ /**
1272
+ * Create a new automod rule.
1273
+ *
1274
+ * @example
1275
+ * await client.automod.create('server-id', { type: 'BLOCKED_WORD', value: 'badword' })
1276
+ * await client.automod.create('server-id', { type: 'BLOCKED_LINK', value: 'example.com' })
1277
+ */
1278
+ async create(serverId, options) {
1279
+ return this.http.post(`/servers/${serverId}/automod`, options);
1280
+ }
1281
+ /**
1282
+ * Enable / disable a rule or update the blocked value.
1283
+ *
1284
+ * @example
1285
+ * await client.automod.edit('rule-id', { enabled: false })
1286
+ */
1287
+ async edit(ruleId, options) {
1288
+ return this.http.patch(`/automod/${ruleId}`, options);
1289
+ }
1290
+ /**
1291
+ * Delete an automod rule.
1292
+ *
1293
+ * @example
1294
+ * await client.automod.delete('rule-id')
1295
+ */
1296
+ async delete(ruleId) {
1297
+ await this.http.delete(`/automod/${ruleId}`);
1298
+ }
1299
+ };
1300
+
1301
+ // src/api/users.ts
1302
+ var UsersAPI = class {
1303
+ constructor(http) {
1304
+ this.http = http;
1305
+ }
1306
+ /**
1307
+ * Fetch a user's public profile by ID.
1308
+ *
1309
+ * @example
1310
+ * const user = await client.users.fetch('user-id')
1311
+ * console.log(user.displayName)
1312
+ */
1313
+ async fetch(userId) {
1314
+ return this.http.get(`/users/${userId}`);
1315
+ }
1316
+ };
1317
+
992
1318
  // src/structures/NovaInteraction.ts
993
1319
  var InteractionOptions = class {
994
1320
  constructor(data) {
@@ -1313,6 +1639,63 @@ var NovaMessage = class _NovaMessage {
1313
1639
  const raw = await this._messages.fetchOne(this.id);
1314
1640
  return new _NovaMessage(raw, this._messages, this._reactions);
1315
1641
  }
1642
+ /**
1643
+ * Forward this message's content (and embed if present) to another channel.
1644
+ * Creates a new message in the target channel.
1645
+ *
1646
+ * @example
1647
+ * const forwarded = await msg.forward('target-channel-id')
1648
+ * console.log('Forwarded to:', forwarded.channelId)
1649
+ */
1650
+ async forward(channelId) {
1651
+ const opts = {
1652
+ content: this.content || void 0,
1653
+ ...this.embed ? { embed: this.embed } : {}
1654
+ };
1655
+ const raw = await this._messages.send(channelId, opts);
1656
+ return new _NovaMessage(raw, this._messages, this._reactions);
1657
+ }
1658
+ /**
1659
+ * Fetch detailed reaction data for a specific emoji on this message.
1660
+ * Returns the users who reacted with that emoji.
1661
+ *
1662
+ * @example
1663
+ * const reactors = await msg.fetchReactionDetails('👍')
1664
+ * for (const r of reactors) console.log(r.username, 'reacted 👍')
1665
+ */
1666
+ fetchReactionDetails(emoji) {
1667
+ return this._reactions.fetchEmoji(this.id, emoji);
1668
+ }
1669
+ /**
1670
+ * Remove **all** reactions from this message.
1671
+ * Requires the `messages.manage` scope.
1672
+ *
1673
+ * @example
1674
+ * await msg.clearAllReactions()
1675
+ */
1676
+ clearAllReactions() {
1677
+ return this._reactions.removeAll(this.id);
1678
+ }
1679
+ /**
1680
+ * Remove all reactions for a specific emoji from this message.
1681
+ * Requires the `messages.manage` scope.
1682
+ *
1683
+ * @example
1684
+ * await msg.clearReactionsFor('👍')
1685
+ */
1686
+ clearReactionsFor(emoji) {
1687
+ return this._reactions.removeEmoji(this.id, emoji);
1688
+ }
1689
+ /**
1690
+ * Fetch a breakdown of all reactions on this message, grouped by emoji.
1691
+ *
1692
+ * @example
1693
+ * const details = await msg.fetchAllReactions()
1694
+ * for (const d of details) console.log(`${d.emoji} — ${d.count}`)
1695
+ */
1696
+ fetchAllReactions() {
1697
+ return this._reactions.fetch(this.id);
1698
+ }
1316
1699
  /**
1317
1700
  * Get a URL to this message (deep link).
1318
1701
  */
@@ -1344,7 +1727,7 @@ var NovaMessage = class _NovaMessage {
1344
1727
 
1345
1728
  // src/structures/NovaChannel.ts
1346
1729
  var NovaChannel = class _NovaChannel {
1347
- constructor(raw, channels, messages) {
1730
+ constructor(raw, channels, messages, webhooks, forum) {
1348
1731
  this.id = raw.id;
1349
1732
  this.name = raw.name;
1350
1733
  this.type = raw.type;
@@ -1355,6 +1738,8 @@ var NovaChannel = class _NovaChannel {
1355
1738
  this.createdAt = new Date(raw.createdAt);
1356
1739
  this._channels = channels;
1357
1740
  this._messages = messages;
1741
+ this._webhooks = webhooks;
1742
+ this._forum = forum;
1358
1743
  }
1359
1744
  // ─── Type guards ────────────────────────────────────────────────────────────
1360
1745
  /** `true` for text channels. */
@@ -1431,7 +1816,7 @@ var NovaChannel = class _NovaChannel {
1431
1816
  */
1432
1817
  async edit(options) {
1433
1818
  const raw = await this._channels.edit(this.id, options);
1434
- return new _NovaChannel(raw, this._channels, this._messages);
1819
+ return new _NovaChannel(raw, this._channels, this._messages, this._webhooks, this._forum);
1435
1820
  }
1436
1821
  /**
1437
1822
  * Delete this channel.
@@ -1443,6 +1828,67 @@ var NovaChannel = class _NovaChannel {
1443
1828
  delete() {
1444
1829
  return this._channels.delete(this.id);
1445
1830
  }
1831
+ /**
1832
+ * Bulk-delete multiple messages in this channel (max 100 at once).
1833
+ * Requires the `messages.manage` scope.
1834
+ *
1835
+ * @example
1836
+ * const ids = messages.map(m => m.id)
1837
+ * const result = await channel.bulkDelete(ids)
1838
+ * console.log(`Deleted ${result.deleted} messages`)
1839
+ */
1840
+ bulkDelete(messageIds) {
1841
+ return this._channels.bulkDelete(this.id, messageIds);
1842
+ }
1843
+ // ─── Webhooks ────────────────────────────────────────────────────────────────
1844
+ /**
1845
+ * Create a webhook in this channel.
1846
+ * Requires the `webhooks.manage` scope.
1847
+ *
1848
+ * @example
1849
+ * const wh = await channel.createWebhook({ name: 'Notifications' })
1850
+ * console.log('Token:', wh.token)
1851
+ */
1852
+ createWebhook(options) {
1853
+ if (!this._webhooks) throw new Error("[NovaChannel] WebhooksAPI not available \u2014 ensure you obtained this channel from client.fetchChannel()");
1854
+ return this._webhooks.create(this.id, options);
1855
+ }
1856
+ /**
1857
+ * Fetch all webhooks in this channel.
1858
+ * Requires the `webhooks.manage` scope.
1859
+ *
1860
+ * @example
1861
+ * const webhooks = await channel.fetchWebhooks()
1862
+ */
1863
+ fetchWebhooks() {
1864
+ if (!this._webhooks) throw new Error("[NovaChannel] WebhooksAPI not available \u2014 ensure you obtained this channel from client.fetchChannel()");
1865
+ return this._webhooks.list(this.id);
1866
+ }
1867
+ // ─── Forum ───────────────────────────────────────────────────────────────────
1868
+ /**
1869
+ * Create a new post in this FORUM channel.
1870
+ * Requires the `channels.write` scope.
1871
+ *
1872
+ * @example
1873
+ * const post = await channel.createForumPost({
1874
+ * title: 'Announcement',
1875
+ * content: 'Welcome everyone!',
1876
+ * })
1877
+ */
1878
+ createForumPost(options) {
1879
+ if (!this._forum) throw new Error("[NovaChannel] ForumAPI not available \u2014 ensure you obtained this channel from client.fetchChannel()");
1880
+ return this._forum.create(this.id, options);
1881
+ }
1882
+ /**
1883
+ * Fetch posts from this FORUM channel.
1884
+ *
1885
+ * @example
1886
+ * const posts = await channel.fetchForumPosts({ limit: 20 })
1887
+ */
1888
+ fetchForumPosts(options) {
1889
+ if (!this._forum) throw new Error("[NovaChannel] ForumAPI not available \u2014 ensure you obtained this channel from client.fetchChannel()");
1890
+ return this._forum.list(this.id, options);
1891
+ }
1446
1892
  // ─── Serialisation ──────────────────────────────────────────────────────────
1447
1893
  /**
1448
1894
  * Returns the channel as a mention-style string: `#name`.
@@ -1560,29 +2006,106 @@ var NovaMember = class {
1560
2006
  removeRole(roleId) {
1561
2007
  return this._members.removeRole(this.serverId, this.userId, roleId);
1562
2008
  }
1563
- // ─── Serialisation ──────────────────────────────────────────────────────────
2009
+ // ─── Moderation ─────────────────────────────────────────────────────────────
1564
2010
  /**
1565
- * Returns a mention-style string: `@displayName`.
2011
+ * Issue a warning to this member.
2012
+ * Requires the `members.moderate` scope.
2013
+ *
2014
+ * @example
2015
+ * await member.warn('Excessive spamming')
1566
2016
  */
1567
- toString() {
1568
- return `@${this.displayName}`;
2017
+ warn(reason) {
2018
+ return this._members.warn(this.serverId, this.userId, reason);
1569
2019
  }
1570
- /** Returns the raw member data. */
1571
- toJSON() {
1572
- return {
1573
- role: this.role,
1574
- joinedAt: this.joinedAt.toISOString(),
1575
- user: {
1576
- id: this.userId,
1577
- username: this.username,
1578
- displayName: this.displayName,
1579
- avatar: this.avatar,
1580
- status: this.status,
1581
- isBot: this.isBot
1582
- }
1583
- };
1584
- }
1585
- };
2020
+ /**
2021
+ * Fetch all warnings for this member in this server.
2022
+ * Requires the `members.moderate` scope.
2023
+ *
2024
+ * @example
2025
+ * const warnings = await member.fetchWarnings()
2026
+ * console.log(`${warnings.length} warnings on record`)
2027
+ */
2028
+ fetchWarnings() {
2029
+ return this._members.fetchWarnings(this.serverId, { userId: this.userId });
2030
+ }
2031
+ // ─── XP & Levels ────────────────────────────────────────────────────────────
2032
+ /**
2033
+ * Get this member's current XP and level.
2034
+ * Requires the `members.read` scope.
2035
+ *
2036
+ * @example
2037
+ * const xp = await member.getXP()
2038
+ * console.log(`Level ${xp.level} — ${xp.xp} XP`)
2039
+ */
2040
+ getXP() {
2041
+ return this._members.getXP(this.serverId, this.userId);
2042
+ }
2043
+ /**
2044
+ * Set this member's XP to a specific value.
2045
+ * Requires the `members.manage` scope.
2046
+ *
2047
+ * @example
2048
+ * await member.setXP(500)
2049
+ */
2050
+ setXP(xp) {
2051
+ return this._members.setXP(this.serverId, this.userId, { xp });
2052
+ }
2053
+ /**
2054
+ * Add (or subtract) XP to this member's current total.
2055
+ * Requires the `members.manage` scope.
2056
+ *
2057
+ * @example
2058
+ * await member.addXP(100) // reward 100 XP
2059
+ * await member.addXP(-50) // deduct 50 XP
2060
+ */
2061
+ addXP(amount) {
2062
+ return this._members.setXP(this.serverId, this.userId, { add: amount });
2063
+ }
2064
+ /**
2065
+ * Delete all warnings on record for this member in this server.
2066
+ * Requires the `members.moderate` scope.
2067
+ *
2068
+ * @example
2069
+ * await member.clearWarnings()
2070
+ */
2071
+ async clearWarnings() {
2072
+ const warnings = await this.fetchWarnings();
2073
+ await Promise.all(warnings.map((w) => this._members.removeWarning(w.id)));
2074
+ return warnings.length;
2075
+ }
2076
+ /**
2077
+ * Reset this member's XP back to zero.
2078
+ * Requires the `members.manage` scope.
2079
+ *
2080
+ * @example
2081
+ * await member.resetXP()
2082
+ */
2083
+ resetXP() {
2084
+ return this._members.setXP(this.serverId, this.userId, { xp: 0 });
2085
+ }
2086
+ // ─── Serialisation ──────────────────────────────────────────────────────────
2087
+ /**
2088
+ * Returns a mention-style string: `@displayName`.
2089
+ */
2090
+ toString() {
2091
+ return `@${this.displayName}`;
2092
+ }
2093
+ /** Returns the raw member data. */
2094
+ toJSON() {
2095
+ return {
2096
+ role: this.role,
2097
+ joinedAt: this.joinedAt.toISOString(),
2098
+ user: {
2099
+ id: this.userId,
2100
+ username: this.username,
2101
+ displayName: this.displayName,
2102
+ avatar: this.avatar,
2103
+ status: this.status,
2104
+ isBot: this.isBot
2105
+ }
2106
+ };
2107
+ }
2108
+ };
1586
2109
 
1587
2110
  // src/structures/NovaRole.ts
1588
2111
  var NovaRole = class {
@@ -1659,7 +2182,7 @@ var NovaRole = class {
1659
2182
 
1660
2183
  // src/structures/NovaServer.ts
1661
2184
  var NovaServerWrapper = class {
1662
- constructor(raw, servers, channels, members, invites, roles, messages) {
2185
+ constructor(raw, servers, channels, members, invites, roles, messages, categories, events, automod, auditLog) {
1663
2186
  this.id = raw.id;
1664
2187
  this.name = raw.name;
1665
2188
  this.icon = raw.icon;
@@ -1673,6 +2196,10 @@ var NovaServerWrapper = class {
1673
2196
  this._invites = invites;
1674
2197
  this._roles = roles;
1675
2198
  this._messages = messages;
2199
+ this._categories = categories;
2200
+ this._events = events;
2201
+ this._automod = automod;
2202
+ this._auditLog = auditLog;
1676
2203
  }
1677
2204
  // ─── Server management ────────────────────────────────────────────────────
1678
2205
  /**
@@ -1751,14 +2278,25 @@ var NovaServerWrapper = class {
1751
2278
  }
1752
2279
  // ─── Roles ────────────────────────────────────────────────────────────────
1753
2280
  /**
1754
- * Fetch all custom roles in this server.
2281
+ * List all custom roles in this server, sorted by position.
1755
2282
  *
1756
2283
  * @example
1757
2284
  * const roles = await server.fetchRoles()
2285
+ * const admins = roles.filter(r => r.name === 'Admin')
1758
2286
  */
1759
2287
  fetchRoles() {
1760
2288
  return this._roles.list(this.id);
1761
2289
  }
2290
+ /**
2291
+ * Create a new custom role in this server.
2292
+ * Requires the `roles.manage` scope.
2293
+ *
2294
+ * @example
2295
+ * const role = await server.createRole({ name: 'Supporter', color: '#00d4ff', hoist: true })
2296
+ */
2297
+ createRole(options) {
2298
+ return this._roles.create(this.id, options);
2299
+ }
1762
2300
  // ─── Invites ──────────────────────────────────────────────────────────────
1763
2301
  /**
1764
2302
  * Fetch all active invites for this server.
@@ -1789,6 +2327,133 @@ var NovaServerWrapper = class {
1789
2327
  send(channelId, content) {
1790
2328
  return this._messages.send(channelId, { content });
1791
2329
  }
2330
+ // ─── Categories ───────────────────────────────────────────────────────────
2331
+ /**
2332
+ * Fetch all channel categories in this server.
2333
+ *
2334
+ * @example
2335
+ * const cats = await server.fetchCategories()
2336
+ * const general = cats.find(c => c.name === 'General')
2337
+ */
2338
+ fetchCategories() {
2339
+ if (!this._categories) throw new Error("[NovaServerWrapper] CategoriesAPI not available");
2340
+ return this._categories.list(this.id);
2341
+ }
2342
+ /**
2343
+ * Create a new channel category in this server.
2344
+ * Requires the `channels.manage` scope.
2345
+ *
2346
+ * @example
2347
+ * const cat = await server.createCategory({ name: 'Bot Channels' })
2348
+ */
2349
+ createCategory(options) {
2350
+ if (!this._categories) throw new Error("[NovaServerWrapper] CategoriesAPI not available");
2351
+ return this._categories.create(this.id, options);
2352
+ }
2353
+ // ─── Events ───────────────────────────────────────────────────────────────
2354
+ /**
2355
+ * Fetch server events. Pass `{ upcoming: true }` to only return future events.
2356
+ *
2357
+ * @example
2358
+ * const events = await server.fetchEvents({ upcoming: true })
2359
+ */
2360
+ fetchEvents(options = {}) {
2361
+ if (!this._events) throw new Error("[NovaServerWrapper] EventsAPI not available");
2362
+ return this._events.list(this.id, options);
2363
+ }
2364
+ /**
2365
+ * Create a new server event.
2366
+ * Requires the `server.manage` scope.
2367
+ *
2368
+ * @example
2369
+ * const event = await server.createEvent({
2370
+ * title: 'Game Night',
2371
+ * startAt: new Date('2025-09-01T20:00:00Z').toISOString(),
2372
+ * })
2373
+ */
2374
+ createEvent(options) {
2375
+ if (!this._events) throw new Error("[NovaServerWrapper] EventsAPI not available");
2376
+ return this._events.create(this.id, options);
2377
+ }
2378
+ // ─── Stats & Audit Log ────────────────────────────────────────────────────
2379
+ /**
2380
+ * Fetch server statistics (member count, message count, etc.).
2381
+ *
2382
+ * @example
2383
+ * const stats = await server.fetchStats()
2384
+ * console.log(`Online: ${stats.onlineCount} / ${stats.memberCount}`)
2385
+ */
2386
+ fetchStats() {
2387
+ return this._servers.getStats(this.id);
2388
+ }
2389
+ /**
2390
+ * Fetch the audit log for this server.
2391
+ * Requires the `audit-log.read` scope.
2392
+ *
2393
+ * @example
2394
+ * const log = await server.fetchAuditLog({ action: 'member.banned', limit: 20 })
2395
+ */
2396
+ fetchAuditLog(options = {}) {
2397
+ if (!this._auditLog) throw new Error("[NovaServerWrapper] AuditLogAPI not available");
2398
+ return this._auditLog.fetch(this.id, options);
2399
+ }
2400
+ // ─── Warnings & Leaderboard ───────────────────────────────────────────────
2401
+ /**
2402
+ * Issue a warning to a member in this server.
2403
+ * Requires the `members.moderate` scope.
2404
+ *
2405
+ * @example
2406
+ * await server.warn('user-id', 'Insulting other members')
2407
+ */
2408
+ warn(userId, reason) {
2409
+ return this._members.warn(this.id, userId, reason);
2410
+ }
2411
+ /**
2412
+ * Fetch warnings for this server, optionally filtered by user.
2413
+ * Requires the `members.moderate` scope.
2414
+ *
2415
+ * @example
2416
+ * const all = await server.fetchWarnings()
2417
+ * const userWarns = await server.fetchWarnings({ userId: 'user-id' })
2418
+ */
2419
+ fetchWarnings(options = {}) {
2420
+ return this._members.fetchWarnings(this.id, options);
2421
+ }
2422
+ /**
2423
+ * Fetch the XP leaderboard for this server.
2424
+ * Requires the `members.read` scope.
2425
+ *
2426
+ * @example
2427
+ * const top = await server.fetchLeaderboard(10)
2428
+ * top.forEach(e => console.log(`#${e.rank} ${e.user.displayName} — ${e.xp} XP`))
2429
+ */
2430
+ fetchLeaderboard(limit = 10) {
2431
+ return this._members.leaderboard(this.id, limit);
2432
+ }
2433
+ // ─── AutoMod ─────────────────────────────────────────────────────────────
2434
+ /**
2435
+ * Fetch all AutoMod rules for this server.
2436
+ * Requires the `server.manage` scope.
2437
+ *
2438
+ * @example
2439
+ * const rules = await server.fetchAutoModRules()
2440
+ * const active = rules.filter(r => r.enabled)
2441
+ */
2442
+ fetchAutoModRules() {
2443
+ if (!this._automod) throw new Error("[NovaServerWrapper] AutoModAPI not available");
2444
+ return this._automod.list(this.id);
2445
+ }
2446
+ /**
2447
+ * Create a new AutoMod rule for this server.
2448
+ * Requires the `server.manage` scope.
2449
+ *
2450
+ * @example
2451
+ * await server.createAutoModRule({ type: 'BLOCKED_WORD', value: 'badword' })
2452
+ */
2453
+ createAutoModRule(options) {
2454
+ if (!this._automod) throw new Error("[NovaServerWrapper] AutoModAPI not available");
2455
+ return this._automod.create(this.id, options);
2456
+ }
1792
2457
  // ─── Helpers ─────────────────────────────────────────────────────────────
1793
2458
  toJSON() {
1794
2459
  return {
@@ -1916,6 +2581,265 @@ var NovaWebhook = class {
1916
2581
  }
1917
2582
  };
1918
2583
 
2584
+ // src/structures/NovaForumPost.ts
2585
+ var NovaForumPost = class _NovaForumPost {
2586
+ constructor(raw, forum) {
2587
+ this.raw = raw;
2588
+ this.forum = forum;
2589
+ this.id = raw.id;
2590
+ this.channelId = raw.channelId;
2591
+ this.authorId = raw.authorId;
2592
+ this.title = raw.title;
2593
+ this.body = raw.body;
2594
+ this.tags = raw.tags;
2595
+ this.pinned = raw.pinned;
2596
+ this.closed = raw.closed;
2597
+ this.upvoteCount = raw.upvoteCount;
2598
+ this.replyCount = raw.replyCount;
2599
+ this.author = raw.author;
2600
+ this.createdAt = raw.createdAt;
2601
+ this.updatedAt = raw.updatedAt;
2602
+ }
2603
+ /**
2604
+ * Edit this forum post.
2605
+ *
2606
+ * @example
2607
+ * await post.edit({ title: 'Updated title' })
2608
+ */
2609
+ async edit(options) {
2610
+ const updated = await this.forum.edit(this.id, options);
2611
+ return new _NovaForumPost(updated, this.forum);
2612
+ }
2613
+ /**
2614
+ * Close the forum post (no new replies).
2615
+ *
2616
+ * @example
2617
+ * await post.close()
2618
+ */
2619
+ async close() {
2620
+ return this.edit({ closed: true });
2621
+ }
2622
+ /**
2623
+ * Re-open a closed forum post.
2624
+ *
2625
+ * @example
2626
+ * await post.open()
2627
+ */
2628
+ async open() {
2629
+ return this.edit({ closed: false });
2630
+ }
2631
+ /**
2632
+ * Pin this forum post in the channel.
2633
+ *
2634
+ * @example
2635
+ * await post.pin()
2636
+ */
2637
+ async pin() {
2638
+ return this.edit({ pinned: true });
2639
+ }
2640
+ /**
2641
+ * Unpin this forum post.
2642
+ *
2643
+ * @example
2644
+ * await post.unpin()
2645
+ */
2646
+ async unpin() {
2647
+ return this.edit({ pinned: false });
2648
+ }
2649
+ /**
2650
+ * Delete this forum post.
2651
+ *
2652
+ * @example
2653
+ * await post.delete()
2654
+ */
2655
+ async delete() {
2656
+ await this.forum.delete(this.id);
2657
+ }
2658
+ /** Whether this post was created by the currently connected bot. */
2659
+ get isOwnPost() {
2660
+ return this.authorId === this.author.id;
2661
+ }
2662
+ /** Returns `true` if the post has been closed. */
2663
+ get isClosed() {
2664
+ return this.closed;
2665
+ }
2666
+ /** Returns `true` if the post is pinned. */
2667
+ get isPinned() {
2668
+ return this.pinned;
2669
+ }
2670
+ toJSON() {
2671
+ return { ...this.raw };
2672
+ }
2673
+ };
2674
+
2675
+ // src/structures/NovaServerEvent.ts
2676
+ var NovaServerEvent = class _NovaServerEvent {
2677
+ constructor(raw, events) {
2678
+ this.raw = raw;
2679
+ this.events = events;
2680
+ this.id = raw.id;
2681
+ this.serverId = raw.serverId;
2682
+ this.channelId = raw.channelId;
2683
+ this.createdById = raw.createdById;
2684
+ this.title = raw.title;
2685
+ this.description = raw.description;
2686
+ this.imageUrl = raw.imageUrl;
2687
+ this.location = raw.location;
2688
+ this.startAt = raw.startAt;
2689
+ this.endAt = raw.endAt;
2690
+ this.attendeeCount = raw.attendeeCount;
2691
+ this.createdAt = raw.createdAt;
2692
+ }
2693
+ /**
2694
+ * Edit this event.
2695
+ *
2696
+ * @example
2697
+ * await event.edit({ title: 'New Title' })
2698
+ */
2699
+ async edit(options) {
2700
+ const updated = await this.events.edit(this.id, options);
2701
+ return new _NovaServerEvent(updated, this.events);
2702
+ }
2703
+ /**
2704
+ * Fetch full event details including the attendee list.
2705
+ *
2706
+ * @example
2707
+ * const full = await event.fetchAttendees()
2708
+ * console.log(`${full.attendees.length} attendees`)
2709
+ */
2710
+ async fetchAttendees() {
2711
+ const data = await this.events.fetch(this.id);
2712
+ return {
2713
+ event: new _NovaServerEvent(data, this.events),
2714
+ attendees: data.attendees
2715
+ };
2716
+ }
2717
+ /**
2718
+ * Delete this event.
2719
+ *
2720
+ * @example
2721
+ * await event.delete()
2722
+ */
2723
+ async delete() {
2724
+ await this.events.delete(this.id);
2725
+ }
2726
+ /**
2727
+ * Whether the event has already started.
2728
+ */
2729
+ get hasStarted() {
2730
+ return new Date(this.startAt) <= /* @__PURE__ */ new Date();
2731
+ }
2732
+ /**
2733
+ * Whether the event has ended.
2734
+ * Returns `false` if no `endAt` is set.
2735
+ */
2736
+ get hasEnded() {
2737
+ return this.endAt ? new Date(this.endAt) <= /* @__PURE__ */ new Date() : false;
2738
+ }
2739
+ /**
2740
+ * Whether this is an upcoming event that has not yet started.
2741
+ */
2742
+ get isUpcoming() {
2743
+ return new Date(this.startAt) > /* @__PURE__ */ new Date();
2744
+ }
2745
+ toJSON() {
2746
+ return { ...this.raw };
2747
+ }
2748
+ };
2749
+
2750
+ // src/structures/NovaBan.ts
2751
+ var NovaBan = class {
2752
+ constructor(raw, serverId, members) {
2753
+ this.userId = raw.userId;
2754
+ this.username = raw.username;
2755
+ this.displayName = raw.displayName;
2756
+ this.avatar = raw.avatar;
2757
+ this.reason = raw.reason;
2758
+ this.bannedAt = raw.bannedAt;
2759
+ this.moderatorId = raw.moderatorId;
2760
+ this.serverId = serverId;
2761
+ this._members = members;
2762
+ }
2763
+ /**
2764
+ * Revoke this ban, allowing the user to rejoin the server.
2765
+ * Requires the `members.ban` scope.
2766
+ *
2767
+ * @example
2768
+ * await ban.unban()
2769
+ */
2770
+ unban() {
2771
+ return this._members.unban(this.serverId, this.userId);
2772
+ }
2773
+ /**
2774
+ * When the ban was issued as a `Date` object.
2775
+ */
2776
+ get bannedAtDate() {
2777
+ return new Date(this.bannedAt);
2778
+ }
2779
+ /**
2780
+ * Human-readable label: `"displayName (username)"`.
2781
+ */
2782
+ get label() {
2783
+ return `${this.displayName} (${this.username})`;
2784
+ }
2785
+ toJSON() {
2786
+ return {
2787
+ userId: this.userId,
2788
+ username: this.username,
2789
+ displayName: this.displayName,
2790
+ avatar: this.avatar,
2791
+ reason: this.reason,
2792
+ bannedAt: this.bannedAt,
2793
+ moderatorId: this.moderatorId
2794
+ };
2795
+ }
2796
+ };
2797
+
2798
+ // src/structures/NovaWarning.ts
2799
+ var NovaWarning = class {
2800
+ constructor(raw, members) {
2801
+ this.id = raw.id;
2802
+ this.serverId = raw.serverId;
2803
+ this.userId = raw.userId;
2804
+ this.moderatorId = raw.moderatorId;
2805
+ this.reason = raw.reason;
2806
+ this.createdAt = raw.createdAt;
2807
+ this._members = members;
2808
+ }
2809
+ /**
2810
+ * Delete (expunge) this warning from the record.
2811
+ * Requires the `members.moderate` scope.
2812
+ *
2813
+ * @example
2814
+ * await warning.delete()
2815
+ */
2816
+ delete() {
2817
+ return this._members.removeWarning(this.id);
2818
+ }
2819
+ /**
2820
+ * When the warning was issued as a `Date` object.
2821
+ */
2822
+ get issuedAt() {
2823
+ return new Date(this.createdAt);
2824
+ }
2825
+ /**
2826
+ * How long ago the warning was issued, in milliseconds.
2827
+ */
2828
+ get ageMs() {
2829
+ return Date.now() - this.issuedAt.getTime();
2830
+ }
2831
+ toJSON() {
2832
+ return {
2833
+ id: this.id,
2834
+ serverId: this.serverId,
2835
+ userId: this.userId,
2836
+ moderatorId: this.moderatorId,
2837
+ reason: this.reason,
2838
+ createdAt: this.createdAt
2839
+ };
2840
+ }
2841
+ };
2842
+
1919
2843
  // src/client.ts
1920
2844
  var NovaClient = class extends EventEmitter {
1921
2845
  constructor(options) {
@@ -1953,6 +2877,11 @@ var NovaClient = class extends EventEmitter {
1953
2877
  this.invites = new InvitesAPI(this.http);
1954
2878
  this.webhooks = new WebhooksAPI(this.http);
1955
2879
  this.auditLog = new AuditLogAPI(this.http);
2880
+ this.forum = new ForumAPI(this.http);
2881
+ this.events = new EventsAPI(this.http);
2882
+ this.categories = new CategoriesAPI(this.http);
2883
+ this.automod = new AutoModAPI(this.http);
2884
+ this.users = new UsersAPI(this.http);
1956
2885
  this.on("error", () => {
1957
2886
  });
1958
2887
  const cleanup = () => this.disconnect();
@@ -2164,16 +3093,53 @@ var NovaClient = class extends EventEmitter {
2164
3093
  case "server.updated":
2165
3094
  this.emit("serverUpdate", event.data);
2166
3095
  break;
2167
- }
2168
- });
2169
- this.socket.on("bot:error", (err) => {
2170
- this.emit("error", err);
2171
- });
2172
- this.socket.on("disconnect", (reason) => {
2173
- this.emit("disconnect", reason);
2174
- });
2175
- });
2176
- }
3096
+ case "invite.created":
3097
+ this.emit("inviteCreate", event.data);
3098
+ break;
3099
+ case "invite.deleted":
3100
+ this.emit("inviteDelete", event.data);
3101
+ break;
3102
+ case "forum.post.created": {
3103
+ const rawPost = event.data;
3104
+ this.emit("forumPostCreate", new NovaForumPost(rawPost, this.forum));
3105
+ break;
3106
+ }
3107
+ case "event.created": {
3108
+ const rawEvent = event.data;
3109
+ this.emit("eventCreate", new NovaServerEvent(rawEvent, this.events));
3110
+ break;
3111
+ }
3112
+ case "event.updated":
3113
+ this.emit("eventUpdate", event.data);
3114
+ break;
3115
+ case "event.deleted":
3116
+ this.emit("eventDelete", event.data);
3117
+ break;
3118
+ case "category.created":
3119
+ this.emit("categoryCreate", event.data);
3120
+ break;
3121
+ case "category.updated":
3122
+ this.emit("categoryUpdate", event.data);
3123
+ break;
3124
+ case "category.deleted":
3125
+ this.emit("categoryDelete", event.data);
3126
+ break;
3127
+ case "member.warned":
3128
+ this.emit("memberWarned", event.data);
3129
+ break;
3130
+ case "messages.bulk_deleted":
3131
+ this.emit("messagesBulkDelete", event.data);
3132
+ break;
3133
+ }
3134
+ });
3135
+ this.socket.on("bot:error", (err) => {
3136
+ this.emit("error", err);
3137
+ });
3138
+ this.socket.on("disconnect", (reason) => {
3139
+ this.emit("disconnect", reason);
3140
+ });
3141
+ });
3142
+ }
2177
3143
  /**
2178
3144
  * Register a recurring task that fires at a set interval.
2179
3145
  * All cron tasks are automatically cancelled on `disconnect()`.
@@ -2230,7 +3196,7 @@ var NovaClient = class extends EventEmitter {
2230
3196
  */
2231
3197
  async fetchChannel(channelId) {
2232
3198
  const raw = await this.channels.fetch(channelId);
2233
- return new NovaChannel(raw, this.channels, this.messages);
3199
+ return new NovaChannel(raw, this.channels, this.messages, this.webhooks, this.forum);
2234
3200
  }
2235
3201
  /**
2236
3202
  * Fetch all channels in a server and return them as `NovaChannel` wrappers.
@@ -2241,7 +3207,7 @@ var NovaClient = class extends EventEmitter {
2241
3207
  */
2242
3208
  async fetchChannels(serverId) {
2243
3209
  const list = await this.channels.list(serverId);
2244
- return list.map((raw) => new NovaChannel(raw, this.channels, this.messages));
3210
+ return list.map((raw) => new NovaChannel(raw, this.channels, this.messages, this.webhooks, this.forum));
2245
3211
  }
2246
3212
  /**
2247
3213
  * Fetch all members in a server and return them as `NovaMember` wrappers.
@@ -2266,6 +3232,36 @@ var NovaClient = class extends EventEmitter {
2266
3232
  const raw = await this.members.fetch(serverId, userId);
2267
3233
  return new NovaMember(raw, serverId, this.members);
2268
3234
  }
3235
+ /**
3236
+ * Fetch all bans in a server and return them as `NovaBan` wrappers.
3237
+ * Each `NovaBan` has an `.unban()` convenience method.
3238
+ * Requires the `members.ban` scope.
3239
+ *
3240
+ * @example
3241
+ * const bans = await client.fetchBans('server-id')
3242
+ * for (const ban of bans) {
3243
+ * if (ban.reason === 'test') await ban.unban()
3244
+ * }
3245
+ */
3246
+ async fetchBans(serverId) {
3247
+ const list = await this.members.listBans(serverId);
3248
+ return list.map((raw) => new NovaBan(raw, serverId, this.members));
3249
+ }
3250
+ /**
3251
+ * Fetch warnings for a member (or all members) in a server and return them
3252
+ * as `NovaWarning` wrappers. Each wrapper has a `.delete()` convenience method.
3253
+ * Requires the `members.moderate` scope.
3254
+ *
3255
+ * @example
3256
+ * const warnings = await client.fetchMemberWarnings('server-id', 'user-id')
3257
+ * for (const w of warnings) {
3258
+ * if (w.ageMs > 30 * 24 * 60 * 60_000) await w.delete() // older than 30 days
3259
+ * }
3260
+ */
3261
+ async fetchMemberWarnings(serverId, userId) {
3262
+ const list = await this.members.fetchWarnings(serverId, userId ? { userId } : {});
3263
+ return list.map((raw) => new NovaWarning(raw, this.members));
3264
+ }
2269
3265
  /**
2270
3266
  * Fetch a single server by ID and return it as a `NovaServerWrapper`.
2271
3267
  *
@@ -2275,7 +3271,7 @@ var NovaClient = class extends EventEmitter {
2275
3271
  */
2276
3272
  async fetchServer(serverId) {
2277
3273
  const raw = await this.servers.fetch(serverId);
2278
- return new NovaServerWrapper(raw, this.servers, this.channels, this.members, this.invites, this.roles, this.messages);
3274
+ return new NovaServerWrapper(raw, this.servers, this.channels, this.members, this.invites, this.roles, this.messages, this.categories, this.events, this.automod, this.auditLog);
2279
3275
  }
2280
3276
  /**
2281
3277
  * Fetch all servers the bot is in and return them as `NovaServerWrapper` objects.
@@ -2286,7 +3282,7 @@ var NovaClient = class extends EventEmitter {
2286
3282
  */
2287
3283
  async fetchServers() {
2288
3284
  const list = await this.servers.list();
2289
- return list.map((raw) => new NovaServerWrapper(raw, this.servers, this.channels, this.members, this.invites, this.roles, this.messages));
3285
+ return list.map((raw) => new NovaServerWrapper(raw, this.servers, this.channels, this.members, this.invites, this.roles, this.messages, this.categories, this.events, this.automod, this.auditLog));
2290
3286
  }
2291
3287
  /**
2292
3288
  * Fetch all custom roles in a server and return them as `NovaRole` wrappers.
@@ -2342,6 +3338,116 @@ var NovaClient = class extends EventEmitter {
2342
3338
  const raw = await this.webhooks.fetch(webhookId);
2343
3339
  return new NovaWebhook(raw, this.webhooks);
2344
3340
  }
3341
+ /**
3342
+ * Fetch forum posts from a FORUM channel and return them as `NovaForumPost` wrappers.
3343
+ *
3344
+ * @example
3345
+ * const posts = await client.fetchForumPosts('channel-id', { limit: 20 })
3346
+ * const open = posts.filter(p => !p.isClosed)
3347
+ */
3348
+ async fetchForumPosts(channelId, options) {
3349
+ const list = await this.forum.list(channelId, options);
3350
+ return list.map((raw) => new NovaForumPost(raw, this.forum));
3351
+ }
3352
+ /**
3353
+ * Fetch server events and return them as `NovaServerEvent` wrappers.
3354
+ *
3355
+ * @example
3356
+ * const upcoming = await client.fetchEvents('server-id', { upcoming: true })
3357
+ * for (const event of upcoming) console.log(event.title)
3358
+ */
3359
+ async fetchEvents(serverId, options) {
3360
+ const list = await this.events.list(serverId, options);
3361
+ return list.map((raw) => new NovaServerEvent(raw, this.events));
3362
+ }
3363
+ /**
3364
+ * Fetch a single server event by ID and return it as a `NovaServerEvent` wrapper.
3365
+ *
3366
+ * @example
3367
+ * const event = await client.fetchEvent('event-id')
3368
+ * if (event.isUpcoming) await event.edit({ title: 'Updated!' })
3369
+ */
3370
+ async fetchEvent(eventId) {
3371
+ const raw = await this.events.fetch(eventId);
3372
+ return new NovaServerEvent(raw, this.events);
3373
+ }
3374
+ /**
3375
+ * Fetch a user's public profile.
3376
+ *
3377
+ * @example
3378
+ * const user = await client.fetchUserProfile('user-id')
3379
+ * console.log(user.bio)
3380
+ */
3381
+ fetchUserProfile(userId) {
3382
+ return this.users.fetch(userId);
3383
+ }
3384
+ /**
3385
+ * Fetch the XP leaderboard for a server.
3386
+ *
3387
+ * @example
3388
+ * const top = await client.fetchLeaderboard('server-id', 10)
3389
+ * top.forEach(entry => console.log(`#${entry.rank} ${entry.user.username} — ${entry.xp} XP`))
3390
+ */
3391
+ fetchLeaderboard(serverId, limit = 10) {
3392
+ return this.members.leaderboard(serverId, limit);
3393
+ }
3394
+ /**
3395
+ * Fetch warnings in a server, optionally filtered by user.
3396
+ *
3397
+ * @example
3398
+ * const warnings = await client.fetchWarnings('server-id', { userId: 'user-id' })
3399
+ */
3400
+ fetchWarnings(serverId, options) {
3401
+ return this.members.fetchWarnings(serverId, options);
3402
+ }
3403
+ /**
3404
+ * Fetch all automod rules for a server.
3405
+ *
3406
+ * @example
3407
+ * const rules = await client.fetchAutoModRules('server-id')
3408
+ */
3409
+ fetchAutoModRules(serverId) {
3410
+ return this.automod.list(serverId);
3411
+ }
3412
+ /**
3413
+ * Fetch all channel categories for a server.
3414
+ *
3415
+ * @example
3416
+ * const cats = await client.fetchCategories('server-id')
3417
+ */
3418
+ fetchCategories(serverId) {
3419
+ return this.categories.list(serverId);
3420
+ }
3421
+ /**
3422
+ * Fetch soundboard clips for a server.
3423
+ *
3424
+ * @example
3425
+ * const clips = await client.fetchSoundboard('server-id')
3426
+ */
3427
+ fetchSoundboard(serverId) {
3428
+ return this.servers.listSoundboard(serverId);
3429
+ }
3430
+ /**
3431
+ * Fetch statistics for a server (member count, message count, etc.).
3432
+ *
3433
+ * @example
3434
+ * const stats = await client.fetchServerStats('server-id')
3435
+ * console.log(`${stats.onlineCount}/${stats.memberCount} online`)
3436
+ */
3437
+ fetchServerStats(serverId) {
3438
+ return this.servers.getStats(serverId);
3439
+ }
3440
+ /**
3441
+ * Bulk delete up to 100 messages in a channel.
3442
+ * Requires the `messages.manage` scope.
3443
+ *
3444
+ * @example
3445
+ * const result = await client.bulkDelete('channel-id', messageIds)
3446
+ * console.log(`Deleted ${result.deleted} messages`)
3447
+ */
3448
+ bulkDelete(channelId, messageIds) {
3449
+ return this.channels.bulkDelete(channelId, messageIds);
3450
+ }
2345
3451
  // ─── Event fence ──────────────────────────────────────────────────────────────
2346
3452
  /**
2347
3453
  * Wait for a specific event to be emitted, optionally filtered.
@@ -2371,6 +3477,58 @@ var NovaClient = class extends EventEmitter {
2371
3477
  this.on(event, handler);
2372
3478
  });
2373
3479
  }
3480
+ /**
3481
+ * Wait for the next message matching an optional filter.
3482
+ * Short for `waitFor('messageCreate', filter, timeoutMs)`.
3483
+ *
3484
+ * @example
3485
+ * // Wait for any message in a specific channel
3486
+ * const msg = await client.waitForMessage(
3487
+ * m => m.channelId === channelId && !m.isFromBot(),
3488
+ * 30_000,
3489
+ * )
3490
+ * console.log(msg.content)
3491
+ */
3492
+ waitForMessage(filter, timeoutMs = 3e4) {
3493
+ return this.waitFor("messageCreate", filter, timeoutMs);
3494
+ }
3495
+ /**
3496
+ * Wait for the next button-click interaction matching an optional filter.
3497
+ * Short for `waitFor('interactionCreate', i => i.isButton() && filter(i), timeoutMs)`.
3498
+ *
3499
+ * @example
3500
+ * // Wait for any button on a specific message
3501
+ * const click = await client.waitForButton(
3502
+ * i => i.triggerMsgId === message.id,
3503
+ * 60_000,
3504
+ * )
3505
+ * await click.reply('Button clicked!')
3506
+ */
3507
+ waitForButton(filter, timeoutMs = 3e4) {
3508
+ return this.waitFor(
3509
+ "interactionCreate",
3510
+ (i) => i.isButton() && (!filter || filter(i)),
3511
+ timeoutMs
3512
+ );
3513
+ }
3514
+ /**
3515
+ * Wait for the next select-menu interaction matching an optional filter.
3516
+ * Short for `waitFor('interactionCreate', i => i.isSelectMenu() && filter(i), timeoutMs)`.
3517
+ *
3518
+ * @example
3519
+ * const pick = await client.waitForSelectMenu(
3520
+ * i => i.customId === 'colour_pick',
3521
+ * 60_000,
3522
+ * )
3523
+ * await pick.reply(`You picked: ${pick.values.join(', ')}`)
3524
+ */
3525
+ waitForSelectMenu(filter, timeoutMs = 3e4) {
3526
+ return this.waitFor(
3527
+ "interactionCreate",
3528
+ (i) => i.isSelectMenu() && (!filter || filter(i)),
3529
+ timeoutMs
3530
+ );
3531
+ }
2374
3532
  /**
2375
3533
  * Disconnect from the gateway and clean up.
2376
3534
  */
@@ -3738,46 +4896,1375 @@ function countdown(target) {
3738
4896
  seconds: Math.floor(total % MINUTE / SECOND)
3739
4897
  };
3740
4898
  }
4899
+
4900
+ // src/structures/NovaCategory.ts
4901
+ var NovaCategory = class _NovaCategory {
4902
+ constructor(raw, categories) {
4903
+ this.id = raw.id;
4904
+ this.name = raw.name;
4905
+ this.position = raw.position;
4906
+ this.serverId = raw.serverId;
4907
+ this.createdAt = raw.createdAt;
4908
+ this._categories = categories;
4909
+ }
4910
+ // ─── Convenience methods ──────────────────────────────────────────────────
4911
+ /**
4912
+ * Edit this category's name and/or position.
4913
+ * Requires the `channels.manage` scope.
4914
+ *
4915
+ * @example
4916
+ * await cat.edit({ name: 'Lounges', position: 0 })
4917
+ */
4918
+ async edit(options) {
4919
+ const updated = await this._categories.edit(this.id, options);
4920
+ return new _NovaCategory(updated, this._categories);
4921
+ }
4922
+ /**
4923
+ * Rename this category.
4924
+ * Requires the `channels.manage` scope.
4925
+ *
4926
+ * @example
4927
+ * await cat.rename('Lounges')
4928
+ */
4929
+ rename(name) {
4930
+ return this.edit({ name });
4931
+ }
4932
+ /**
4933
+ * Move this category to a specific position.
4934
+ * Requires the `channels.manage` scope.
4935
+ *
4936
+ * @example
4937
+ * await cat.setPosition(0)
4938
+ */
4939
+ setPosition(position) {
4940
+ return this.edit({ position });
4941
+ }
4942
+ /**
4943
+ * Delete this category.
4944
+ * Requires the `channels.manage` scope.
4945
+ *
4946
+ * @example
4947
+ * await cat.delete()
4948
+ */
4949
+ async delete() {
4950
+ await this._categories.delete(this.id);
4951
+ }
4952
+ // ─── Serialisation ─────────────────────────────────────────────────────────
4953
+ toJSON() {
4954
+ return {
4955
+ id: this.id,
4956
+ name: this.name,
4957
+ position: this.position,
4958
+ serverId: this.serverId,
4959
+ createdAt: this.createdAt
4960
+ };
4961
+ }
4962
+ toString() {
4963
+ return `NovaCategory(${this.id}): ${this.name}`;
4964
+ }
4965
+ };
4966
+
4967
+ // src/builders/ForumPostBuilder.ts
4968
+ var ForumPostBuilder = class {
4969
+ constructor() {
4970
+ this._title = "";
4971
+ this._body = "";
4972
+ this._tags = [];
4973
+ }
4974
+ /**
4975
+ * Set the post title.
4976
+ */
4977
+ setTitle(title) {
4978
+ this._title = title;
4979
+ return this;
4980
+ }
4981
+ /**
4982
+ * Set the post body / content.
4983
+ */
4984
+ setBody(body) {
4985
+ this._body = body;
4986
+ return this;
4987
+ }
4988
+ /**
4989
+ * Add a tag to the post.
4990
+ * Tags are used to categorise forum posts.
4991
+ */
4992
+ addTag(tag) {
4993
+ this._tags.push(tag);
4994
+ return this;
4995
+ }
4996
+ /**
4997
+ * Set all tags at once, replacing any previously added tags.
4998
+ */
4999
+ setTags(tags) {
5000
+ this._tags = [...tags];
5001
+ return this;
5002
+ }
5003
+ /**
5004
+ * Validate and return the final `CreateForumPostOptions` object.
5005
+ *
5006
+ * @throws {Error} if title or body is missing.
5007
+ */
5008
+ build() {
5009
+ if (!this._title.trim()) throw new Error("[ForumPostBuilder] title is required");
5010
+ if (!this._body.trim()) throw new Error("[ForumPostBuilder] body is required");
5011
+ return {
5012
+ title: this._title.trim(),
5013
+ body: this._body.trim(),
5014
+ tags: this._tags
5015
+ };
5016
+ }
5017
+ };
5018
+
5019
+ // src/builders/EventBuilder.ts
5020
+ var EventBuilder = class {
5021
+ constructor() {
5022
+ this._title = "";
5023
+ }
5024
+ /** Set the event title (required). */
5025
+ setTitle(title) {
5026
+ this._title = title;
5027
+ return this;
5028
+ }
5029
+ /** Set the event description. */
5030
+ setDescription(description) {
5031
+ this._description = description;
5032
+ return this;
5033
+ }
5034
+ /** Set a banner/cover image URL for the event. */
5035
+ setImage(url) {
5036
+ this._imageUrl = url;
5037
+ return this;
5038
+ }
5039
+ /** Set the physical or virtual location for the event. */
5040
+ setLocation(location) {
5041
+ this._location = location;
5042
+ return this;
5043
+ }
5044
+ /** Set when the event starts. Accepts a Date or ISO string. */
5045
+ setStartAt(date) {
5046
+ this._startAt = date instanceof Date ? date.toISOString() : date;
5047
+ return this;
5048
+ }
5049
+ /** Set when the event ends. Accepts a Date or ISO string. */
5050
+ setEndAt(date) {
5051
+ this._endAt = date instanceof Date ? date.toISOString() : date;
5052
+ return this;
5053
+ }
5054
+ /** Link the event to a specific channel. */
5055
+ setChannel(channelId) {
5056
+ this._channelId = channelId;
5057
+ return this;
5058
+ }
5059
+ /**
5060
+ * Validate and return the final `CreateEventOptions` object.
5061
+ *
5062
+ * @throws {Error} if title or startAt is missing.
5063
+ */
5064
+ build() {
5065
+ if (!this._title.trim()) throw new Error("[EventBuilder] title is required");
5066
+ if (!this._startAt) throw new Error("[EventBuilder] startAt is required \u2014 call setStartAt()");
5067
+ return {
5068
+ title: this._title.trim(),
5069
+ ...this._description !== void 0 ? { description: this._description } : {},
5070
+ ...this._imageUrl !== void 0 ? { imageUrl: this._imageUrl } : {},
5071
+ ...this._location !== void 0 ? { location: this._location } : {},
5072
+ startAt: this._startAt,
5073
+ ...this._endAt !== void 0 ? { endAt: this._endAt } : {},
5074
+ ...this._channelId !== void 0 ? { channelId: this._channelId } : {}
5075
+ };
5076
+ }
5077
+ };
5078
+
5079
+ // src/builders/RoleBuilder.ts
5080
+ var RoleBuilder = class {
5081
+ constructor() {
5082
+ this._name = "";
5083
+ }
5084
+ /** Set the role's display name (required). */
5085
+ setName(name) {
5086
+ this._name = name;
5087
+ return this;
5088
+ }
5089
+ /**
5090
+ * Set the role's accent colour.
5091
+ * @param color Hex string — e.g. `'#5865F2'` or `'5865F2'`
5092
+ */
5093
+ setColor(color) {
5094
+ this._color = color.startsWith("#") ? color : `#${color}`;
5095
+ return this;
5096
+ }
5097
+ /**
5098
+ * Whether this role is shown separately in the member sidebar.
5099
+ * Default: `true` when called without arguments.
5100
+ */
5101
+ setHoist(hoist = true) {
5102
+ this._hoist = hoist;
5103
+ return this;
5104
+ }
5105
+ /** Set a specific permission key to true or false. */
5106
+ addPermission(key, value = true) {
5107
+ if (!this._permissions) this._permissions = {};
5108
+ this._permissions[key] = value;
5109
+ return this;
5110
+ }
5111
+ /** Replace all permission overrides at once. */
5112
+ setPermissions(permissions) {
5113
+ this._permissions = { ...permissions };
5114
+ return this;
5115
+ }
5116
+ // ─── Common permission shorthands ─────────────────────────────────────────
5117
+ /** Grant the `sendMessages` permission. */
5118
+ allowSendMessages() {
5119
+ return this.addPermission("sendMessages");
5120
+ }
5121
+ /** Deny the `sendMessages` permission. */
5122
+ denySendMessages() {
5123
+ return this.addPermission("sendMessages", false);
5124
+ }
5125
+ /** Grant the `addReactions` permission. */
5126
+ allowAddReactions() {
5127
+ return this.addPermission("addReactions");
5128
+ }
5129
+ /** Grant the `manageMessages` permission (delete/pin messages). */
5130
+ allowManageMessages() {
5131
+ return this.addPermission("manageMessages");
5132
+ }
5133
+ /** Grant the `manageChannels` permission. */
5134
+ allowManageChannels() {
5135
+ return this.addPermission("manageChannels");
5136
+ }
5137
+ /** Grant the `kickMembers` permission. */
5138
+ allowKickMembers() {
5139
+ return this.addPermission("kickMembers");
5140
+ }
5141
+ /** Grant the `banMembers` permission. */
5142
+ allowBanMembers() {
5143
+ return this.addPermission("banMembers");
5144
+ }
5145
+ /** Grant the `manageServer` permission. */
5146
+ allowManageServer() {
5147
+ return this.addPermission("manageServer");
5148
+ }
5149
+ /** Grant the `manageRoles` permission. */
5150
+ allowManageRoles() {
5151
+ return this.addPermission("manageRoles");
5152
+ }
5153
+ /**
5154
+ * Validate and build the `RoleCreateOptions` object.
5155
+ *
5156
+ * @throws if name is not set.
5157
+ */
5158
+ build() {
5159
+ if (!this._name.trim()) throw new Error("[RoleBuilder] name is required \u2014 call .setName()");
5160
+ return {
5161
+ name: this._name.trim(),
5162
+ ...this._color !== void 0 ? { color: this._color } : {},
5163
+ ...this._hoist !== void 0 ? { hoist: this._hoist } : {},
5164
+ ...this._permissions !== void 0 ? { permissions: this._permissions } : {}
5165
+ };
5166
+ }
5167
+ };
5168
+
5169
+ // src/builders/ChannelBuilder.ts
5170
+ var ChannelBuilder = class {
5171
+ constructor() {
5172
+ this._name = "";
5173
+ this._type = "TEXT";
5174
+ }
5175
+ /** Set the channel name (required). No # prefix needed. */
5176
+ setName(name) {
5177
+ this._name = name;
5178
+ return this;
5179
+ }
5180
+ /**
5181
+ * Set the channel type.
5182
+ * @default 'TEXT'
5183
+ */
5184
+ setType(type) {
5185
+ this._type = type;
5186
+ return this;
5187
+ }
5188
+ // ─── Type shorthands ──────────────────────────────────────────────────────
5189
+ /** Create a TEXT channel. */
5190
+ asText() {
5191
+ return this.setType("TEXT");
5192
+ }
5193
+ /** Create a VOICE channel. */
5194
+ asVoice() {
5195
+ return this.setType("VOICE");
5196
+ }
5197
+ /** Create an ANNOUNCEMENT channel. */
5198
+ asAnnouncement() {
5199
+ return this.setType("ANNOUNCEMENT");
5200
+ }
5201
+ /** Create a FORUM channel. */
5202
+ asForum() {
5203
+ return this.setType("FORUM");
5204
+ }
5205
+ /** Create a STAGE channel. */
5206
+ asStage() {
5207
+ return this.setType("STAGE");
5208
+ }
5209
+ // ─── Properties ───────────────────────────────────────────────────────────
5210
+ /** Set the channel topic / description. */
5211
+ setTopic(topic) {
5212
+ this._topic = topic;
5213
+ return this;
5214
+ }
5215
+ /** Set the channel's sort position. */
5216
+ setPosition(position) {
5217
+ this._position = position;
5218
+ return this;
5219
+ }
5220
+ /** Place the channel inside an existing category by its ID. */
5221
+ setCategory(categoryId) {
5222
+ this._categoryId = categoryId;
5223
+ return this;
5224
+ }
5225
+ /**
5226
+ * Set a slow-mode interval in seconds.
5227
+ * Members must wait this long between messages.
5228
+ * Pass `0` to disable.
5229
+ */
5230
+ setSlowMode(seconds) {
5231
+ this._slowMode = seconds;
5232
+ return this;
5233
+ }
5234
+ /**
5235
+ * Mark the channel as private.
5236
+ * Private channels are only visible to members with explicit access.
5237
+ */
5238
+ setPrivate(isPrivate = true) {
5239
+ this._isPrivate = isPrivate;
5240
+ return this;
5241
+ }
5242
+ /**
5243
+ * Validate and build the `CreateChannelOptions` object.
5244
+ *
5245
+ * @throws if name is not set.
5246
+ */
5247
+ build() {
5248
+ if (!this._name.trim()) throw new Error("[ChannelBuilder] name is required \u2014 call .setName()");
5249
+ return {
5250
+ name: this._name.trim(),
5251
+ type: this._type,
5252
+ ...this._topic !== void 0 ? { topic: this._topic } : {},
5253
+ ...this._position !== void 0 ? { position: this._position } : {},
5254
+ ...this._categoryId !== void 0 ? { categoryId: this._categoryId } : {},
5255
+ ...this._slowMode !== void 0 ? { slowMode: this._slowMode } : {},
5256
+ ...this._isPrivate !== void 0 ? { isPrivate: this._isPrivate } : {}
5257
+ };
5258
+ }
5259
+ };
5260
+
5261
+ // src/builders/InviteBuilder.ts
5262
+ var InviteBuilder = class {
5263
+ /**
5264
+ * Set the maximum number of times this invite can be used.
5265
+ * Pass `null` for unlimited uses.
5266
+ */
5267
+ setMaxUses(maxUses) {
5268
+ this._maxUses = maxUses;
5269
+ return this;
5270
+ }
5271
+ /**
5272
+ * Set an absolute expiry date/time for this invite.
5273
+ * Pass a `Date`, an ISO string, or `null` to never expire.
5274
+ */
5275
+ setExpiresAt(date) {
5276
+ if (date === null) {
5277
+ this._expiresAt = null;
5278
+ } else {
5279
+ this._expiresAt = date instanceof Date ? date.toISOString() : date;
5280
+ }
5281
+ return this;
5282
+ }
5283
+ /**
5284
+ * Set expiry as a duration from now.
5285
+ * @param ms Duration in milliseconds.
5286
+ *
5287
+ * @example
5288
+ * builder.expiresIn(7 * 24 * 60 * 60 * 1000) // 7 days
5289
+ */
5290
+ expiresIn(ms) {
5291
+ this._expiresAt = new Date(Date.now() + ms).toISOString();
5292
+ return this;
5293
+ }
5294
+ /**
5295
+ * Create a one-time use invite (`maxUses = 1`).
5296
+ */
5297
+ setOneTimeUse() {
5298
+ return this.setMaxUses(1);
5299
+ }
5300
+ /**
5301
+ * Create a permanent invite with no expiry or usage limit.
5302
+ */
5303
+ setPermanent() {
5304
+ this._maxUses = null;
5305
+ this._expiresAt = null;
5306
+ return this;
5307
+ }
5308
+ /**
5309
+ * Build the `InviteCreateOptions` object.
5310
+ */
5311
+ build() {
5312
+ return {
5313
+ ...this._maxUses !== void 0 ? { maxUses: this._maxUses } : {},
5314
+ ...this._expiresAt !== void 0 ? { expiresAt: this._expiresAt } : {}
5315
+ };
5316
+ }
5317
+ };
5318
+
5319
+ // src/builders/AutoModRuleBuilder.ts
5320
+ var AutoModRuleBuilder = class {
5321
+ constructor() {
5322
+ this._value = "";
5323
+ this._enabled = true;
5324
+ }
5325
+ /** Set the rule type explicitly. */
5326
+ setType(type) {
5327
+ this._type = type;
5328
+ return this;
5329
+ }
5330
+ /**
5331
+ * Set the blocked value (word or domain).
5332
+ */
5333
+ setValue(value) {
5334
+ this._value = value;
5335
+ return this;
5336
+ }
5337
+ /**
5338
+ * Shorthand: set type to BLOCKED_WORD and optionally set the value.
5339
+ *
5340
+ * @example
5341
+ * builder.blockWord('badword')
5342
+ */
5343
+ blockWord(word) {
5344
+ this._type = "BLOCKED_WORD";
5345
+ if (word !== void 0) this._value = word;
5346
+ return this;
5347
+ }
5348
+ /**
5349
+ * Shorthand: set type to BLOCKED_LINK and optionally set the domain.
5350
+ *
5351
+ * @example
5352
+ * builder.blockLink('spam.com')
5353
+ */
5354
+ blockLink(domain) {
5355
+ this._type = "BLOCKED_LINK";
5356
+ if (domain !== void 0) this._value = domain;
5357
+ return this;
5358
+ }
5359
+ /**
5360
+ * Whether the rule is active immediately.
5361
+ * @default true
5362
+ */
5363
+ setEnabled(enabled) {
5364
+ this._enabled = enabled;
5365
+ return this;
5366
+ }
5367
+ /**
5368
+ * Validate and build the `CreateAutoModRuleOptions` object.
5369
+ *
5370
+ * @throws if type or value is not set.
5371
+ */
5372
+ build() {
5373
+ if (!this._type) {
5374
+ throw new Error("[AutoModRuleBuilder] type is required \u2014 call .setType(), .blockWord(), or .blockLink()");
5375
+ }
5376
+ if (!this._value.trim()) {
5377
+ throw new Error("[AutoModRuleBuilder] value is required \u2014 call .setValue(), .blockWord(word), or .blockLink(domain)");
5378
+ }
5379
+ return {
5380
+ type: this._type,
5381
+ value: this._value.trim(),
5382
+ enabled: this._enabled
5383
+ };
5384
+ }
5385
+ };
5386
+
5387
+ // src/builders/ContextMenuCommandBuilder.ts
5388
+ var ContextMenuCommandBuilder = class {
5389
+ constructor() {
5390
+ this._name = "";
5391
+ this._target = "MESSAGE";
5392
+ this._guildId = null;
5393
+ }
5394
+ /**
5395
+ * Display name of the command (shown in the context menu).
5396
+ * Max 32 characters. May contain spaces.
5397
+ *
5398
+ * @example
5399
+ * builder.setName('Report Message')
5400
+ */
5401
+ setName(name) {
5402
+ this._name = name.trim();
5403
+ return this;
5404
+ }
5405
+ /**
5406
+ * Set whether the command targets messages or users.
5407
+ *
5408
+ * @example
5409
+ * builder.setTarget('USER')
5410
+ */
5411
+ setTarget(target) {
5412
+ this._target = target;
5413
+ return this;
5414
+ }
5415
+ /**
5416
+ * Shorthand for `setTarget('MESSAGE')`.
5417
+ *
5418
+ * @example
5419
+ * builder.forMessages()
5420
+ */
5421
+ forMessages() {
5422
+ return this.setTarget("MESSAGE");
5423
+ }
5424
+ /**
5425
+ * Shorthand for `setTarget('USER')`.
5426
+ *
5427
+ * @example
5428
+ * builder.forUsers()
5429
+ */
5430
+ forUsers() {
5431
+ return this.setTarget("USER");
5432
+ }
5433
+ /**
5434
+ * Register this command only for a specific server (guild).
5435
+ * Omit (or pass `null`) to make the command available globally.
5436
+ *
5437
+ * @example
5438
+ * builder.setGuildId('server-id')
5439
+ */
5440
+ setGuildId(guildId) {
5441
+ this._guildId = guildId;
5442
+ return this;
5443
+ }
5444
+ /**
5445
+ * Validate and return the final command definition object.
5446
+ * Throws if `name` has not been set.
5447
+ */
5448
+ build() {
5449
+ if (!this._name) {
5450
+ throw new Error("[ContextMenuCommandBuilder] Command name is required");
5451
+ }
5452
+ return {
5453
+ name: this._name,
5454
+ target: this._target,
5455
+ guildId: this._guildId
5456
+ };
5457
+ }
5458
+ /** Alias for `build()`. */
5459
+ toJSON() {
5460
+ return this.build();
5461
+ }
5462
+ };
5463
+
5464
+ // src/utils/MessageCollector.ts
5465
+ var MessageCollector = class {
5466
+ constructor(emitter, options = {}) {
5467
+ this._emitter = emitter;
5468
+ this._options = {
5469
+ count: options.count ?? 1,
5470
+ timeout: options.timeout ?? 6e4,
5471
+ filter: options.filter ?? (() => true)
5472
+ };
5473
+ }
5474
+ /**
5475
+ * Start collecting messages.
5476
+ * Returns a promise that resolves with the collected `NovaMessage` array.
5477
+ */
5478
+ run() {
5479
+ return new Promise((resolve) => {
5480
+ const collected = [];
5481
+ const cleanup = () => {
5482
+ clearTimeout(timer);
5483
+ this._emitter.off("messageCreate", handler);
5484
+ };
5485
+ const handler = (msg) => {
5486
+ if (!this._options.filter(msg)) return;
5487
+ collected.push(msg);
5488
+ if (collected.length >= this._options.count) {
5489
+ cleanup();
5490
+ resolve([...collected]);
5491
+ }
5492
+ };
5493
+ const timer = setTimeout(() => {
5494
+ this._emitter.off("messageCreate", handler);
5495
+ resolve([...collected]);
5496
+ }, this._options.timeout);
5497
+ this._emitter.on("messageCreate", handler);
5498
+ });
5499
+ }
5500
+ };
5501
+
5502
+ // src/utils/InteractionCollector.ts
5503
+ var InteractionCollector = class {
5504
+ constructor(emitter, options = {}) {
5505
+ this._emitter = emitter;
5506
+ this._options = {
5507
+ count: options.count ?? 1,
5508
+ timeout: options.timeout ?? 6e4,
5509
+ filter: options.filter ?? (() => true),
5510
+ messageId: options.messageId
5511
+ };
5512
+ }
5513
+ /**
5514
+ * Start collecting interactions.
5515
+ * Returns a promise that resolves with the collected `NovaInteraction` array.
5516
+ */
5517
+ run() {
5518
+ return new Promise((resolve) => {
5519
+ const collected = [];
5520
+ const cleanup = () => {
5521
+ clearTimeout(timer);
5522
+ this._emitter.off("interactionCreate", handler);
5523
+ };
5524
+ const handler = (interaction) => {
5525
+ if (this._options.messageId && interaction.triggerMsgId !== this._options.messageId) return;
5526
+ if (!this._options.filter(interaction)) return;
5527
+ collected.push(interaction);
5528
+ if (collected.length >= this._options.count) {
5529
+ cleanup();
5530
+ resolve([...collected]);
5531
+ }
5532
+ };
5533
+ const timer = setTimeout(() => {
5534
+ this._emitter.off("interactionCreate", handler);
5535
+ resolve([...collected]);
5536
+ }, this._options.timeout);
5537
+ this._emitter.on("interactionCreate", handler);
5538
+ });
5539
+ }
5540
+ };
5541
+
5542
+ // src/utils/MentionParser.ts
5543
+ var MentionParser = class {
5544
+ // ─── Extraction ────────────────────────────────────────────────────────────
5545
+ /**
5546
+ * Extract all user IDs mentioned in the content (`<@userId>`).
5547
+ *
5548
+ * @example
5549
+ * MentionParser.userIds('Hello <@abc> and <@xyz>!') // ['abc', 'xyz']
5550
+ */
5551
+ static userIds(content) {
5552
+ return [...content.matchAll(/<@([a-zA-Z0-9_-]+)>/g)].map((m) => m[1]);
5553
+ }
5554
+ /**
5555
+ * Extract all channel IDs mentioned in the content (`<#channelId>`).
5556
+ *
5557
+ * @example
5558
+ * MentionParser.channelIds('Go to <#general>!') // ['general']
5559
+ */
5560
+ static channelIds(content) {
5561
+ return [...content.matchAll(/<#([a-zA-Z0-9_-]+)>/g)].map((m) => m[1]);
5562
+ }
5563
+ /**
5564
+ * Extract all role IDs mentioned in the content (`<&roleId>`).
5565
+ *
5566
+ * @example
5567
+ * MentionParser.roleIds('Paging <&mods>!') // ['mods']
5568
+ */
5569
+ static roleIds(content) {
5570
+ return [...content.matchAll(/<&([a-zA-Z0-9_-]+)>/g)].map((m) => m[1]);
5571
+ }
5572
+ /**
5573
+ * Extract all custom emoji IDs mentioned in the content (`<:name:id>`).
5574
+ *
5575
+ * @example
5576
+ * MentionParser.emojiIds('Love it <:nova:12345>!')
5577
+ * // [{ name: 'nova', id: '12345' }]
5578
+ */
5579
+ static emojiIds(content) {
5580
+ return [...content.matchAll(/<:([a-zA-Z0-9_]+):([a-zA-Z0-9_-]+)>/g)].map((m) => ({
5581
+ name: m[1],
5582
+ id: m[2]
5583
+ }));
5584
+ }
5585
+ // ─── Format ─────────────────────────────────────────────────────────────────
5586
+ /**
5587
+ * Format a user ID into a mention string `<@userId>`.
5588
+ *
5589
+ * @example
5590
+ * MentionParser.user('abc123') // '<@abc123>'
5591
+ */
5592
+ static user(userId) {
5593
+ return `<@${userId}>`;
5594
+ }
5595
+ /**
5596
+ * Format a channel ID into a mention string `<#channelId>`.
5597
+ *
5598
+ * @example
5599
+ * MentionParser.channel('general') // '<#general>'
5600
+ */
5601
+ static channel(channelId) {
5602
+ return `<#${channelId}>`;
5603
+ }
5604
+ /**
5605
+ * Format a role ID into a mention string `<&roleId>`.
5606
+ *
5607
+ * @example
5608
+ * MentionParser.role('mods') // '<&mods>'
5609
+ */
5610
+ static role(roleId) {
5611
+ return `<&${roleId}>`;
5612
+ }
5613
+ /**
5614
+ * Format a custom emoji mention `<:name:id>`.
5615
+ *
5616
+ * @example
5617
+ * MentionParser.emoji('nova', '12345') // '<:nova:12345>'
5618
+ */
5619
+ static emoji(name, id) {
5620
+ return `<:${name}:${id}>`;
5621
+ }
5622
+ // ─── Clean ──────────────────────────────────────────────────────────────────
5623
+ /**
5624
+ * Remove all mention tokens from `content` and return the cleaned string.
5625
+ *
5626
+ * @example
5627
+ * MentionParser.stripMentions('Hello <@u1> in <#c1>!')
5628
+ * // 'Hello in !'
5629
+ */
5630
+ static stripMentions(content) {
5631
+ return content.replace(/<@[a-zA-Z0-9_-]+>/g, "").replace(/<#[a-zA-Z0-9_-]+>/g, "").replace(/<&[a-zA-Z0-9_-]+>/g, "").replace(/<:[a-zA-Z0-9_]+:[a-zA-Z0-9_-]+>/g, "");
5632
+ }
5633
+ /**
5634
+ * Returns `true` if `content` mentions at least one user.
5635
+ * Optionally checks for a specific `userId`.
5636
+ *
5637
+ * @example
5638
+ * MentionParser.hasMention('Hi <@u1>') // true
5639
+ * MentionParser.hasMention('Hi <@u1>', 'u1') // true
5640
+ * MentionParser.hasMention('Hi <@u1>', 'u2') // false
5641
+ */
5642
+ static hasMention(content, userId) {
5643
+ if (userId) return content.includes(`<@${userId}>`);
5644
+ return /<@[a-zA-Z0-9_-]+>/.test(content);
5645
+ }
5646
+ /**
5647
+ * Returns `true` if `content` mentions a specific bot by its ID.
5648
+ *
5649
+ * @example
5650
+ * if (MentionParser.mentionsBotId(message.content, client.user.id)) {
5651
+ * await message.reply('You called?')
5652
+ * }
5653
+ */
5654
+ static mentionsBotId(content, botId) {
5655
+ return content.includes(`<@${botId}>`);
5656
+ }
5657
+ };
5658
+
5659
+ // src/utils/EmbedPaginator.ts
5660
+ var EmbedPaginator = class {
5661
+ constructor(emitter, messages, interactions, options) {
5662
+ this.emitter = emitter;
5663
+ this.messages = messages;
5664
+ this.interactions = interactions;
5665
+ this.options = options;
5666
+ this.page = 0;
5667
+ const size = Math.max(1, options.pageSize ?? 10);
5668
+ const pages = [];
5669
+ for (let i = 0; i < options.items.length; i += size) {
5670
+ pages.push(options.items.slice(i, i + size));
5671
+ }
5672
+ this.pages = pages;
5673
+ }
5674
+ /** Total number of pages. */
5675
+ get totalPages() {
5676
+ return this.pages.length;
5677
+ }
5678
+ /**
5679
+ * Send the first page to the configured channel.
5680
+ * If there are multiple pages, attaches ◀ ▶ navigation buttons and
5681
+ * listens for clicks for up to `options.timeout` ms.
5682
+ */
5683
+ async send() {
5684
+ if (this.pages.length === 0) {
5685
+ await this.messages.send(this.options.channelId, { content: "*Nothing to display.*" });
5686
+ return;
5687
+ }
5688
+ const msg = await this.messages.send(this.options.channelId, this._buildPayload());
5689
+ if (this.pages.length <= 1) return;
5690
+ const timeout = this.options.timeout ?? 6e4;
5691
+ const deadline = Date.now() + timeout;
5692
+ while (true) {
5693
+ const remaining = deadline - Date.now();
5694
+ if (remaining <= 0) break;
5695
+ const click = await this._awaitClick(msg.id, remaining);
5696
+ if (!click) break;
5697
+ if (click.customId === "_pg_prev" && this.page > 0) this.page--;
5698
+ else if (click.customId === "_pg_next" && this.page < this.pages.length - 1) this.page++;
5699
+ this.interactions.respond(click.id, { content: "\u200B", ephemeral: true }).catch(() => void 0);
5700
+ await this.messages.edit(msg.id, this._buildPayload());
5701
+ }
5702
+ }
5703
+ // ── Internal helpers ────────────────────────────────────────────────────────
5704
+ _awaitClick(msgId, timeoutMs) {
5705
+ return new Promise((resolve) => {
5706
+ const timer = setTimeout(() => {
5707
+ this.emitter.off("interactionCreate", handler);
5708
+ resolve(null);
5709
+ }, timeoutMs);
5710
+ const handler = (i) => {
5711
+ const isOurs = i.triggerMsgId === msgId && (i.customId === "_pg_prev" || i.customId === "_pg_next");
5712
+ const isAllowed = !this.options.userId || i.userId === this.options.userId;
5713
+ if (isOurs && isAllowed) {
5714
+ clearTimeout(timer);
5715
+ this.emitter.off("interactionCreate", handler);
5716
+ resolve({ id: i.id, customId: i.customId });
5717
+ }
5718
+ };
5719
+ this.emitter.on("interactionCreate", handler);
5720
+ });
5721
+ }
5722
+ _buildPayload() {
5723
+ const page = this.pages[this.page];
5724
+ const embed = this.options.renderPage(page, this.page, this.pages.length);
5725
+ if (this.options.showPageCounter !== false && this.pages.length > 1) {
5726
+ const counter = `Page ${this.page + 1} / ${this.pages.length}`;
5727
+ embed.footer = embed.footer ? `${embed.footer} \u2022 ${counter}` : counter;
5728
+ }
5729
+ const row = new ActionRowBuilder().addComponent(
5730
+ new ButtonBuilder().setCustomId("_pg_prev").setLabel("\u25C0").setStyle("secondary").setDisabled(this.page === 0)
5731
+ ).addComponent(
5732
+ new ButtonBuilder().setCustomId("_pg_next").setLabel("\u25B6").setStyle("secondary").setDisabled(this.page === this.pages.length - 1)
5733
+ );
5734
+ return { embed, components: row.toJSON() };
5735
+ }
5736
+ };
5737
+
5738
+ // src/utils/ConfirmationDialog.ts
5739
+ var ConfirmationDialog = class {
5740
+ constructor(emitter, messages, interactions) {
5741
+ this.emitter = emitter;
5742
+ this.messages = messages;
5743
+ this.interactions = interactions;
5744
+ }
5745
+ /**
5746
+ * Send the confirmation prompt and await the user's choice.
5747
+ * Returns `true` for confirm, `false` for cancel or timeout.
5748
+ */
5749
+ async ask(options) {
5750
+ const content = options.content ?? "Are you sure?";
5751
+ const confirmLabel = options.confirmLabel ?? "Confirm";
5752
+ const cancelLabel = options.cancelLabel ?? "Cancel";
5753
+ const timeout = options.timeout ?? 3e4;
5754
+ const row = new ActionRowBuilder().addComponent(
5755
+ new ButtonBuilder().setCustomId("_confirm_yes").setLabel(confirmLabel).setStyle("success")
5756
+ ).addComponent(
5757
+ new ButtonBuilder().setCustomId("_confirm_no").setLabel(cancelLabel).setStyle("danger")
5758
+ );
5759
+ const msg = await this.messages.send(options.channelId, {
5760
+ content,
5761
+ components: row.toJSON()
5762
+ });
5763
+ const click = await this._awaitClick(msg.id, options.userId, timeout);
5764
+ const disabledRow = new ActionRowBuilder().addComponent(
5765
+ new ButtonBuilder().setCustomId("_confirm_yes").setLabel(confirmLabel).setStyle("success").setDisabled(true)
5766
+ ).addComponent(
5767
+ new ButtonBuilder().setCustomId("_confirm_no").setLabel(cancelLabel).setStyle("danger").setDisabled(true)
5768
+ );
5769
+ await this.messages.edit(msg.id, { content, components: disabledRow.toJSON() }).catch(() => void 0);
5770
+ if (click) {
5771
+ this.interactions.respond(click.id, { content: "\u200B", ephemeral: true }).catch(() => void 0);
5772
+ return click.customId === "_confirm_yes";
5773
+ }
5774
+ return false;
5775
+ }
5776
+ _awaitClick(msgId, userId, timeoutMs) {
5777
+ return new Promise((resolve) => {
5778
+ const timer = setTimeout(() => {
5779
+ this.emitter.off("interactionCreate", handler);
5780
+ resolve(null);
5781
+ }, timeoutMs);
5782
+ const handler = (i) => {
5783
+ const isOurs = i.triggerMsgId === msgId && (i.customId === "_confirm_yes" || i.customId === "_confirm_no");
5784
+ const isAllowed = !userId || i.userId === userId;
5785
+ if (isOurs && isAllowed && typeof i.customId === "string") {
5786
+ clearTimeout(timer);
5787
+ this.emitter.off("interactionCreate", handler);
5788
+ resolve({ id: i.id, customId: i.customId });
5789
+ }
5790
+ };
5791
+ this.emitter.on("interactionCreate", handler);
5792
+ });
5793
+ }
5794
+ };
5795
+
5796
+ // src/utils/ArgumentParser.ts
5797
+ var ParsedArguments = class {
5798
+ constructor(positional, flags) {
5799
+ this.positional = positional;
5800
+ this._flags = flags;
5801
+ }
5802
+ /**
5803
+ * Whether a named flag is present (regardless of value).
5804
+ *
5805
+ * @example
5806
+ * args.has('notify') // true for --notify or --notify true
5807
+ */
5808
+ has(flag) {
5809
+ return this._flags.has(flag);
5810
+ }
5811
+ /**
5812
+ * Get the string value of a named flag, or `null` if absent.
5813
+ *
5814
+ * @example
5815
+ * args.get('reason') // 'rule violation'
5816
+ */
5817
+ get(flag) {
5818
+ return this._flags.get(flag) ?? null;
5819
+ }
5820
+ /**
5821
+ * Get the string value of a named flag, throwing if missing.
5822
+ *
5823
+ * @example
5824
+ * const reason = args.require('reason') // throws if --reason not supplied
5825
+ */
5826
+ require(flag) {
5827
+ const v = this._flags.get(flag);
5828
+ if (v == null) throw new Error(`[ArgumentParser] Required flag "--${flag}" is missing`);
5829
+ return v;
5830
+ }
5831
+ /**
5832
+ * Parse the flag value as an integer, or `null` if absent / not a number.
5833
+ *
5834
+ * @example
5835
+ * args.getInt('days') // 7 from '--days 7'
5836
+ */
5837
+ getInt(flag) {
5838
+ const v = this._flags.get(flag);
5839
+ if (v == null) return null;
5840
+ const n = parseInt(v, 10);
5841
+ return isNaN(n) ? null : n;
5842
+ }
5843
+ /**
5844
+ * Parse the flag value as a float, or `null` if absent / not a number.
5845
+ */
5846
+ getFloat(flag) {
5847
+ const v = this._flags.get(flag);
5848
+ if (v == null) return null;
5849
+ const n = parseFloat(v);
5850
+ return isNaN(n) ? null : n;
5851
+ }
5852
+ /**
5853
+ * Get a positional argument by index (0-based), or `null` if absent.
5854
+ *
5855
+ * @example
5856
+ * args.at(0) // first positional token
5857
+ */
5858
+ at(index) {
5859
+ return this.positional[index] ?? null;
5860
+ }
5861
+ /**
5862
+ * Get positional argument at `index` as an integer, or `null`.
5863
+ *
5864
+ * @example
5865
+ * args.getInt(1) // 7 when positional is ['nick123', '7']
5866
+ */
5867
+ getIntAt(index) {
5868
+ const v = this.positional[index];
5869
+ if (v == null) return null;
5870
+ const n = parseInt(v, 10);
5871
+ return isNaN(n) ? null : n;
5872
+ }
5873
+ /**
5874
+ * All flag names that were parsed.
5875
+ *
5876
+ * @example
5877
+ * args.flags() // ['reason', 'notify']
5878
+ */
5879
+ flags() {
5880
+ return [...this._flags.keys()];
5881
+ }
5882
+ toJSON() {
5883
+ return {
5884
+ positional: this.positional,
5885
+ flags: Object.fromEntries(this._flags)
5886
+ };
5887
+ }
5888
+ };
5889
+ var ArgumentParser = class _ArgumentParser {
5890
+ /**
5891
+ * Parse a raw string of arguments.
5892
+ * Returns a `ParsedArguments` instance.
5893
+ */
5894
+ static parse(input) {
5895
+ const positional = [];
5896
+ const flags = /* @__PURE__ */ new Map();
5897
+ const tokens = _ArgumentParser._tokenise(input);
5898
+ let i = 0;
5899
+ while (i < tokens.length) {
5900
+ const token = tokens[i];
5901
+ if (token.startsWith("--")) {
5902
+ const key = token.slice(2);
5903
+ const next = tokens[i + 1];
5904
+ if (next && !next.startsWith("-")) {
5905
+ flags.set(key, next);
5906
+ i += 2;
5907
+ } else {
5908
+ flags.set(key, "true");
5909
+ i++;
5910
+ }
5911
+ } else if (token.startsWith("-") && token.length === 2) {
5912
+ flags.set(token.slice(1), "true");
5913
+ i++;
5914
+ } else {
5915
+ positional.push(token);
5916
+ i++;
5917
+ }
5918
+ }
5919
+ return new ParsedArguments(positional, flags);
5920
+ }
5921
+ /**
5922
+ * Strip a user mention like `<@123456>` from the start of the input string
5923
+ * and return the remainder trimmed. Returns the original string unchanged if
5924
+ * no leading mention is found.
5925
+ *
5926
+ * @example
5927
+ * ArgumentParser.stripMention('<@98765> hello world') // 'hello world'
5928
+ */
5929
+ static stripMention(input) {
5930
+ return input.replace(/^<@!?\d+>\s*/, "").trim();
5931
+ }
5932
+ /**
5933
+ * Split `input` into tokens. Quoted strings are treated as single tokens.
5934
+ */
5935
+ static _tokenise(input) {
5936
+ const tokens = [];
5937
+ let current = "";
5938
+ let inDouble = false;
5939
+ let inSingle = false;
5940
+ for (let i = 0; i < input.length; i++) {
5941
+ const ch = input[i];
5942
+ if (ch === '"' && !inSingle) {
5943
+ inDouble = !inDouble;
5944
+ continue;
5945
+ }
5946
+ if (ch === "'" && !inDouble) {
5947
+ inSingle = !inSingle;
5948
+ continue;
5949
+ }
5950
+ if (ch === " " && !inDouble && !inSingle) {
5951
+ if (current.length > 0) {
5952
+ tokens.push(current);
5953
+ current = "";
5954
+ }
5955
+ continue;
5956
+ }
5957
+ current += ch;
5958
+ }
5959
+ if (current.length > 0) tokens.push(current);
5960
+ return tokens;
5961
+ }
5962
+ };
5963
+
5964
+ // src/utils/CacheMap.ts
5965
+ var CacheMap = class {
5966
+ constructor() {
5967
+ this._store = /* @__PURE__ */ new Map();
5968
+ }
5969
+ /**
5970
+ * Store a value.
5971
+ *
5972
+ * @param key - Cache key.
5973
+ * @param value - Value to store.
5974
+ * @param ttlMs - Optional time-to-live in milliseconds. Omit for permanent storage.
5975
+ */
5976
+ set(key, value, ttlMs) {
5977
+ this._store.set(key, {
5978
+ value,
5979
+ expiresAt: ttlMs != null ? Date.now() + ttlMs : null
5980
+ });
5981
+ return this;
5982
+ }
5983
+ /**
5984
+ * Retrieve a value, or `undefined` if absent or expired.
5985
+ * Expired entries are automatically deleted on access.
5986
+ */
5987
+ get(key) {
5988
+ const entry = this._store.get(key);
5989
+ if (!entry) return void 0;
5990
+ if (entry.expiresAt !== null && Date.now() >= entry.expiresAt) {
5991
+ this._store.delete(key);
5992
+ return void 0;
5993
+ }
5994
+ return entry.value;
5995
+ }
5996
+ /**
5997
+ * Check if a live (non-expired) entry exists for this key.
5998
+ */
5999
+ has(key) {
6000
+ return this.get(key) !== void 0;
6001
+ }
6002
+ /**
6003
+ * Delete an entry.
6004
+ * Returns `true` if the entry existed (even if it was expired).
6005
+ */
6006
+ delete(key) {
6007
+ return this._store.delete(key);
6008
+ }
6009
+ /** Remove all entries. */
6010
+ clear() {
6011
+ this._store.clear();
6012
+ }
6013
+ /**
6014
+ * Number of **live** (non-expired) entries.
6015
+ * This prunes expired entries as a side-effect.
6016
+ */
6017
+ get size() {
6018
+ this.prune();
6019
+ return this._store.size;
6020
+ }
6021
+ /**
6022
+ * Remove all expired entries immediately.
6023
+ * Useful to call occasionally to free memory.
6024
+ *
6025
+ * @example
6026
+ * // Prune every 10 minutes
6027
+ * setInterval(() => cache.prune(), 10 * 60_000)
6028
+ */
6029
+ prune() {
6030
+ const now = Date.now();
6031
+ let removed = 0;
6032
+ for (const [key, entry] of this._store) {
6033
+ if (entry.expiresAt !== null && now >= entry.expiresAt) {
6034
+ this._store.delete(key);
6035
+ removed++;
6036
+ }
6037
+ }
6038
+ return removed;
6039
+ }
6040
+ /**
6041
+ * Return all **live** keys.
6042
+ */
6043
+ keys() {
6044
+ this.prune();
6045
+ return [...this._store.keys()];
6046
+ }
6047
+ /**
6048
+ * Return all **live** values.
6049
+ */
6050
+ values() {
6051
+ this.prune();
6052
+ return [...this._store.values()].map((e) => e.value);
6053
+ }
6054
+ /**
6055
+ * Return all **live** `[key, value]` pairs.
6056
+ */
6057
+ entries() {
6058
+ this.prune();
6059
+ return [...this._store.entries()].map(([k, e]) => [k, e.value]);
6060
+ }
6061
+ /**
6062
+ * Get a value or compute & cache it if missing.
6063
+ * Very useful for "cache-aside" data loading.
6064
+ *
6065
+ * @param key - Cache key.
6066
+ * @param loader - Async function that produces the value when the cache is cold.
6067
+ * @param ttlMs - Optional TTL to apply to the newly cached value.
6068
+ *
6069
+ * @example
6070
+ * const profile = await cache.getOrSet(userId, () => client.fetchUserProfile(userId), 60_000)
6071
+ */
6072
+ async getOrSet(key, loader, ttlMs) {
6073
+ const existing = this.get(key);
6074
+ if (existing !== void 0) return existing;
6075
+ const value = await loader();
6076
+ this.set(key, value, ttlMs);
6077
+ return value;
6078
+ }
6079
+ /**
6080
+ * Return the number of milliseconds until a key expires.
6081
+ * Returns `null` if the key has no TTL, `0` if it is already expired.
6082
+ */
6083
+ ttl(key) {
6084
+ const entry = this._store.get(key);
6085
+ if (!entry) return null;
6086
+ if (entry.expiresAt === null) return null;
6087
+ return Math.max(0, entry.expiresAt - Date.now());
6088
+ }
6089
+ [Symbol.iterator]() {
6090
+ return this.entries()[Symbol.iterator]();
6091
+ }
6092
+ };
6093
+
6094
+ // src/utils/EventScheduler.ts
6095
+ var _idCounter = 0;
6096
+ function nextId() {
6097
+ return `task_${++_idCounter}`;
6098
+ }
6099
+ var EventScheduler = class {
6100
+ constructor() {
6101
+ this._tasks = /* @__PURE__ */ new Map();
6102
+ }
6103
+ /**
6104
+ * Schedule a one-shot task to run after `delayMs` milliseconds.
6105
+ * Returns a task ID that can be passed to `cancel()`.
6106
+ *
6107
+ * @example
6108
+ * const id = scheduler.schedule(5_000, () => console.log('5 seconds later'))
6109
+ * // Change your mind:
6110
+ * scheduler.cancel(id)
6111
+ */
6112
+ schedule(delayMs, fn) {
6113
+ const id = nextId();
6114
+ const handle = setTimeout(() => {
6115
+ this._tasks.delete(id);
6116
+ this._run(fn, id);
6117
+ }, delayMs);
6118
+ this._tasks.set(id, { id, handle, type: "once" });
6119
+ return id;
6120
+ }
6121
+ /**
6122
+ * Schedule a one-shot task to run at a specific `Date`.
6123
+ * If the date is in the past, the callback fires on the next event-loop tick.
6124
+ *
6125
+ * @example
6126
+ * const nextMidnight = new Date()
6127
+ * nextMidnight.setHours(24, 0, 0, 0)
6128
+ * scheduler.scheduleAt(nextMidnight, () => console.log('Midnight!'))
6129
+ */
6130
+ scheduleAt(date, fn) {
6131
+ const delay = Math.max(0, date.getTime() - Date.now());
6132
+ return this.schedule(delay, fn);
6133
+ }
6134
+ /**
6135
+ * Schedule a recurring task that fires every `intervalMs` milliseconds.
6136
+ * Returns a task ID that can be passed to `cancel()`.
6137
+ *
6138
+ * @example
6139
+ * // Check incoming messages every minute
6140
+ * const id = scheduler.repeat(60_000, () => checkForAnnouncements())
6141
+ */
6142
+ repeat(intervalMs, fn) {
6143
+ const id = nextId();
6144
+ const handle = setInterval(() => this._run(fn, id), intervalMs);
6145
+ this._tasks.set(id, { id, handle, type: "repeat" });
6146
+ return id;
6147
+ }
6148
+ /**
6149
+ * Cancel a previously scheduled task by its ID.
6150
+ * Returns `true` if the task existed, `false` if it had already fired or
6151
+ * was not found.
6152
+ *
6153
+ * @example
6154
+ * const id = scheduler.schedule(30_000, doSomething)
6155
+ * // …maybe cancel it:
6156
+ * scheduler.cancel(id)
6157
+ */
6158
+ cancel(id) {
6159
+ const task = this._tasks.get(id);
6160
+ if (!task) return false;
6161
+ if (task.type === "once") clearTimeout(task.handle);
6162
+ else clearInterval(task.handle);
6163
+ this._tasks.delete(id);
6164
+ return true;
6165
+ }
6166
+ /**
6167
+ * Cancel all pending tasks.
6168
+ * Useful during graceful shutdown.
6169
+ *
6170
+ * @example
6171
+ * client.once('disconnect', () => scheduler.cancelAll())
6172
+ */
6173
+ cancelAll() {
6174
+ for (const id of this._tasks.keys()) this.cancel(id);
6175
+ }
6176
+ /**
6177
+ * Number of currently active (pending or recurring) tasks.
6178
+ */
6179
+ get pendingCount() {
6180
+ return this._tasks.size;
6181
+ }
6182
+ /**
6183
+ * IDs of all currently registered tasks.
6184
+ */
6185
+ taskIds() {
6186
+ return [...this._tasks.keys()];
6187
+ }
6188
+ _run(fn, id) {
6189
+ try {
6190
+ const result = fn();
6191
+ if (result && typeof result.catch === "function") {
6192
+ ;
6193
+ result.catch((e) => {
6194
+ console.error(`[EventScheduler] Task ${id} threw:`, e);
6195
+ });
6196
+ }
6197
+ } catch (e) {
6198
+ console.error(`[EventScheduler] Task ${id} threw:`, e);
6199
+ }
6200
+ }
6201
+ };
3741
6202
  export {
3742
6203
  ActionRowBuilder,
6204
+ ArgumentParser,
3743
6205
  AuditLogAPI,
6206
+ AutoModAPI,
6207
+ AutoModRuleBuilder,
3744
6208
  ButtonBuilder,
6209
+ CacheMap,
6210
+ CategoriesAPI,
6211
+ ChannelBuilder,
3745
6212
  ChannelsAPI,
3746
6213
  Collection,
3747
6214
  CommandsAPI,
6215
+ ConfirmationDialog,
6216
+ ContextMenuCommandBuilder,
3748
6217
  Cooldown,
3749
6218
  CooldownManager,
3750
6219
  EmbedBuilder,
6220
+ EmbedPaginator,
6221
+ EventBuilder,
6222
+ EventScheduler,
6223
+ EventsAPI,
6224
+ ForumAPI,
6225
+ ForumPostBuilder,
3751
6226
  HttpClient,
6227
+ InteractionCollector,
3752
6228
  InteractionOptions,
3753
6229
  InteractionsAPI,
6230
+ InviteBuilder,
3754
6231
  InvitesAPI,
3755
6232
  Logger,
3756
6233
  MembersAPI,
6234
+ MentionParser,
3757
6235
  MessageBuilder,
6236
+ MessageCollector,
3758
6237
  MessagesAPI,
3759
6238
  ModalBuilder,
6239
+ NovaBan,
6240
+ NovaCategory,
3760
6241
  NovaChannel,
3761
6242
  NovaClient,
6243
+ NovaForumPost,
3762
6244
  NovaInteraction,
3763
6245
  NovaInvite,
3764
6246
  NovaMember,
3765
6247
  NovaMessage,
3766
6248
  NovaRole,
6249
+ NovaServerEvent,
3767
6250
  NovaServerWrapper,
6251
+ NovaWarning,
3768
6252
  NovaWebhook,
3769
6253
  Paginator,
6254
+ ParsedArguments,
3770
6255
  Permissions,
3771
6256
  PermissionsAPI,
3772
6257
  PermissionsBitfield,
3773
6258
  PollBuilder,
3774
6259
  ReactionsAPI,
6260
+ RoleBuilder,
3775
6261
  RolesAPI,
3776
6262
  SelectMenuBuilder,
3777
6263
  ServersAPI,
3778
6264
  SlashCommandBuilder,
3779
6265
  SlashCommandOptionBuilder,
3780
6266
  TextInputBuilder,
6267
+ UsersAPI,
3781
6268
  WebhooksAPI,
3782
6269
  countdown,
3783
6270
  formatDuration,