novaapp-sdk 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +610 -5
- package/dist/index.d.ts +610 -5
- package/dist/index.js +702 -2
- package/dist/index.mjs +690 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -36,17 +36,28 @@ __export(index_exports, {
|
|
|
36
36
|
MessageBuilder: () => MessageBuilder,
|
|
37
37
|
MessagesAPI: () => MessagesAPI,
|
|
38
38
|
ModalBuilder: () => ModalBuilder,
|
|
39
|
+
NovaChannel: () => NovaChannel,
|
|
39
40
|
NovaClient: () => NovaClient,
|
|
40
41
|
NovaInteraction: () => NovaInteraction,
|
|
42
|
+
NovaMember: () => NovaMember,
|
|
41
43
|
NovaMessage: () => NovaMessage,
|
|
44
|
+
Paginator: () => Paginator,
|
|
45
|
+
Permissions: () => Permissions,
|
|
42
46
|
PermissionsAPI: () => PermissionsAPI,
|
|
47
|
+
PermissionsBitfield: () => PermissionsBitfield,
|
|
43
48
|
PollBuilder: () => PollBuilder,
|
|
44
49
|
ReactionsAPI: () => ReactionsAPI,
|
|
45
50
|
SelectMenuBuilder: () => SelectMenuBuilder,
|
|
46
51
|
ServersAPI: () => ServersAPI,
|
|
47
52
|
SlashCommandBuilder: () => SlashCommandBuilder,
|
|
48
53
|
SlashCommandOptionBuilder: () => SlashCommandOptionBuilder,
|
|
49
|
-
TextInputBuilder: () => TextInputBuilder
|
|
54
|
+
TextInputBuilder: () => TextInputBuilder,
|
|
55
|
+
countdown: () => countdown,
|
|
56
|
+
formatDuration: () => formatDuration,
|
|
57
|
+
formatRelative: () => formatRelative,
|
|
58
|
+
parseTimestamp: () => parseTimestamp,
|
|
59
|
+
sleep: () => sleep,
|
|
60
|
+
withTimeout: () => withTimeout
|
|
50
61
|
});
|
|
51
62
|
module.exports = __toCommonJS(index_exports);
|
|
52
63
|
|
|
@@ -1074,6 +1085,248 @@ var NovaMessage = class _NovaMessage {
|
|
|
1074
1085
|
}
|
|
1075
1086
|
};
|
|
1076
1087
|
|
|
1088
|
+
// src/structures/NovaChannel.ts
|
|
1089
|
+
var NovaChannel = class _NovaChannel {
|
|
1090
|
+
constructor(raw, channels, messages) {
|
|
1091
|
+
this.id = raw.id;
|
|
1092
|
+
this.name = raw.name;
|
|
1093
|
+
this.type = raw.type;
|
|
1094
|
+
this.serverId = raw.serverId;
|
|
1095
|
+
this.topic = raw.topic;
|
|
1096
|
+
this.position = raw.position;
|
|
1097
|
+
this.slowMode = raw.slowMode ?? 0;
|
|
1098
|
+
this.createdAt = new Date(raw.createdAt);
|
|
1099
|
+
this._channels = channels;
|
|
1100
|
+
this._messages = messages;
|
|
1101
|
+
}
|
|
1102
|
+
// ─── Type guards ────────────────────────────────────────────────────────────
|
|
1103
|
+
/** `true` for text channels. */
|
|
1104
|
+
isText() {
|
|
1105
|
+
return this.type === "TEXT";
|
|
1106
|
+
}
|
|
1107
|
+
/** `true` for voice channels. */
|
|
1108
|
+
isVoice() {
|
|
1109
|
+
return this.type === "VOICE";
|
|
1110
|
+
}
|
|
1111
|
+
/** `true` for announcement channels. */
|
|
1112
|
+
isAnnouncement() {
|
|
1113
|
+
return this.type === "ANNOUNCEMENT";
|
|
1114
|
+
}
|
|
1115
|
+
/** `true` for forum channels. */
|
|
1116
|
+
isForum() {
|
|
1117
|
+
return this.type === "FORUM";
|
|
1118
|
+
}
|
|
1119
|
+
/** `true` for stage channels. */
|
|
1120
|
+
isStage() {
|
|
1121
|
+
return this.type === "STAGE";
|
|
1122
|
+
}
|
|
1123
|
+
/** `true` if slow-mode is enabled on this channel. */
|
|
1124
|
+
hasSlowMode() {
|
|
1125
|
+
return this.slowMode > 0;
|
|
1126
|
+
}
|
|
1127
|
+
// ─── Messaging ──────────────────────────────────────────────────────────────
|
|
1128
|
+
/**
|
|
1129
|
+
* Send a message to this channel.
|
|
1130
|
+
* Accepts a plain string or a full options object.
|
|
1131
|
+
*
|
|
1132
|
+
* @example
|
|
1133
|
+
* await channel.send('Hello!')
|
|
1134
|
+
* await channel.send({ content: 'Hi', embed: { title: 'Stats' } })
|
|
1135
|
+
*/
|
|
1136
|
+
send(options) {
|
|
1137
|
+
const body = typeof options === "string" ? { content: options } : options;
|
|
1138
|
+
return this._messages.send(this.id, body);
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Fetch recent messages from this channel.
|
|
1142
|
+
*
|
|
1143
|
+
* @example
|
|
1144
|
+
* const messages = await channel.fetchMessages({ limit: 50 })
|
|
1145
|
+
*/
|
|
1146
|
+
fetchMessages(options = {}) {
|
|
1147
|
+
return this._channels.fetchMessages(this.id, options);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Fetch all pinned messages in this channel.
|
|
1151
|
+
*
|
|
1152
|
+
* @example
|
|
1153
|
+
* const pins = await channel.fetchPins()
|
|
1154
|
+
*/
|
|
1155
|
+
fetchPins() {
|
|
1156
|
+
return this._channels.fetchPins(this.id);
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Start a typing indicator (shows "Bot is typing…" for ~5 seconds).
|
|
1160
|
+
*
|
|
1161
|
+
* @example
|
|
1162
|
+
* await channel.startTyping()
|
|
1163
|
+
*/
|
|
1164
|
+
startTyping() {
|
|
1165
|
+
return this._channels.startTyping(this.id);
|
|
1166
|
+
}
|
|
1167
|
+
// ─── Management ─────────────────────────────────────────────────────────────
|
|
1168
|
+
/**
|
|
1169
|
+
* Edit this channel's properties.
|
|
1170
|
+
* Requires the `channels.manage` scope.
|
|
1171
|
+
*
|
|
1172
|
+
* @example
|
|
1173
|
+
* await channel.edit({ name: 'general-2', topic: 'The second general channel' })
|
|
1174
|
+
*/
|
|
1175
|
+
async edit(options) {
|
|
1176
|
+
const raw = await this._channels.edit(this.id, options);
|
|
1177
|
+
return new _NovaChannel(raw, this._channels, this._messages);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Delete this channel.
|
|
1181
|
+
* Requires the `channels.manage` scope.
|
|
1182
|
+
*
|
|
1183
|
+
* @example
|
|
1184
|
+
* await channel.delete()
|
|
1185
|
+
*/
|
|
1186
|
+
delete() {
|
|
1187
|
+
return this._channels.delete(this.id);
|
|
1188
|
+
}
|
|
1189
|
+
// ─── Serialisation ──────────────────────────────────────────────────────────
|
|
1190
|
+
/**
|
|
1191
|
+
* Returns the channel as a mention-style string: `#name`.
|
|
1192
|
+
*/
|
|
1193
|
+
toString() {
|
|
1194
|
+
return `#${this.name}`;
|
|
1195
|
+
}
|
|
1196
|
+
/** Returns the raw channel data. */
|
|
1197
|
+
toJSON() {
|
|
1198
|
+
return {
|
|
1199
|
+
id: this.id,
|
|
1200
|
+
name: this.name,
|
|
1201
|
+
type: this.type,
|
|
1202
|
+
serverId: this.serverId,
|
|
1203
|
+
topic: this.topic,
|
|
1204
|
+
position: this.position,
|
|
1205
|
+
slowMode: this.slowMode,
|
|
1206
|
+
createdAt: this.createdAt.toISOString()
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// src/structures/NovaMember.ts
|
|
1212
|
+
var NovaMember = class {
|
|
1213
|
+
constructor(raw, serverId, members) {
|
|
1214
|
+
this.userId = raw.user.id;
|
|
1215
|
+
this.serverId = serverId;
|
|
1216
|
+
this.username = raw.user.username;
|
|
1217
|
+
this.displayName = raw.user.displayName;
|
|
1218
|
+
this.avatar = raw.user.avatar;
|
|
1219
|
+
this.role = raw.role;
|
|
1220
|
+
this.status = raw.user.status;
|
|
1221
|
+
this.isBot = raw.user.isBot;
|
|
1222
|
+
this.joinedAt = new Date(raw.joinedAt);
|
|
1223
|
+
this._members = members;
|
|
1224
|
+
}
|
|
1225
|
+
// ─── Type guards ────────────────────────────────────────────────────────────
|
|
1226
|
+
/** `true` if this member is the server owner. */
|
|
1227
|
+
isOwner() {
|
|
1228
|
+
return this.role === "OWNER";
|
|
1229
|
+
}
|
|
1230
|
+
/** `true` if this member is an admin or the server owner. */
|
|
1231
|
+
isAdmin() {
|
|
1232
|
+
return this.role === "ADMIN" || this.role === "OWNER";
|
|
1233
|
+
}
|
|
1234
|
+
/** `true` if this member is a regular (non-privileged) member. */
|
|
1235
|
+
isRegularMember() {
|
|
1236
|
+
return this.role === "MEMBER";
|
|
1237
|
+
}
|
|
1238
|
+
/** `true` if the user is currently online. */
|
|
1239
|
+
isOnline() {
|
|
1240
|
+
return this.status === "ONLINE";
|
|
1241
|
+
}
|
|
1242
|
+
/** `true` if the user is idle / away. */
|
|
1243
|
+
isIdle() {
|
|
1244
|
+
return this.status === "IDLE";
|
|
1245
|
+
}
|
|
1246
|
+
/** `true` if the user is set to Do Not Disturb. */
|
|
1247
|
+
isDND() {
|
|
1248
|
+
return this.status === "DND";
|
|
1249
|
+
}
|
|
1250
|
+
/** `true` if the user appears offline. */
|
|
1251
|
+
isOffline() {
|
|
1252
|
+
return this.status === "OFFLINE";
|
|
1253
|
+
}
|
|
1254
|
+
// ─── Actions ────────────────────────────────────────────────────────────────
|
|
1255
|
+
/**
|
|
1256
|
+
* Kick this member from the server.
|
|
1257
|
+
* Bots cannot kick owners or admins (throws 403).
|
|
1258
|
+
*
|
|
1259
|
+
* @example
|
|
1260
|
+
* await member.kick()
|
|
1261
|
+
*/
|
|
1262
|
+
kick() {
|
|
1263
|
+
return this._members.kick(this.serverId, this.userId);
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Ban this member from the server with an optional reason.
|
|
1267
|
+
* Bots cannot ban owners or admins (throws 403).
|
|
1268
|
+
*
|
|
1269
|
+
* @example
|
|
1270
|
+
* await member.ban('Repeated rule violations')
|
|
1271
|
+
*/
|
|
1272
|
+
ban(reason) {
|
|
1273
|
+
return this._members.ban(this.serverId, this.userId, reason);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Send this user a direct message.
|
|
1277
|
+
* Requires the `messages.write` scope.
|
|
1278
|
+
*
|
|
1279
|
+
* @example
|
|
1280
|
+
* await member.dm('Welcome to the server!')
|
|
1281
|
+
* await member.dm({ content: 'Hello', embed: { title: 'Rules' } })
|
|
1282
|
+
*/
|
|
1283
|
+
dm(options) {
|
|
1284
|
+
return this._members.dm(this.userId, options);
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Assign a custom role to this member.
|
|
1288
|
+
* Requires the `members.roles` scope.
|
|
1289
|
+
*
|
|
1290
|
+
* @example
|
|
1291
|
+
* await member.addRole('role-id')
|
|
1292
|
+
*/
|
|
1293
|
+
addRole(roleId) {
|
|
1294
|
+
return this._members.addRole(this.serverId, this.userId, roleId);
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Remove a custom role from this member.
|
|
1298
|
+
* Requires the `members.roles` scope.
|
|
1299
|
+
*
|
|
1300
|
+
* @example
|
|
1301
|
+
* await member.removeRole('role-id')
|
|
1302
|
+
*/
|
|
1303
|
+
removeRole(roleId) {
|
|
1304
|
+
return this._members.removeRole(this.serverId, this.userId, roleId);
|
|
1305
|
+
}
|
|
1306
|
+
// ─── Serialisation ──────────────────────────────────────────────────────────
|
|
1307
|
+
/**
|
|
1308
|
+
* Returns a mention-style string: `@displayName`.
|
|
1309
|
+
*/
|
|
1310
|
+
toString() {
|
|
1311
|
+
return `@${this.displayName}`;
|
|
1312
|
+
}
|
|
1313
|
+
/** Returns the raw member data. */
|
|
1314
|
+
toJSON() {
|
|
1315
|
+
return {
|
|
1316
|
+
role: this.role,
|
|
1317
|
+
joinedAt: this.joinedAt.toISOString(),
|
|
1318
|
+
user: {
|
|
1319
|
+
id: this.userId,
|
|
1320
|
+
username: this.username,
|
|
1321
|
+
displayName: this.displayName,
|
|
1322
|
+
avatar: this.avatar,
|
|
1323
|
+
status: this.status,
|
|
1324
|
+
isBot: this.isBot
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
|
|
1077
1330
|
// src/client.ts
|
|
1078
1331
|
var NovaClient = class extends import_node_events.EventEmitter {
|
|
1079
1332
|
constructor(options) {
|
|
@@ -1086,6 +1339,7 @@ var NovaClient = class extends import_node_events.EventEmitter {
|
|
|
1086
1339
|
this._commandHandlers = /* @__PURE__ */ new Map();
|
|
1087
1340
|
this._buttonHandlers = /* @__PURE__ */ new Map();
|
|
1088
1341
|
this._selectHandlers = /* @__PURE__ */ new Map();
|
|
1342
|
+
this._modalHandlers = /* @__PURE__ */ new Map();
|
|
1089
1343
|
if (!options.token) {
|
|
1090
1344
|
throw new Error("[nova-bot-sdk] A bot token is required.");
|
|
1091
1345
|
}
|
|
@@ -1156,6 +1410,20 @@ var NovaClient = class extends import_node_events.EventEmitter {
|
|
|
1156
1410
|
this._selectHandlers.set(customId, handler);
|
|
1157
1411
|
return this;
|
|
1158
1412
|
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Register a handler for a modal submission by its `customId`.
|
|
1415
|
+
* Called automatically when the user submits a modal with that `customId`.
|
|
1416
|
+
*
|
|
1417
|
+
* @example
|
|
1418
|
+
* client.modal('report_modal', async (interaction) => {
|
|
1419
|
+
* const reason = interaction.modalData.reason
|
|
1420
|
+
* await interaction.replyEphemeral(`Report received: ${reason}`)
|
|
1421
|
+
* })
|
|
1422
|
+
*/
|
|
1423
|
+
modal(customId, handler) {
|
|
1424
|
+
this._modalHandlers.set(customId, handler);
|
|
1425
|
+
return this;
|
|
1426
|
+
}
|
|
1159
1427
|
/**
|
|
1160
1428
|
* Connect to the Nova WebSocket gateway.
|
|
1161
1429
|
* Resolves when the `ready` event is received.
|
|
@@ -1205,6 +1473,8 @@ var NovaClient = class extends import_node_events.EventEmitter {
|
|
|
1205
1473
|
run(this._buttonHandlers.get(interaction.customId));
|
|
1206
1474
|
} else if (interaction.isSelectMenu() && interaction.customId) {
|
|
1207
1475
|
run(this._selectHandlers.get(interaction.customId));
|
|
1476
|
+
} else if (interaction.isModalSubmit() && interaction.customId) {
|
|
1477
|
+
run(this._modalHandlers.get(interaction.customId));
|
|
1208
1478
|
}
|
|
1209
1479
|
});
|
|
1210
1480
|
this.socket.on("bot:event", (event) => {
|
|
@@ -1239,6 +1509,36 @@ var NovaClient = class extends import_node_events.EventEmitter {
|
|
|
1239
1509
|
case "message.pinned":
|
|
1240
1510
|
this.emit("messagePinned", event.data);
|
|
1241
1511
|
break;
|
|
1512
|
+
case "channel.created":
|
|
1513
|
+
this.emit("channelCreate", event.data);
|
|
1514
|
+
break;
|
|
1515
|
+
case "channel.updated":
|
|
1516
|
+
this.emit("channelUpdate", event.data);
|
|
1517
|
+
break;
|
|
1518
|
+
case "channel.deleted":
|
|
1519
|
+
this.emit("channelDelete", event.data);
|
|
1520
|
+
break;
|
|
1521
|
+
case "role.created":
|
|
1522
|
+
this.emit("roleCreate", event.data);
|
|
1523
|
+
break;
|
|
1524
|
+
case "role.deleted":
|
|
1525
|
+
this.emit("roleDelete", event.data);
|
|
1526
|
+
break;
|
|
1527
|
+
case "user.voice_joined":
|
|
1528
|
+
this.emit("voiceJoin", event.data);
|
|
1529
|
+
break;
|
|
1530
|
+
case "user.voice_left":
|
|
1531
|
+
this.emit("voiceLeave", event.data);
|
|
1532
|
+
break;
|
|
1533
|
+
case "user.banned":
|
|
1534
|
+
this.emit("memberBanned", event.data);
|
|
1535
|
+
break;
|
|
1536
|
+
case "user.unbanned":
|
|
1537
|
+
this.emit("memberUnbanned", event.data);
|
|
1538
|
+
break;
|
|
1539
|
+
case "user.updated_profile":
|
|
1540
|
+
this.emit("memberUpdate", event.data);
|
|
1541
|
+
break;
|
|
1242
1542
|
}
|
|
1243
1543
|
});
|
|
1244
1544
|
this.socket.on("bot:error", (err) => {
|
|
@@ -1295,6 +1595,83 @@ var NovaClient = class extends import_node_events.EventEmitter {
|
|
|
1295
1595
|
setStatus(status) {
|
|
1296
1596
|
this.socket?.emit("bot:status", { status });
|
|
1297
1597
|
}
|
|
1598
|
+
// ─── Rich-wrapper helpers ────────────────────────────────────────────────────
|
|
1599
|
+
/**
|
|
1600
|
+
* Fetch a single channel and return it as a rich `NovaChannel` wrapper.
|
|
1601
|
+
*
|
|
1602
|
+
* @example
|
|
1603
|
+
* const channel = await client.fetchChannel('channel-id')
|
|
1604
|
+
* await channel.send('Hello!')
|
|
1605
|
+
*/
|
|
1606
|
+
async fetchChannel(channelId) {
|
|
1607
|
+
const raw = await this.channels.fetch(channelId);
|
|
1608
|
+
return new NovaChannel(raw, this.channels, this.messages);
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Fetch all channels in a server and return them as `NovaChannel` wrappers.
|
|
1612
|
+
*
|
|
1613
|
+
* @example
|
|
1614
|
+
* const channels = await client.fetchChannels('server-id')
|
|
1615
|
+
* const textChannels = channels.filter(c => c.isText())
|
|
1616
|
+
*/
|
|
1617
|
+
async fetchChannels(serverId) {
|
|
1618
|
+
const list = await this.channels.list(serverId);
|
|
1619
|
+
return list.map((raw) => new NovaChannel(raw, this.channels, this.messages));
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Fetch all members in a server and return them as `NovaMember` wrappers.
|
|
1623
|
+
*
|
|
1624
|
+
* @example
|
|
1625
|
+
* const members = await client.fetchMembers('server-id')
|
|
1626
|
+
* const bots = members.filter(m => m.isBot)
|
|
1627
|
+
*/
|
|
1628
|
+
async fetchMembers(serverId) {
|
|
1629
|
+
const list = await this.members.list(serverId);
|
|
1630
|
+
return list.map((raw) => new NovaMember(raw, serverId, this.members));
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Fetch a single member from a server and return them as a `NovaMember` wrapper.
|
|
1634
|
+
* Throws if the user is not found in the server.
|
|
1635
|
+
*
|
|
1636
|
+
* @example
|
|
1637
|
+
* const member = await client.fetchMember('server-id', 'user-id')
|
|
1638
|
+
* await member.dm('Welcome!')
|
|
1639
|
+
*/
|
|
1640
|
+
async fetchMember(serverId, userId) {
|
|
1641
|
+
const list = await this.members.list(serverId);
|
|
1642
|
+
const raw = list.find((m) => m.user.id === userId);
|
|
1643
|
+
if (!raw) throw new Error(`[nova-bot-sdk] Member ${userId} not found in server ${serverId}`);
|
|
1644
|
+
return new NovaMember(raw, serverId, this.members);
|
|
1645
|
+
}
|
|
1646
|
+
// ─── Event fence ──────────────────────────────────────────────────────────────
|
|
1647
|
+
/**
|
|
1648
|
+
* Wait for a specific event to be emitted, optionally filtered.
|
|
1649
|
+
* Rejects after `timeoutMs` (default 30 s) if the condition never fires.
|
|
1650
|
+
*
|
|
1651
|
+
* @example
|
|
1652
|
+
* // Wait for any message in a specific channel
|
|
1653
|
+
* const msg = await client.waitFor('messageCreate', m => m.channelId === channelId)
|
|
1654
|
+
*
|
|
1655
|
+
* // Wait for a button click with a timeout
|
|
1656
|
+
* const i = await client.waitFor('interactionCreate', i => i.isButton(), 60_000)
|
|
1657
|
+
*/
|
|
1658
|
+
waitFor(event, filter, timeoutMs = 3e4) {
|
|
1659
|
+
return new Promise((resolve, reject) => {
|
|
1660
|
+
const timer = setTimeout(() => {
|
|
1661
|
+
this.off(event, handler);
|
|
1662
|
+
reject(new Error(`[nova-bot-sdk] waitFor('${String(event)}') timed out after ${timeoutMs}ms`));
|
|
1663
|
+
}, timeoutMs);
|
|
1664
|
+
const handler = (...args) => {
|
|
1665
|
+
const arg = args[0];
|
|
1666
|
+
if (!filter || filter(arg)) {
|
|
1667
|
+
clearTimeout(timer);
|
|
1668
|
+
this.off(event, handler);
|
|
1669
|
+
resolve(arg);
|
|
1670
|
+
}
|
|
1671
|
+
};
|
|
1672
|
+
this.on(event, handler);
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1298
1675
|
/**
|
|
1299
1676
|
* Disconnect from the gateway and clean up.
|
|
1300
1677
|
*/
|
|
@@ -2350,6 +2727,318 @@ var Logger = class {
|
|
|
2350
2727
|
}
|
|
2351
2728
|
}
|
|
2352
2729
|
};
|
|
2730
|
+
|
|
2731
|
+
// src/utils/Paginator.ts
|
|
2732
|
+
var Paginator = class {
|
|
2733
|
+
constructor(fetchFn) {
|
|
2734
|
+
this.fetchFn = fetchFn;
|
|
2735
|
+
this._cursor = null;
|
|
2736
|
+
this._done = false;
|
|
2737
|
+
this._totalFetched = 0;
|
|
2738
|
+
}
|
|
2739
|
+
/** Whether all pages have been consumed. */
|
|
2740
|
+
get done() {
|
|
2741
|
+
return this._done;
|
|
2742
|
+
}
|
|
2743
|
+
/** Total number of items fetched so far (across all pages). */
|
|
2744
|
+
get totalFetched() {
|
|
2745
|
+
return this._totalFetched;
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* Fetch the next page of results.
|
|
2749
|
+
* Returns an empty array when there are no more pages.
|
|
2750
|
+
*
|
|
2751
|
+
* @example
|
|
2752
|
+
* const page1 = await paginator.fetchPage()
|
|
2753
|
+
* const page2 = await paginator.fetchPage()
|
|
2754
|
+
*/
|
|
2755
|
+
async fetchPage() {
|
|
2756
|
+
if (this._done) return [];
|
|
2757
|
+
const { items, cursor } = await this.fetchFn(this._cursor);
|
|
2758
|
+
this._totalFetched += items.length;
|
|
2759
|
+
this._cursor = cursor;
|
|
2760
|
+
if (!cursor || items.length === 0) this._done = true;
|
|
2761
|
+
return items;
|
|
2762
|
+
}
|
|
2763
|
+
/**
|
|
2764
|
+
* Fetch **all** remaining pages and return a flat array.
|
|
2765
|
+
*
|
|
2766
|
+
* ⚠️ Use with caution on very large collections.
|
|
2767
|
+
*
|
|
2768
|
+
* @example
|
|
2769
|
+
* const allMembers = await paginator.fetchAll()
|
|
2770
|
+
*/
|
|
2771
|
+
async fetchAll() {
|
|
2772
|
+
const all = [];
|
|
2773
|
+
while (!this._done) {
|
|
2774
|
+
const page = await this.fetchPage();
|
|
2775
|
+
all.push(...page);
|
|
2776
|
+
}
|
|
2777
|
+
return all;
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Fetch up to `n` items total (across however many pages are needed).
|
|
2781
|
+
*
|
|
2782
|
+
* @example
|
|
2783
|
+
* const first100 = await paginator.fetchN(100)
|
|
2784
|
+
*/
|
|
2785
|
+
async fetchN(n) {
|
|
2786
|
+
const result = [];
|
|
2787
|
+
while (!this._done && result.length < n) {
|
|
2788
|
+
const page = await this.fetchPage();
|
|
2789
|
+
result.push(...page.slice(0, n - result.length));
|
|
2790
|
+
}
|
|
2791
|
+
return result;
|
|
2792
|
+
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Async-iterate over every item, one page at a time.
|
|
2795
|
+
*
|
|
2796
|
+
* @example
|
|
2797
|
+
* for await (const msg of paginator) {
|
|
2798
|
+
* process(msg)
|
|
2799
|
+
* }
|
|
2800
|
+
*/
|
|
2801
|
+
async *[Symbol.asyncIterator]() {
|
|
2802
|
+
while (!this._done) {
|
|
2803
|
+
const page = await this.fetchPage();
|
|
2804
|
+
for (const item of page) yield item;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
/**
|
|
2808
|
+
* Reset the paginator so you can iterate from the beginning again.
|
|
2809
|
+
*
|
|
2810
|
+
* @example
|
|
2811
|
+
* await paginator.fetchAll()
|
|
2812
|
+
* paginator.reset()
|
|
2813
|
+
* const againFirst = await paginator.fetchPage()
|
|
2814
|
+
*/
|
|
2815
|
+
reset() {
|
|
2816
|
+
this._cursor = null;
|
|
2817
|
+
this._done = false;
|
|
2818
|
+
this._totalFetched = 0;
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2821
|
+
|
|
2822
|
+
// src/utils/PermissionsBitfield.ts
|
|
2823
|
+
var Permissions = {
|
|
2824
|
+
/** Read messages in channels. */
|
|
2825
|
+
MESSAGES_READ: "messages.read",
|
|
2826
|
+
/** Send messages in channels. */
|
|
2827
|
+
MESSAGES_WRITE: "messages.write",
|
|
2828
|
+
/** Edit / delete any message (not just the bot's own). */
|
|
2829
|
+
MESSAGES_MANAGE: "messages.manage",
|
|
2830
|
+
/** Create, edit and delete channels. */
|
|
2831
|
+
CHANNELS_MANAGE: "channels.manage",
|
|
2832
|
+
/** Kick members from the server. */
|
|
2833
|
+
MEMBERS_KICK: "members.kick",
|
|
2834
|
+
/** Ban and unban members from the server. */
|
|
2835
|
+
MEMBERS_BAN: "members.ban",
|
|
2836
|
+
/** Assign and remove roles on members. */
|
|
2837
|
+
MEMBERS_ROLES: "members.roles",
|
|
2838
|
+
/** Edit server settings. */
|
|
2839
|
+
SERVERS_MANAGE: "servers.manage"
|
|
2840
|
+
};
|
|
2841
|
+
var _PermissionsBitfield = class _PermissionsBitfield {
|
|
2842
|
+
constructor(perms = {}) {
|
|
2843
|
+
this._perms = { ...perms };
|
|
2844
|
+
}
|
|
2845
|
+
// ─── Checking ──────────────────────────────────────────────────────────────
|
|
2846
|
+
/**
|
|
2847
|
+
* Returns `true` if the given permission is explicitly granted.
|
|
2848
|
+
*
|
|
2849
|
+
* @example
|
|
2850
|
+
* if (!perms.has(Permissions.MESSAGES_WRITE)) {
|
|
2851
|
+
* throw new Error('Bot cannot write messages here.')
|
|
2852
|
+
* }
|
|
2853
|
+
*/
|
|
2854
|
+
has(permission) {
|
|
2855
|
+
return this._perms[permission] === true;
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Returns `true` if **all** listed permissions are granted.
|
|
2859
|
+
*
|
|
2860
|
+
* @example
|
|
2861
|
+
* perms.hasAll('messages.read', 'messages.write') // true
|
|
2862
|
+
*/
|
|
2863
|
+
hasAll(...permissions) {
|
|
2864
|
+
return permissions.every((p) => this.has(p));
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* Returns `true` if **at least one** of the listed permissions is granted.
|
|
2868
|
+
*
|
|
2869
|
+
* @example
|
|
2870
|
+
* perms.hasAny('channels.manage', 'servers.manage') // true if either is set
|
|
2871
|
+
*/
|
|
2872
|
+
hasAny(...permissions) {
|
|
2873
|
+
return permissions.some((p) => this.has(p));
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Returns the list of permissions that are **not** granted.
|
|
2877
|
+
*
|
|
2878
|
+
* @example
|
|
2879
|
+
* const missing = perms.missing('messages.write', 'members.kick')
|
|
2880
|
+
* if (missing.length) throw new Error(`Missing: ${missing.join(', ')}`)
|
|
2881
|
+
*/
|
|
2882
|
+
missing(...permissions) {
|
|
2883
|
+
return permissions.filter((p) => !this.has(p));
|
|
2884
|
+
}
|
|
2885
|
+
// ─── Building ──────────────────────────────────────────────────────────────
|
|
2886
|
+
/**
|
|
2887
|
+
* Return a **new** `PermissionsBitfield` with the given permission granted.
|
|
2888
|
+
*
|
|
2889
|
+
* @example
|
|
2890
|
+
* const updated = perms.grant('channels.manage')
|
|
2891
|
+
*/
|
|
2892
|
+
grant(...permissions) {
|
|
2893
|
+
const next = { ...this._perms };
|
|
2894
|
+
for (const p of permissions) next[p] = true;
|
|
2895
|
+
return new _PermissionsBitfield(next);
|
|
2896
|
+
}
|
|
2897
|
+
/**
|
|
2898
|
+
* Return a **new** `PermissionsBitfield` with the given permission denied.
|
|
2899
|
+
*
|
|
2900
|
+
* @example
|
|
2901
|
+
* const restricted = perms.deny('members.kick')
|
|
2902
|
+
*/
|
|
2903
|
+
deny(...permissions) {
|
|
2904
|
+
const next = { ...this._perms };
|
|
2905
|
+
for (const p of permissions) next[p] = false;
|
|
2906
|
+
return new _PermissionsBitfield(next);
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Merge another record or `PermissionsBitfield` on top of this one.
|
|
2910
|
+
* The incoming values **override** any existing ones.
|
|
2911
|
+
*
|
|
2912
|
+
* @example
|
|
2913
|
+
* const merged = serverPerms.merge(channelOverrides)
|
|
2914
|
+
*/
|
|
2915
|
+
merge(other) {
|
|
2916
|
+
const otherRecord = other instanceof _PermissionsBitfield ? other.toRecord() : other;
|
|
2917
|
+
return new _PermissionsBitfield({ ...this._perms, ...otherRecord });
|
|
2918
|
+
}
|
|
2919
|
+
// ─── Inspection ────────────────────────────────────────────────────────────
|
|
2920
|
+
/**
|
|
2921
|
+
* Returns an array of the permission keys that are **currently granted**.
|
|
2922
|
+
*
|
|
2923
|
+
* @example
|
|
2924
|
+
* perms.toArray() // ['messages.read', 'messages.write']
|
|
2925
|
+
*/
|
|
2926
|
+
toArray() {
|
|
2927
|
+
return Object.entries(this._perms).filter(([, v]) => v).map(([k]) => k);
|
|
2928
|
+
}
|
|
2929
|
+
/**
|
|
2930
|
+
* Returns the raw `Record<string, boolean>` map.
|
|
2931
|
+
*/
|
|
2932
|
+
toRecord() {
|
|
2933
|
+
return { ...this._perms };
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Pretty-print the granted permissions.
|
|
2937
|
+
*
|
|
2938
|
+
* @example
|
|
2939
|
+
* console.log(String(perms)) // 'PermissionsBitfield[messages.read, messages.write]'
|
|
2940
|
+
*/
|
|
2941
|
+
toString() {
|
|
2942
|
+
const granted = this.toArray();
|
|
2943
|
+
return granted.length > 0 ? `PermissionsBitfield[${granted.join(", ")}]` : "PermissionsBitfield[none]";
|
|
2944
|
+
}
|
|
2945
|
+
// ─── Static helpers ─────────────────────────────────────────────────────────
|
|
2946
|
+
/** Create a `PermissionsBitfield` from an existing record. */
|
|
2947
|
+
static from(perms) {
|
|
2948
|
+
return new _PermissionsBitfield(perms);
|
|
2949
|
+
}
|
|
2950
|
+
};
|
|
2951
|
+
/** A `PermissionsBitfield` with all known permissions granted. */
|
|
2952
|
+
_PermissionsBitfield.ALL = new _PermissionsBitfield({
|
|
2953
|
+
[Permissions.MESSAGES_READ]: true,
|
|
2954
|
+
[Permissions.MESSAGES_WRITE]: true,
|
|
2955
|
+
[Permissions.MESSAGES_MANAGE]: true,
|
|
2956
|
+
[Permissions.CHANNELS_MANAGE]: true,
|
|
2957
|
+
[Permissions.MEMBERS_KICK]: true,
|
|
2958
|
+
[Permissions.MEMBERS_BAN]: true,
|
|
2959
|
+
[Permissions.MEMBERS_ROLES]: true,
|
|
2960
|
+
[Permissions.SERVERS_MANAGE]: true
|
|
2961
|
+
});
|
|
2962
|
+
/** A `PermissionsBitfield` with no permissions granted. */
|
|
2963
|
+
_PermissionsBitfield.NONE = new _PermissionsBitfield({});
|
|
2964
|
+
var PermissionsBitfield = _PermissionsBitfield;
|
|
2965
|
+
|
|
2966
|
+
// src/utils/time.ts
|
|
2967
|
+
function sleep(ms) {
|
|
2968
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2969
|
+
}
|
|
2970
|
+
function withTimeout(promise, ms, message) {
|
|
2971
|
+
return new Promise((resolve, reject) => {
|
|
2972
|
+
const id = setTimeout(() => {
|
|
2973
|
+
reject(new Error(message ?? `[nova-bot-sdk] Operation timed out after ${ms}ms`));
|
|
2974
|
+
}, ms);
|
|
2975
|
+
promise.then(
|
|
2976
|
+
(v) => {
|
|
2977
|
+
clearTimeout(id);
|
|
2978
|
+
resolve(v);
|
|
2979
|
+
},
|
|
2980
|
+
(e) => {
|
|
2981
|
+
clearTimeout(id);
|
|
2982
|
+
reject(e);
|
|
2983
|
+
}
|
|
2984
|
+
);
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
var SECOND = 1e3;
|
|
2988
|
+
var MINUTE = 60 * SECOND;
|
|
2989
|
+
var HOUR = 60 * MINUTE;
|
|
2990
|
+
var DAY = 24 * HOUR;
|
|
2991
|
+
var WEEK = 7 * DAY;
|
|
2992
|
+
function formatDuration(ms) {
|
|
2993
|
+
if (ms < SECOND) return "0s";
|
|
2994
|
+
const weeks = Math.floor(ms / WEEK);
|
|
2995
|
+
const days = Math.floor(ms % WEEK / DAY);
|
|
2996
|
+
const hours = Math.floor(ms % DAY / HOUR);
|
|
2997
|
+
const minutes = Math.floor(ms % HOUR / MINUTE);
|
|
2998
|
+
const seconds = Math.floor(ms % MINUTE / SECOND);
|
|
2999
|
+
const parts = [];
|
|
3000
|
+
if (weeks) parts.push(`${weeks}w`);
|
|
3001
|
+
if (days) parts.push(`${days}d`);
|
|
3002
|
+
if (hours) parts.push(`${hours}h`);
|
|
3003
|
+
if (minutes) parts.push(`${minutes}m`);
|
|
3004
|
+
if (seconds) parts.push(`${seconds}s`);
|
|
3005
|
+
return parts.join(" ");
|
|
3006
|
+
}
|
|
3007
|
+
function formatRelative(timestamp) {
|
|
3008
|
+
const ts = timestamp instanceof Date ? timestamp.getTime() : typeof timestamp === "string" ? new Date(timestamp).getTime() : timestamp;
|
|
3009
|
+
const diff = Date.now() - ts;
|
|
3010
|
+
const abs = Math.abs(diff);
|
|
3011
|
+
const past = diff > 0;
|
|
3012
|
+
const fmt = (n, unit) => past ? `${n} ${unit}${n !== 1 ? "s" : ""} ago` : `in ${n} ${unit}${n !== 1 ? "s" : ""}`;
|
|
3013
|
+
if (abs < 5e3) return "just now";
|
|
3014
|
+
if (abs < MINUTE) return fmt(Math.round(abs / SECOND), "second");
|
|
3015
|
+
if (abs < 2 * MINUTE) return past ? "a minute ago" : "in a minute";
|
|
3016
|
+
if (abs < HOUR) return fmt(Math.round(abs / MINUTE), "minute");
|
|
3017
|
+
if (abs < 2 * HOUR) return past ? "an hour ago" : "in an hour";
|
|
3018
|
+
if (abs < DAY) return fmt(Math.round(abs / HOUR), "hour");
|
|
3019
|
+
if (abs < 2 * DAY) return past ? "yesterday" : "tomorrow";
|
|
3020
|
+
if (abs < WEEK) return fmt(Math.round(abs / DAY), "day");
|
|
3021
|
+
if (abs < 4 * WEEK) return fmt(Math.round(abs / WEEK), "week");
|
|
3022
|
+
return new Date(ts).toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
|
|
3023
|
+
}
|
|
3024
|
+
function parseTimestamp(value) {
|
|
3025
|
+
if (value == null) return null;
|
|
3026
|
+
if (value instanceof Date) return isNaN(value.getTime()) ? null : value;
|
|
3027
|
+
const d = new Date(value);
|
|
3028
|
+
return isNaN(d.getTime()) ? null : d;
|
|
3029
|
+
}
|
|
3030
|
+
function countdown(target) {
|
|
3031
|
+
const targetMs = target instanceof Date ? target.getTime() : typeof target === "string" ? new Date(target).getTime() : target;
|
|
3032
|
+
const total = Math.max(0, targetMs - Date.now());
|
|
3033
|
+
return {
|
|
3034
|
+
total,
|
|
3035
|
+
weeks: Math.floor(total / WEEK),
|
|
3036
|
+
days: Math.floor(total % WEEK / DAY),
|
|
3037
|
+
hours: Math.floor(total % DAY / HOUR),
|
|
3038
|
+
minutes: Math.floor(total % HOUR / MINUTE),
|
|
3039
|
+
seconds: Math.floor(total % MINUTE / SECOND)
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
2353
3042
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2354
3043
|
0 && (module.exports = {
|
|
2355
3044
|
ActionRowBuilder,
|
|
@@ -2368,15 +3057,26 @@ var Logger = class {
|
|
|
2368
3057
|
MessageBuilder,
|
|
2369
3058
|
MessagesAPI,
|
|
2370
3059
|
ModalBuilder,
|
|
3060
|
+
NovaChannel,
|
|
2371
3061
|
NovaClient,
|
|
2372
3062
|
NovaInteraction,
|
|
3063
|
+
NovaMember,
|
|
2373
3064
|
NovaMessage,
|
|
3065
|
+
Paginator,
|
|
3066
|
+
Permissions,
|
|
2374
3067
|
PermissionsAPI,
|
|
3068
|
+
PermissionsBitfield,
|
|
2375
3069
|
PollBuilder,
|
|
2376
3070
|
ReactionsAPI,
|
|
2377
3071
|
SelectMenuBuilder,
|
|
2378
3072
|
ServersAPI,
|
|
2379
3073
|
SlashCommandBuilder,
|
|
2380
3074
|
SlashCommandOptionBuilder,
|
|
2381
|
-
TextInputBuilder
|
|
3075
|
+
TextInputBuilder,
|
|
3076
|
+
countdown,
|
|
3077
|
+
formatDuration,
|
|
3078
|
+
formatRelative,
|
|
3079
|
+
parseTimestamp,
|
|
3080
|
+
sleep,
|
|
3081
|
+
withTimeout
|
|
2382
3082
|
});
|