@furlow/discord 0.1.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/client/index.d.ts +79 -0
- package/dist/client/index.js +236 -0
- package/dist/client/index.js.map +1 -0
- package/dist/gateway/index.d.ts +62 -0
- package/dist/gateway/index.js +128 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1089 -0
- package/dist/index.js.map +1 -0
- package/dist/interactions/index.d.ts +89 -0
- package/dist/interactions/index.js +266 -0
- package/dist/interactions/index.js.map +1 -0
- package/dist/video/index.d.ts +94 -0
- package/dist/video/index.js +209 -0
- package/dist/video/index.js.map +1 -0
- package/dist/voice/index.d.ts +136 -0
- package/dist/voice/index.js +258 -0
- package/dist/voice/index.js.map +1 -0
- package/package.json +74 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
// src/client/index.ts
|
|
2
|
+
import {
|
|
3
|
+
Client,
|
|
4
|
+
GatewayIntentBits,
|
|
5
|
+
Partials
|
|
6
|
+
} from "discord.js";
|
|
7
|
+
var INTENT_MAP = {
|
|
8
|
+
guilds: GatewayIntentBits.Guilds,
|
|
9
|
+
guild_members: GatewayIntentBits.GuildMembers,
|
|
10
|
+
guild_moderation: GatewayIntentBits.GuildModeration,
|
|
11
|
+
guild_emojis_and_stickers: GatewayIntentBits.GuildEmojisAndStickers,
|
|
12
|
+
guild_integrations: GatewayIntentBits.GuildIntegrations,
|
|
13
|
+
guild_webhooks: GatewayIntentBits.GuildWebhooks,
|
|
14
|
+
guild_invites: GatewayIntentBits.GuildInvites,
|
|
15
|
+
guild_voice_states: GatewayIntentBits.GuildVoiceStates,
|
|
16
|
+
guild_presences: GatewayIntentBits.GuildPresences,
|
|
17
|
+
guild_messages: GatewayIntentBits.GuildMessages,
|
|
18
|
+
guild_message_reactions: GatewayIntentBits.GuildMessageReactions,
|
|
19
|
+
guild_message_typing: GatewayIntentBits.GuildMessageTyping,
|
|
20
|
+
direct_messages: GatewayIntentBits.DirectMessages,
|
|
21
|
+
direct_message_reactions: GatewayIntentBits.DirectMessageReactions,
|
|
22
|
+
direct_message_typing: GatewayIntentBits.DirectMessageTyping,
|
|
23
|
+
message_content: GatewayIntentBits.MessageContent,
|
|
24
|
+
guild_scheduled_events: GatewayIntentBits.GuildScheduledEvents,
|
|
25
|
+
auto_moderation_configuration: GatewayIntentBits.AutoModerationConfiguration,
|
|
26
|
+
auto_moderation_execution: GatewayIntentBits.AutoModerationExecution
|
|
27
|
+
};
|
|
28
|
+
var FurlowClient = class {
|
|
29
|
+
client;
|
|
30
|
+
token;
|
|
31
|
+
spec;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.token = options.token;
|
|
34
|
+
this.spec = options.spec;
|
|
35
|
+
const intents = this.resolveIntents(options.spec.intents);
|
|
36
|
+
const clientOptions = {
|
|
37
|
+
intents,
|
|
38
|
+
partials: [
|
|
39
|
+
Partials.Message,
|
|
40
|
+
Partials.Channel,
|
|
41
|
+
Partials.Reaction,
|
|
42
|
+
Partials.User,
|
|
43
|
+
Partials.GuildMember
|
|
44
|
+
]
|
|
45
|
+
};
|
|
46
|
+
this.client = new Client(clientOptions);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve intents from spec
|
|
50
|
+
*/
|
|
51
|
+
resolveIntents(config) {
|
|
52
|
+
if (!config) {
|
|
53
|
+
return [
|
|
54
|
+
GatewayIntentBits.Guilds,
|
|
55
|
+
GatewayIntentBits.GuildMessages,
|
|
56
|
+
GatewayIntentBits.GuildMembers,
|
|
57
|
+
GatewayIntentBits.MessageContent
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
if (config.auto) {
|
|
61
|
+
return this.autoDetectIntents();
|
|
62
|
+
}
|
|
63
|
+
if (config.explicit) {
|
|
64
|
+
return config.explicit.map((intent) => INTENT_MAP[intent]).filter((i) => i !== void 0);
|
|
65
|
+
}
|
|
66
|
+
return [GatewayIntentBits.Guilds];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Auto-detect required intents from spec
|
|
70
|
+
*/
|
|
71
|
+
autoDetectIntents() {
|
|
72
|
+
const intents = /* @__PURE__ */ new Set();
|
|
73
|
+
intents.add(GatewayIntentBits.Guilds);
|
|
74
|
+
if (this.spec.events) {
|
|
75
|
+
for (const handler of this.spec.events) {
|
|
76
|
+
switch (handler.event) {
|
|
77
|
+
case "message_create":
|
|
78
|
+
case "message":
|
|
79
|
+
case "message_update":
|
|
80
|
+
case "message_delete":
|
|
81
|
+
intents.add(GatewayIntentBits.GuildMessages);
|
|
82
|
+
intents.add(GatewayIntentBits.MessageContent);
|
|
83
|
+
break;
|
|
84
|
+
case "guild_member_add":
|
|
85
|
+
case "guild_member_remove":
|
|
86
|
+
case "guild_member_update":
|
|
87
|
+
case "member_join":
|
|
88
|
+
case "member_leave":
|
|
89
|
+
intents.add(GatewayIntentBits.GuildMembers);
|
|
90
|
+
break;
|
|
91
|
+
case "voice_state_update":
|
|
92
|
+
case "voice_join":
|
|
93
|
+
case "voice_leave":
|
|
94
|
+
intents.add(GatewayIntentBits.GuildVoiceStates);
|
|
95
|
+
break;
|
|
96
|
+
case "message_reaction_add":
|
|
97
|
+
case "message_reaction_remove":
|
|
98
|
+
intents.add(GatewayIntentBits.GuildMessageReactions);
|
|
99
|
+
break;
|
|
100
|
+
case "presence_update":
|
|
101
|
+
intents.add(GatewayIntentBits.GuildPresences);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (this.spec.commands?.length) {
|
|
107
|
+
intents.add(GatewayIntentBits.GuildMessages);
|
|
108
|
+
}
|
|
109
|
+
if (this.spec.voice) {
|
|
110
|
+
intents.add(GatewayIntentBits.GuildVoiceStates);
|
|
111
|
+
}
|
|
112
|
+
return [...intents];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Start the client
|
|
116
|
+
*/
|
|
117
|
+
async start() {
|
|
118
|
+
await this.client.login(this.token);
|
|
119
|
+
if (!this.client.isReady()) {
|
|
120
|
+
await new Promise((resolve) => {
|
|
121
|
+
this.client.once("ready", () => resolve());
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
await this.applyIdentity();
|
|
125
|
+
await this.applyPresence();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Stop the client
|
|
129
|
+
*/
|
|
130
|
+
async stop() {
|
|
131
|
+
await this.client.destroy();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Apply bot identity
|
|
135
|
+
*/
|
|
136
|
+
async applyIdentity() {
|
|
137
|
+
if (!this.spec.identity) return;
|
|
138
|
+
const identity = this.spec.identity;
|
|
139
|
+
if (identity.name && this.client.user?.username !== identity.name) {
|
|
140
|
+
try {
|
|
141
|
+
await this.client.user?.setUsername(identity.name);
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (identity.avatar) {
|
|
146
|
+
try {
|
|
147
|
+
await this.client.user?.setAvatar(identity.avatar);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Apply presence
|
|
154
|
+
*/
|
|
155
|
+
async applyPresence() {
|
|
156
|
+
if (!this.spec.presence) return;
|
|
157
|
+
const presence = this.spec.presence;
|
|
158
|
+
this.client.user?.setPresence({
|
|
159
|
+
status: presence.status ?? "online",
|
|
160
|
+
activities: presence.activity ? [
|
|
161
|
+
{
|
|
162
|
+
type: this.getActivityType(presence.activity.type),
|
|
163
|
+
name: presence.activity.text,
|
|
164
|
+
url: presence.activity.url,
|
|
165
|
+
state: presence.activity.state
|
|
166
|
+
}
|
|
167
|
+
] : void 0
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get Discord.js activity type
|
|
172
|
+
*/
|
|
173
|
+
getActivityType(type) {
|
|
174
|
+
const types = {
|
|
175
|
+
playing: 0,
|
|
176
|
+
streaming: 1,
|
|
177
|
+
listening: 2,
|
|
178
|
+
watching: 3,
|
|
179
|
+
custom: 4,
|
|
180
|
+
competing: 5
|
|
181
|
+
};
|
|
182
|
+
return types[type] ?? 0;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Register event listener
|
|
186
|
+
*/
|
|
187
|
+
on(event, listener) {
|
|
188
|
+
this.client.on(event, listener);
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Register one-time event listener
|
|
193
|
+
*/
|
|
194
|
+
once(event, listener) {
|
|
195
|
+
this.client.once(event, listener);
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get the underlying Discord.js client
|
|
200
|
+
*/
|
|
201
|
+
getClient() {
|
|
202
|
+
return this.client;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get the spec
|
|
206
|
+
*/
|
|
207
|
+
getSpec() {
|
|
208
|
+
return this.spec;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Check if client is ready
|
|
212
|
+
*/
|
|
213
|
+
isReady() {
|
|
214
|
+
return this.client.isReady();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get guild count
|
|
218
|
+
*/
|
|
219
|
+
get guildCount() {
|
|
220
|
+
return this.client.guilds.cache.size;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get user count (approximate)
|
|
224
|
+
*/
|
|
225
|
+
get userCount() {
|
|
226
|
+
return this.client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
function createClient(options) {
|
|
230
|
+
return new FurlowClient(options);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/gateway/index.ts
|
|
234
|
+
var GatewayManager = class {
|
|
235
|
+
client;
|
|
236
|
+
config;
|
|
237
|
+
reconnectAttempts = 0;
|
|
238
|
+
maxReconnectAttempts;
|
|
239
|
+
baseDelay;
|
|
240
|
+
maxDelay;
|
|
241
|
+
backoffStrategy;
|
|
242
|
+
constructor(client, options = {}) {
|
|
243
|
+
this.client = client;
|
|
244
|
+
this.config = options.config ?? {};
|
|
245
|
+
const reconnect = this.config.reconnect ?? {};
|
|
246
|
+
this.maxReconnectAttempts = reconnect.max_retries ?? 10;
|
|
247
|
+
const baseDelay = reconnect.base_delay ?? "1s";
|
|
248
|
+
const maxDelay = reconnect.max_delay ?? "60s";
|
|
249
|
+
this.baseDelay = typeof baseDelay === "number" ? baseDelay : this.parseDuration(baseDelay);
|
|
250
|
+
this.maxDelay = typeof maxDelay === "number" ? maxDelay : this.parseDuration(maxDelay);
|
|
251
|
+
this.backoffStrategy = reconnect.backoff ?? "exponential";
|
|
252
|
+
this.setupListeners(options);
|
|
253
|
+
}
|
|
254
|
+
setupListeners(options) {
|
|
255
|
+
this.client.on("shardReady", (shardId) => {
|
|
256
|
+
console.log(`Shard ${shardId} ready`);
|
|
257
|
+
this.reconnectAttempts = 0;
|
|
258
|
+
});
|
|
259
|
+
this.client.on("shardDisconnect", (event, shardId) => {
|
|
260
|
+
console.log(`Shard ${shardId} disconnected: ${event.code}`);
|
|
261
|
+
options.onDisconnect?.();
|
|
262
|
+
});
|
|
263
|
+
this.client.on("shardReconnecting", (shardId) => {
|
|
264
|
+
console.log(`Shard ${shardId} reconnecting...`);
|
|
265
|
+
this.reconnectAttempts++;
|
|
266
|
+
options.onReconnect?.();
|
|
267
|
+
});
|
|
268
|
+
this.client.on("shardError", (error, shardId) => {
|
|
269
|
+
console.error(`Shard ${shardId} error:`, error);
|
|
270
|
+
options.onError?.(error);
|
|
271
|
+
});
|
|
272
|
+
this.client.on("shardResume", (shardId, replayedEvents) => {
|
|
273
|
+
console.log(`Shard ${shardId} resumed, replayed ${replayedEvents} events`);
|
|
274
|
+
this.reconnectAttempts = 0;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get the delay for the next reconnection attempt
|
|
279
|
+
*/
|
|
280
|
+
getReconnectDelay() {
|
|
281
|
+
switch (this.backoffStrategy) {
|
|
282
|
+
case "exponential":
|
|
283
|
+
return Math.min(
|
|
284
|
+
this.baseDelay * Math.pow(2, this.reconnectAttempts),
|
|
285
|
+
this.maxDelay
|
|
286
|
+
);
|
|
287
|
+
case "linear":
|
|
288
|
+
return Math.min(
|
|
289
|
+
this.baseDelay * (this.reconnectAttempts + 1),
|
|
290
|
+
this.maxDelay
|
|
291
|
+
);
|
|
292
|
+
case "fixed":
|
|
293
|
+
default:
|
|
294
|
+
return this.baseDelay;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Check if we should attempt reconnection
|
|
299
|
+
*/
|
|
300
|
+
shouldReconnect() {
|
|
301
|
+
return this.reconnectAttempts < this.maxReconnectAttempts;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Get current reconnect attempt count
|
|
305
|
+
*/
|
|
306
|
+
getReconnectAttempts() {
|
|
307
|
+
return this.reconnectAttempts;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Reset reconnect counter
|
|
311
|
+
*/
|
|
312
|
+
resetReconnectAttempts() {
|
|
313
|
+
this.reconnectAttempts = 0;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Get gateway latency (ping)
|
|
317
|
+
*/
|
|
318
|
+
getPing() {
|
|
319
|
+
return this.client.ws.ping;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get shard info
|
|
323
|
+
*/
|
|
324
|
+
getShardInfo() {
|
|
325
|
+
return [...this.client.ws.shards.values()].map((shard) => ({
|
|
326
|
+
id: shard.id,
|
|
327
|
+
status: shard.status.toString(),
|
|
328
|
+
ping: shard.ping
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Parse duration string to milliseconds
|
|
333
|
+
*/
|
|
334
|
+
parseDuration(duration) {
|
|
335
|
+
const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
|
|
336
|
+
if (!match) return 1e3;
|
|
337
|
+
const value = parseInt(match[1], 10);
|
|
338
|
+
const unit = match[2] ?? "s";
|
|
339
|
+
switch (unit) {
|
|
340
|
+
case "ms":
|
|
341
|
+
return value;
|
|
342
|
+
case "s":
|
|
343
|
+
return value * 1e3;
|
|
344
|
+
case "m":
|
|
345
|
+
return value * 60 * 1e3;
|
|
346
|
+
case "h":
|
|
347
|
+
return value * 60 * 60 * 1e3;
|
|
348
|
+
default:
|
|
349
|
+
return value * 1e3;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
function createGatewayManager(client, options) {
|
|
354
|
+
return new GatewayManager(client, options);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/interactions/index.ts
|
|
358
|
+
import {
|
|
359
|
+
REST,
|
|
360
|
+
Routes,
|
|
361
|
+
SlashCommandBuilder,
|
|
362
|
+
ContextMenuCommandBuilder,
|
|
363
|
+
ApplicationCommandType
|
|
364
|
+
} from "discord.js";
|
|
365
|
+
var InteractionHandler = class {
|
|
366
|
+
client;
|
|
367
|
+
rest;
|
|
368
|
+
clientId;
|
|
369
|
+
guildId;
|
|
370
|
+
commandHandlers = /* @__PURE__ */ new Map();
|
|
371
|
+
buttonHandlers = /* @__PURE__ */ new Map();
|
|
372
|
+
selectHandlers = /* @__PURE__ */ new Map();
|
|
373
|
+
modalHandlers = /* @__PURE__ */ new Map();
|
|
374
|
+
userContextHandlers = /* @__PURE__ */ new Map();
|
|
375
|
+
messageContextHandlers = /* @__PURE__ */ new Map();
|
|
376
|
+
constructor(options) {
|
|
377
|
+
this.client = options.client;
|
|
378
|
+
this.clientId = options.clientId;
|
|
379
|
+
this.guildId = options.guildId;
|
|
380
|
+
this.rest = new REST({ version: "10" }).setToken(options.token);
|
|
381
|
+
this.setupListener();
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Set up the interaction listener
|
|
385
|
+
*/
|
|
386
|
+
setupListener() {
|
|
387
|
+
this.client.on("interactionCreate", async (interaction) => {
|
|
388
|
+
try {
|
|
389
|
+
await this.handleInteraction(interaction);
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error("Interaction error:", error);
|
|
392
|
+
if (interaction.isRepliable() && !interaction.replied && !interaction.deferred) {
|
|
393
|
+
await interaction.reply({
|
|
394
|
+
content: "An error occurred while processing this interaction.",
|
|
395
|
+
ephemeral: true
|
|
396
|
+
}).catch(() => {
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Handle an interaction
|
|
404
|
+
*/
|
|
405
|
+
async handleInteraction(interaction) {
|
|
406
|
+
if (interaction.isChatInputCommand()) {
|
|
407
|
+
const handler = this.commandHandlers.get(interaction.commandName);
|
|
408
|
+
if (handler) {
|
|
409
|
+
await handler(interaction);
|
|
410
|
+
}
|
|
411
|
+
} else if (interaction.isButton()) {
|
|
412
|
+
const handler = this.buttonHandlers.get(interaction.customId) ?? this.findPrefixHandler(this.buttonHandlers, interaction.customId);
|
|
413
|
+
if (handler) {
|
|
414
|
+
await handler(interaction);
|
|
415
|
+
}
|
|
416
|
+
} else if (interaction.isStringSelectMenu()) {
|
|
417
|
+
const handler = this.selectHandlers.get(interaction.customId) ?? this.findPrefixHandler(this.selectHandlers, interaction.customId);
|
|
418
|
+
if (handler) {
|
|
419
|
+
await handler(interaction);
|
|
420
|
+
}
|
|
421
|
+
} else if (interaction.isModalSubmit()) {
|
|
422
|
+
const handler = this.modalHandlers.get(interaction.customId) ?? this.findPrefixHandler(this.modalHandlers, interaction.customId);
|
|
423
|
+
if (handler) {
|
|
424
|
+
await handler(interaction);
|
|
425
|
+
}
|
|
426
|
+
} else if (interaction.isUserContextMenuCommand()) {
|
|
427
|
+
const handler = this.userContextHandlers.get(interaction.commandName);
|
|
428
|
+
if (handler) {
|
|
429
|
+
await handler(interaction);
|
|
430
|
+
}
|
|
431
|
+
} else if (interaction.isMessageContextMenuCommand()) {
|
|
432
|
+
const handler = this.messageContextHandlers.get(interaction.commandName);
|
|
433
|
+
if (handler) {
|
|
434
|
+
await handler(interaction);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Find a handler by prefix (for dynamic IDs like "button_123")
|
|
440
|
+
*/
|
|
441
|
+
findPrefixHandler(handlers, customId) {
|
|
442
|
+
for (const [key, handler] of handlers) {
|
|
443
|
+
if (key.endsWith("*") && customId.startsWith(key.slice(0, -1))) {
|
|
444
|
+
return handler;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return void 0;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Register a command handler
|
|
451
|
+
*/
|
|
452
|
+
onCommand(name, handler) {
|
|
453
|
+
this.commandHandlers.set(name, handler);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Register a button handler
|
|
457
|
+
*/
|
|
458
|
+
onButton(customId, handler) {
|
|
459
|
+
this.buttonHandlers.set(customId, handler);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Register a select menu handler
|
|
463
|
+
*/
|
|
464
|
+
onSelect(customId, handler) {
|
|
465
|
+
this.selectHandlers.set(customId, handler);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Register a modal handler
|
|
469
|
+
*/
|
|
470
|
+
onModal(customId, handler) {
|
|
471
|
+
this.modalHandlers.set(customId, handler);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Register a user context menu handler
|
|
475
|
+
*/
|
|
476
|
+
onUserContext(name, handler) {
|
|
477
|
+
this.userContextHandlers.set(name, handler);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Register a message context menu handler
|
|
481
|
+
*/
|
|
482
|
+
onMessageContext(name, handler) {
|
|
483
|
+
this.messageContextHandlers.set(name, handler);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Register slash commands with Discord
|
|
487
|
+
*/
|
|
488
|
+
async registerCommands(commands, contextMenus) {
|
|
489
|
+
const slashCommands = commands.map((cmd) => this.buildSlashCommand(cmd));
|
|
490
|
+
const contextCommands = (contextMenus ?? []).map((cmd) => this.buildContextMenu(cmd));
|
|
491
|
+
const allCommands = [...slashCommands, ...contextCommands];
|
|
492
|
+
if (this.guildId) {
|
|
493
|
+
await this.rest.put(
|
|
494
|
+
Routes.applicationGuildCommands(this.clientId, this.guildId),
|
|
495
|
+
{ body: allCommands }
|
|
496
|
+
);
|
|
497
|
+
} else {
|
|
498
|
+
await this.rest.put(Routes.applicationCommands(this.clientId), {
|
|
499
|
+
body: allCommands
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Build a slash command from definition
|
|
505
|
+
*/
|
|
506
|
+
buildSlashCommand(cmd) {
|
|
507
|
+
const builder = new SlashCommandBuilder().setName(cmd.name).setDescription(cmd.description);
|
|
508
|
+
if (cmd.dm_permission !== void 0) {
|
|
509
|
+
builder.setDMPermission(cmd.dm_permission);
|
|
510
|
+
}
|
|
511
|
+
if (cmd.nsfw) {
|
|
512
|
+
builder.setNSFW(true);
|
|
513
|
+
}
|
|
514
|
+
if (cmd.options) {
|
|
515
|
+
for (const opt of cmd.options) {
|
|
516
|
+
this.addOption(builder, opt);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (cmd.subcommands) {
|
|
520
|
+
for (const sub of cmd.subcommands) {
|
|
521
|
+
builder.addSubcommand((subBuilder) => {
|
|
522
|
+
subBuilder.setName(sub.name).setDescription(sub.description);
|
|
523
|
+
if (sub.options) {
|
|
524
|
+
for (const opt of sub.options) {
|
|
525
|
+
this.addOption(subBuilder, opt);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return subBuilder;
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (cmd.subcommand_groups) {
|
|
533
|
+
for (const group of cmd.subcommand_groups) {
|
|
534
|
+
builder.addSubcommandGroup((groupBuilder) => {
|
|
535
|
+
groupBuilder.setName(group.name).setDescription(group.description);
|
|
536
|
+
for (const sub of group.subcommands) {
|
|
537
|
+
groupBuilder.addSubcommand((subBuilder) => {
|
|
538
|
+
subBuilder.setName(sub.name).setDescription(sub.description);
|
|
539
|
+
if (sub.options) {
|
|
540
|
+
for (const opt of sub.options) {
|
|
541
|
+
this.addOption(subBuilder, opt);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return subBuilder;
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return groupBuilder;
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return builder.toJSON();
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Add an option to a command builder
|
|
555
|
+
*/
|
|
556
|
+
addOption(builder, opt) {
|
|
557
|
+
const addMethod = `add${this.getOptionMethodName(opt.type)}Option`;
|
|
558
|
+
if (typeof builder[addMethod] !== "function") {
|
|
559
|
+
console.warn(`Unknown option type: ${opt.type}`);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
builder[addMethod]((optBuilder) => {
|
|
563
|
+
optBuilder.setName(opt.name).setDescription(opt.description);
|
|
564
|
+
if (opt.required) {
|
|
565
|
+
optBuilder.setRequired(true);
|
|
566
|
+
}
|
|
567
|
+
if (opt.choices && optBuilder.addChoices) {
|
|
568
|
+
optBuilder.addChoices(...opt.choices);
|
|
569
|
+
}
|
|
570
|
+
if (opt.min_value !== void 0 && optBuilder.setMinValue) {
|
|
571
|
+
optBuilder.setMinValue(opt.min_value);
|
|
572
|
+
}
|
|
573
|
+
if (opt.max_value !== void 0 && optBuilder.setMaxValue) {
|
|
574
|
+
optBuilder.setMaxValue(opt.max_value);
|
|
575
|
+
}
|
|
576
|
+
if (opt.min_length !== void 0 && optBuilder.setMinLength) {
|
|
577
|
+
optBuilder.setMinLength(opt.min_length);
|
|
578
|
+
}
|
|
579
|
+
if (opt.max_length !== void 0 && optBuilder.setMaxLength) {
|
|
580
|
+
optBuilder.setMaxLength(opt.max_length);
|
|
581
|
+
}
|
|
582
|
+
if (opt.autocomplete && optBuilder.setAutocomplete) {
|
|
583
|
+
optBuilder.setAutocomplete(true);
|
|
584
|
+
}
|
|
585
|
+
return optBuilder;
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Get the method name for an option type
|
|
590
|
+
*/
|
|
591
|
+
getOptionMethodName(type) {
|
|
592
|
+
const map = {
|
|
593
|
+
string: "String",
|
|
594
|
+
integer: "Integer",
|
|
595
|
+
number: "Number",
|
|
596
|
+
boolean: "Boolean",
|
|
597
|
+
user: "User",
|
|
598
|
+
channel: "Channel",
|
|
599
|
+
role: "Role",
|
|
600
|
+
mentionable: "Mentionable",
|
|
601
|
+
attachment: "Attachment"
|
|
602
|
+
};
|
|
603
|
+
return map[type] ?? "String";
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Build a context menu command
|
|
607
|
+
*/
|
|
608
|
+
buildContextMenu(cmd) {
|
|
609
|
+
const builder = new ContextMenuCommandBuilder().setName(cmd.name).setType(
|
|
610
|
+
cmd.type === "user" ? ApplicationCommandType.User : ApplicationCommandType.Message
|
|
611
|
+
);
|
|
612
|
+
return builder.toJSON();
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
function createInteractionHandler(options) {
|
|
616
|
+
return new InteractionHandler(options);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/voice/index.ts
|
|
620
|
+
import {
|
|
621
|
+
joinVoiceChannel,
|
|
622
|
+
createAudioPlayer,
|
|
623
|
+
createAudioResource,
|
|
624
|
+
AudioPlayerStatus,
|
|
625
|
+
VoiceConnectionStatus,
|
|
626
|
+
entersState,
|
|
627
|
+
getVoiceConnection
|
|
628
|
+
} from "@discordjs/voice";
|
|
629
|
+
var VoiceManager = class {
|
|
630
|
+
guildStates = /* @__PURE__ */ new Map();
|
|
631
|
+
config = {};
|
|
632
|
+
/**
|
|
633
|
+
* Configure the voice manager
|
|
634
|
+
*/
|
|
635
|
+
configure(config) {
|
|
636
|
+
this.config = config;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Join a voice channel
|
|
640
|
+
*/
|
|
641
|
+
async join(channel, options = {}) {
|
|
642
|
+
const guildId = channel.guild.id;
|
|
643
|
+
const existing = getVoiceConnection(guildId);
|
|
644
|
+
if (existing) {
|
|
645
|
+
existing.destroy();
|
|
646
|
+
}
|
|
647
|
+
const connection = joinVoiceChannel({
|
|
648
|
+
channelId: channel.id,
|
|
649
|
+
guildId,
|
|
650
|
+
adapterCreator: channel.guild.voiceAdapterCreator,
|
|
651
|
+
selfDeaf: options.selfDeaf ?? this.config.connection?.self_deaf ?? true,
|
|
652
|
+
selfMute: options.selfMute ?? this.config.connection?.self_mute ?? false
|
|
653
|
+
});
|
|
654
|
+
await entersState(connection, VoiceConnectionStatus.Ready, 3e4);
|
|
655
|
+
const player = createAudioPlayer();
|
|
656
|
+
player.on(AudioPlayerStatus.Idle, () => {
|
|
657
|
+
this.handleTrackEnd(guildId);
|
|
658
|
+
});
|
|
659
|
+
player.on("error", (error) => {
|
|
660
|
+
console.error(`Audio player error in ${guildId}:`, error);
|
|
661
|
+
this.handleTrackEnd(guildId);
|
|
662
|
+
});
|
|
663
|
+
connection.subscribe(player);
|
|
664
|
+
this.guildStates.set(guildId, {
|
|
665
|
+
connection,
|
|
666
|
+
player,
|
|
667
|
+
queue: [],
|
|
668
|
+
currentTrack: null,
|
|
669
|
+
volume: this.config.default_volume ?? 100,
|
|
670
|
+
loopMode: this.config.default_loop ?? "off",
|
|
671
|
+
filters: /* @__PURE__ */ new Set(),
|
|
672
|
+
paused: false
|
|
673
|
+
});
|
|
674
|
+
return connection;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Leave a voice channel
|
|
678
|
+
*/
|
|
679
|
+
leave(guildId) {
|
|
680
|
+
const state = this.guildStates.get(guildId);
|
|
681
|
+
if (!state) return false;
|
|
682
|
+
state.player.stop();
|
|
683
|
+
state.connection.destroy();
|
|
684
|
+
this.guildStates.delete(guildId);
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Play audio from a URL or file
|
|
689
|
+
*/
|
|
690
|
+
async play(guildId, source, options = {}) {
|
|
691
|
+
const state = this.guildStates.get(guildId);
|
|
692
|
+
if (!state) {
|
|
693
|
+
throw new Error("Not connected to voice in this guild");
|
|
694
|
+
}
|
|
695
|
+
const resource = createAudioResource(source, {
|
|
696
|
+
inlineVolume: true
|
|
697
|
+
});
|
|
698
|
+
const volume = options.volume ?? state.volume;
|
|
699
|
+
resource.volume?.setVolume(volume / 100);
|
|
700
|
+
state.player.play(resource);
|
|
701
|
+
state.paused = false;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Add a track to the queue
|
|
705
|
+
*/
|
|
706
|
+
addToQueue(guildId, item, position) {
|
|
707
|
+
const state = this.guildStates.get(guildId);
|
|
708
|
+
if (!state) {
|
|
709
|
+
throw new Error("Not connected to voice in this guild");
|
|
710
|
+
}
|
|
711
|
+
const maxSize = this.config.max_queue_size ?? 1e3;
|
|
712
|
+
if (state.queue.length >= maxSize) {
|
|
713
|
+
throw new Error(`Queue is full (max ${maxSize} tracks)`);
|
|
714
|
+
}
|
|
715
|
+
if (position === "next" || position === 0) {
|
|
716
|
+
state.queue.unshift(item);
|
|
717
|
+
return 0;
|
|
718
|
+
} else if (position === "last" || position === void 0) {
|
|
719
|
+
state.queue.push(item);
|
|
720
|
+
return state.queue.length - 1;
|
|
721
|
+
} else if (typeof position === "number") {
|
|
722
|
+
state.queue.splice(position, 0, item);
|
|
723
|
+
return position;
|
|
724
|
+
}
|
|
725
|
+
state.queue.push(item);
|
|
726
|
+
return state.queue.length - 1;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Remove a track from the queue
|
|
730
|
+
*/
|
|
731
|
+
removeFromQueue(guildId, position) {
|
|
732
|
+
const state = this.guildStates.get(guildId);
|
|
733
|
+
if (!state) return null;
|
|
734
|
+
const [removed] = state.queue.splice(position, 1);
|
|
735
|
+
return removed ?? null;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Clear the queue
|
|
739
|
+
*/
|
|
740
|
+
clearQueue(guildId) {
|
|
741
|
+
const state = this.guildStates.get(guildId);
|
|
742
|
+
if (!state) return 0;
|
|
743
|
+
const count = state.queue.length;
|
|
744
|
+
state.queue = [];
|
|
745
|
+
return count;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Shuffle the queue
|
|
749
|
+
*/
|
|
750
|
+
shuffleQueue(guildId) {
|
|
751
|
+
const state = this.guildStates.get(guildId);
|
|
752
|
+
if (!state) return;
|
|
753
|
+
for (let i = state.queue.length - 1; i > 0; i--) {
|
|
754
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
755
|
+
[state.queue[i], state.queue[j]] = [state.queue[j], state.queue[i]];
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Skip the current track
|
|
760
|
+
*/
|
|
761
|
+
skip(guildId) {
|
|
762
|
+
const state = this.guildStates.get(guildId);
|
|
763
|
+
if (!state) return false;
|
|
764
|
+
state.player.stop();
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Pause playback
|
|
769
|
+
*/
|
|
770
|
+
pause(guildId) {
|
|
771
|
+
const state = this.guildStates.get(guildId);
|
|
772
|
+
if (!state) return false;
|
|
773
|
+
state.player.pause();
|
|
774
|
+
state.paused = true;
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Resume playback
|
|
779
|
+
*/
|
|
780
|
+
resume(guildId) {
|
|
781
|
+
const state = this.guildStates.get(guildId);
|
|
782
|
+
if (!state) return false;
|
|
783
|
+
state.player.unpause();
|
|
784
|
+
state.paused = false;
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Stop playback
|
|
789
|
+
*/
|
|
790
|
+
stop(guildId) {
|
|
791
|
+
const state = this.guildStates.get(guildId);
|
|
792
|
+
if (!state) return false;
|
|
793
|
+
state.player.stop();
|
|
794
|
+
state.queue = [];
|
|
795
|
+
state.currentTrack = null;
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Set volume
|
|
800
|
+
*/
|
|
801
|
+
setVolume(guildId, volume) {
|
|
802
|
+
const state = this.guildStates.get(guildId);
|
|
803
|
+
if (!state) return false;
|
|
804
|
+
state.volume = Math.max(0, Math.min(200, volume));
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Set loop mode
|
|
809
|
+
*/
|
|
810
|
+
setLoopMode(guildId, mode) {
|
|
811
|
+
const state = this.guildStates.get(guildId);
|
|
812
|
+
if (!state) return;
|
|
813
|
+
state.loopMode = mode;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Get the current state for a guild
|
|
817
|
+
*/
|
|
818
|
+
getState(guildId) {
|
|
819
|
+
return this.guildStates.get(guildId);
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Check if connected to a guild
|
|
823
|
+
*/
|
|
824
|
+
isConnected(guildId) {
|
|
825
|
+
return this.guildStates.has(guildId);
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Get the queue for a guild
|
|
829
|
+
*/
|
|
830
|
+
getQueue(guildId) {
|
|
831
|
+
return this.guildStates.get(guildId)?.queue ?? [];
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Get the current track for a guild
|
|
835
|
+
*/
|
|
836
|
+
getCurrentTrack(guildId) {
|
|
837
|
+
return this.guildStates.get(guildId)?.currentTrack ?? null;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Handle track end (play next or loop)
|
|
841
|
+
*/
|
|
842
|
+
async handleTrackEnd(guildId) {
|
|
843
|
+
const state = this.guildStates.get(guildId);
|
|
844
|
+
if (!state) return;
|
|
845
|
+
if (state.loopMode === "track" && state.currentTrack) {
|
|
846
|
+
await this.play(guildId, state.currentTrack.url);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (state.loopMode === "queue" && state.currentTrack) {
|
|
850
|
+
state.queue.push(state.currentTrack);
|
|
851
|
+
}
|
|
852
|
+
const next = state.queue.shift();
|
|
853
|
+
if (next) {
|
|
854
|
+
state.currentTrack = next;
|
|
855
|
+
await this.play(guildId, next.url);
|
|
856
|
+
} else {
|
|
857
|
+
state.currentTrack = null;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Disconnect from all voice channels
|
|
862
|
+
*/
|
|
863
|
+
disconnectAll() {
|
|
864
|
+
for (const [guildId] of this.guildStates) {
|
|
865
|
+
this.leave(guildId);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
function createVoiceManager() {
|
|
870
|
+
return new VoiceManager();
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// src/video/index.ts
|
|
874
|
+
var VideoManager = class {
|
|
875
|
+
client;
|
|
876
|
+
config = {};
|
|
877
|
+
streamingMembers = /* @__PURE__ */ new Map();
|
|
878
|
+
// guildId -> Map<memberId, StreamInfo>
|
|
879
|
+
listeners = [];
|
|
880
|
+
initialized = false;
|
|
881
|
+
constructor(client) {
|
|
882
|
+
this.client = client;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Configure and initialize the video manager
|
|
886
|
+
*/
|
|
887
|
+
configure(config) {
|
|
888
|
+
this.config = config;
|
|
889
|
+
if (config.stream_detection && !this.initialized) {
|
|
890
|
+
this.setupListener();
|
|
891
|
+
this.initialized = true;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Set up voice state update listener for stream detection
|
|
896
|
+
*/
|
|
897
|
+
setupListener() {
|
|
898
|
+
this.client.on("voiceStateUpdate", (oldState, newState) => {
|
|
899
|
+
this.handleVoiceStateUpdate(oldState, newState);
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Handle voice state update for stream detection
|
|
904
|
+
*/
|
|
905
|
+
async handleVoiceStateUpdate(oldState, newState) {
|
|
906
|
+
const guildId = newState.guild.id;
|
|
907
|
+
const memberId = newState.member?.id;
|
|
908
|
+
if (!memberId || !newState.member) return;
|
|
909
|
+
if (!this.streamingMembers.has(guildId)) {
|
|
910
|
+
this.streamingMembers.set(guildId, /* @__PURE__ */ new Map());
|
|
911
|
+
}
|
|
912
|
+
const streaming = this.streamingMembers.get(guildId);
|
|
913
|
+
const wasStreaming = oldState.streaming;
|
|
914
|
+
const isStreaming = newState.streaming;
|
|
915
|
+
if (!wasStreaming && isStreaming && newState.channelId) {
|
|
916
|
+
const streamInfo = {
|
|
917
|
+
memberId,
|
|
918
|
+
username: newState.member.user.username,
|
|
919
|
+
channelId: newState.channelId,
|
|
920
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
921
|
+
};
|
|
922
|
+
streaming.set(memberId, streamInfo);
|
|
923
|
+
const event = {
|
|
924
|
+
type: "start",
|
|
925
|
+
member: newState.member,
|
|
926
|
+
channelId: newState.channelId,
|
|
927
|
+
guildId,
|
|
928
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
929
|
+
};
|
|
930
|
+
await this.emit(event);
|
|
931
|
+
await this.sendNotification(event);
|
|
932
|
+
}
|
|
933
|
+
if (wasStreaming && !isStreaming) {
|
|
934
|
+
streaming.delete(memberId);
|
|
935
|
+
const event = {
|
|
936
|
+
type: "stop",
|
|
937
|
+
member: newState.member,
|
|
938
|
+
channelId: oldState.channelId ?? "",
|
|
939
|
+
guildId,
|
|
940
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
941
|
+
};
|
|
942
|
+
await this.emit(event);
|
|
943
|
+
}
|
|
944
|
+
if (wasStreaming && !newState.channelId) {
|
|
945
|
+
streaming.delete(memberId);
|
|
946
|
+
const event = {
|
|
947
|
+
type: "stop",
|
|
948
|
+
member: newState.member,
|
|
949
|
+
channelId: oldState.channelId ?? "",
|
|
950
|
+
guildId,
|
|
951
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
952
|
+
};
|
|
953
|
+
await this.emit(event);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Emit a stream event to all listeners
|
|
958
|
+
*/
|
|
959
|
+
async emit(event) {
|
|
960
|
+
for (const listener of this.listeners) {
|
|
961
|
+
try {
|
|
962
|
+
await listener(event);
|
|
963
|
+
} catch (error) {
|
|
964
|
+
console.error("Stream event listener error:", error);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Send notification when a stream starts
|
|
970
|
+
*/
|
|
971
|
+
async sendNotification(event) {
|
|
972
|
+
if (event.type !== "start" || !this.config.notify_channel) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const guild = this.client.guilds.cache.get(event.guildId);
|
|
977
|
+
if (!guild) return;
|
|
978
|
+
const channelId = typeof this.config.notify_channel === "string" ? this.config.notify_channel.replace(/[<#>]/g, "") : String(this.config.notify_channel);
|
|
979
|
+
const channel = guild.channels.cache.get(channelId);
|
|
980
|
+
if (!channel || !channel.isTextBased()) return;
|
|
981
|
+
const voiceChannel = guild.channels.cache.get(event.channelId);
|
|
982
|
+
const voiceChannelName = voiceChannel?.name ?? "Unknown Channel";
|
|
983
|
+
let content = `**${event.member.displayName}** started streaming in **${voiceChannelName}**!`;
|
|
984
|
+
if (this.config.notify_role) {
|
|
985
|
+
const roleId = typeof this.config.notify_role === "string" ? this.config.notify_role.replace(/[<@&>]/g, "") : String(this.config.notify_role);
|
|
986
|
+
const role = guild.roles.cache.get(roleId);
|
|
987
|
+
if (role) {
|
|
988
|
+
content = `${role.toString()} ${content}`;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
await channel.send({
|
|
992
|
+
content,
|
|
993
|
+
allowedMentions: {
|
|
994
|
+
roles: this.config.notify_role ? [String(this.config.notify_role).replace(/[<@&>]/g, "")] : []
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
} catch (error) {
|
|
998
|
+
console.error("Failed to send stream notification:", error);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Register a stream event listener
|
|
1003
|
+
*/
|
|
1004
|
+
onStreamEvent(callback) {
|
|
1005
|
+
this.listeners.push(callback);
|
|
1006
|
+
return () => {
|
|
1007
|
+
const index = this.listeners.indexOf(callback);
|
|
1008
|
+
if (index !== -1) {
|
|
1009
|
+
this.listeners.splice(index, 1);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Get all members currently streaming in a guild
|
|
1015
|
+
*/
|
|
1016
|
+
getStreamingMembers(guildId) {
|
|
1017
|
+
const streaming = this.streamingMembers.get(guildId);
|
|
1018
|
+
if (!streaming) return [];
|
|
1019
|
+
return [...streaming.values()];
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Get streaming info for a specific member
|
|
1023
|
+
*/
|
|
1024
|
+
getStreamInfo(guildId, memberId) {
|
|
1025
|
+
return this.streamingMembers.get(guildId)?.get(memberId) ?? null;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Check if a member is streaming
|
|
1029
|
+
*/
|
|
1030
|
+
isStreaming(guildId, memberId) {
|
|
1031
|
+
return this.streamingMembers.get(guildId)?.has(memberId) ?? false;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Get stream count in a guild
|
|
1035
|
+
*/
|
|
1036
|
+
getStreamCount(guildId) {
|
|
1037
|
+
return this.streamingMembers.get(guildId)?.size ?? 0;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Get total active streams across all guilds
|
|
1041
|
+
*/
|
|
1042
|
+
getTotalStreamCount() {
|
|
1043
|
+
let count = 0;
|
|
1044
|
+
for (const streaming of this.streamingMembers.values()) {
|
|
1045
|
+
count += streaming.size;
|
|
1046
|
+
}
|
|
1047
|
+
return count;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Get all active streams across all guilds
|
|
1051
|
+
*/
|
|
1052
|
+
getAllActiveStreams() {
|
|
1053
|
+
const result = [];
|
|
1054
|
+
for (const [guildId, streaming] of this.streamingMembers) {
|
|
1055
|
+
if (streaming.size > 0) {
|
|
1056
|
+
result.push({ guildId, streams: [...streaming.values()] });
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return result;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Check if stream detection is enabled
|
|
1063
|
+
*/
|
|
1064
|
+
isEnabled() {
|
|
1065
|
+
return this.config.stream_detection ?? false;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Get current configuration
|
|
1069
|
+
*/
|
|
1070
|
+
getConfig() {
|
|
1071
|
+
return { ...this.config };
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
function createVideoManager(client) {
|
|
1075
|
+
return new VideoManager(client);
|
|
1076
|
+
}
|
|
1077
|
+
export {
|
|
1078
|
+
FurlowClient,
|
|
1079
|
+
GatewayManager,
|
|
1080
|
+
InteractionHandler,
|
|
1081
|
+
VideoManager,
|
|
1082
|
+
VoiceManager,
|
|
1083
|
+
createClient,
|
|
1084
|
+
createGatewayManager,
|
|
1085
|
+
createInteractionHandler,
|
|
1086
|
+
createVideoManager,
|
|
1087
|
+
createVoiceManager
|
|
1088
|
+
};
|
|
1089
|
+
//# sourceMappingURL=index.js.map
|