@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.
@@ -0,0 +1,209 @@
1
+ // src/video/index.ts
2
+ var VideoManager = class {
3
+ client;
4
+ config = {};
5
+ streamingMembers = /* @__PURE__ */ new Map();
6
+ // guildId -> Map<memberId, StreamInfo>
7
+ listeners = [];
8
+ initialized = false;
9
+ constructor(client) {
10
+ this.client = client;
11
+ }
12
+ /**
13
+ * Configure and initialize the video manager
14
+ */
15
+ configure(config) {
16
+ this.config = config;
17
+ if (config.stream_detection && !this.initialized) {
18
+ this.setupListener();
19
+ this.initialized = true;
20
+ }
21
+ }
22
+ /**
23
+ * Set up voice state update listener for stream detection
24
+ */
25
+ setupListener() {
26
+ this.client.on("voiceStateUpdate", (oldState, newState) => {
27
+ this.handleVoiceStateUpdate(oldState, newState);
28
+ });
29
+ }
30
+ /**
31
+ * Handle voice state update for stream detection
32
+ */
33
+ async handleVoiceStateUpdate(oldState, newState) {
34
+ const guildId = newState.guild.id;
35
+ const memberId = newState.member?.id;
36
+ if (!memberId || !newState.member) return;
37
+ if (!this.streamingMembers.has(guildId)) {
38
+ this.streamingMembers.set(guildId, /* @__PURE__ */ new Map());
39
+ }
40
+ const streaming = this.streamingMembers.get(guildId);
41
+ const wasStreaming = oldState.streaming;
42
+ const isStreaming = newState.streaming;
43
+ if (!wasStreaming && isStreaming && newState.channelId) {
44
+ const streamInfo = {
45
+ memberId,
46
+ username: newState.member.user.username,
47
+ channelId: newState.channelId,
48
+ startedAt: /* @__PURE__ */ new Date()
49
+ };
50
+ streaming.set(memberId, streamInfo);
51
+ const event = {
52
+ type: "start",
53
+ member: newState.member,
54
+ channelId: newState.channelId,
55
+ guildId,
56
+ timestamp: /* @__PURE__ */ new Date()
57
+ };
58
+ await this.emit(event);
59
+ await this.sendNotification(event);
60
+ }
61
+ if (wasStreaming && !isStreaming) {
62
+ streaming.delete(memberId);
63
+ const event = {
64
+ type: "stop",
65
+ member: newState.member,
66
+ channelId: oldState.channelId ?? "",
67
+ guildId,
68
+ timestamp: /* @__PURE__ */ new Date()
69
+ };
70
+ await this.emit(event);
71
+ }
72
+ if (wasStreaming && !newState.channelId) {
73
+ streaming.delete(memberId);
74
+ const event = {
75
+ type: "stop",
76
+ member: newState.member,
77
+ channelId: oldState.channelId ?? "",
78
+ guildId,
79
+ timestamp: /* @__PURE__ */ new Date()
80
+ };
81
+ await this.emit(event);
82
+ }
83
+ }
84
+ /**
85
+ * Emit a stream event to all listeners
86
+ */
87
+ async emit(event) {
88
+ for (const listener of this.listeners) {
89
+ try {
90
+ await listener(event);
91
+ } catch (error) {
92
+ console.error("Stream event listener error:", error);
93
+ }
94
+ }
95
+ }
96
+ /**
97
+ * Send notification when a stream starts
98
+ */
99
+ async sendNotification(event) {
100
+ if (event.type !== "start" || !this.config.notify_channel) {
101
+ return;
102
+ }
103
+ try {
104
+ const guild = this.client.guilds.cache.get(event.guildId);
105
+ if (!guild) return;
106
+ const channelId = typeof this.config.notify_channel === "string" ? this.config.notify_channel.replace(/[<#>]/g, "") : String(this.config.notify_channel);
107
+ const channel = guild.channels.cache.get(channelId);
108
+ if (!channel || !channel.isTextBased()) return;
109
+ const voiceChannel = guild.channels.cache.get(event.channelId);
110
+ const voiceChannelName = voiceChannel?.name ?? "Unknown Channel";
111
+ let content = `**${event.member.displayName}** started streaming in **${voiceChannelName}**!`;
112
+ if (this.config.notify_role) {
113
+ const roleId = typeof this.config.notify_role === "string" ? this.config.notify_role.replace(/[<@&>]/g, "") : String(this.config.notify_role);
114
+ const role = guild.roles.cache.get(roleId);
115
+ if (role) {
116
+ content = `${role.toString()} ${content}`;
117
+ }
118
+ }
119
+ await channel.send({
120
+ content,
121
+ allowedMentions: {
122
+ roles: this.config.notify_role ? [String(this.config.notify_role).replace(/[<@&>]/g, "")] : []
123
+ }
124
+ });
125
+ } catch (error) {
126
+ console.error("Failed to send stream notification:", error);
127
+ }
128
+ }
129
+ /**
130
+ * Register a stream event listener
131
+ */
132
+ onStreamEvent(callback) {
133
+ this.listeners.push(callback);
134
+ return () => {
135
+ const index = this.listeners.indexOf(callback);
136
+ if (index !== -1) {
137
+ this.listeners.splice(index, 1);
138
+ }
139
+ };
140
+ }
141
+ /**
142
+ * Get all members currently streaming in a guild
143
+ */
144
+ getStreamingMembers(guildId) {
145
+ const streaming = this.streamingMembers.get(guildId);
146
+ if (!streaming) return [];
147
+ return [...streaming.values()];
148
+ }
149
+ /**
150
+ * Get streaming info for a specific member
151
+ */
152
+ getStreamInfo(guildId, memberId) {
153
+ return this.streamingMembers.get(guildId)?.get(memberId) ?? null;
154
+ }
155
+ /**
156
+ * Check if a member is streaming
157
+ */
158
+ isStreaming(guildId, memberId) {
159
+ return this.streamingMembers.get(guildId)?.has(memberId) ?? false;
160
+ }
161
+ /**
162
+ * Get stream count in a guild
163
+ */
164
+ getStreamCount(guildId) {
165
+ return this.streamingMembers.get(guildId)?.size ?? 0;
166
+ }
167
+ /**
168
+ * Get total active streams across all guilds
169
+ */
170
+ getTotalStreamCount() {
171
+ let count = 0;
172
+ for (const streaming of this.streamingMembers.values()) {
173
+ count += streaming.size;
174
+ }
175
+ return count;
176
+ }
177
+ /**
178
+ * Get all active streams across all guilds
179
+ */
180
+ getAllActiveStreams() {
181
+ const result = [];
182
+ for (const [guildId, streaming] of this.streamingMembers) {
183
+ if (streaming.size > 0) {
184
+ result.push({ guildId, streams: [...streaming.values()] });
185
+ }
186
+ }
187
+ return result;
188
+ }
189
+ /**
190
+ * Check if stream detection is enabled
191
+ */
192
+ isEnabled() {
193
+ return this.config.stream_detection ?? false;
194
+ }
195
+ /**
196
+ * Get current configuration
197
+ */
198
+ getConfig() {
199
+ return { ...this.config };
200
+ }
201
+ };
202
+ function createVideoManager(client) {
203
+ return new VideoManager(client);
204
+ }
205
+ export {
206
+ VideoManager,
207
+ createVideoManager
208
+ };
209
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/video/index.ts"],"sourcesContent":["/**\n * Video/streaming detection and notifications\n */\n\nimport type { Client, VoiceState, GuildMember, TextChannel, Role } from 'discord.js';\nimport type { VideoConfig } from '@furlow/schema';\n\nexport interface StreamEvent {\n type: 'start' | 'stop';\n member: GuildMember;\n channelId: string;\n guildId: string;\n timestamp: Date;\n}\n\nexport interface StreamInfo {\n memberId: string;\n username: string;\n channelId: string;\n startedAt: Date;\n}\n\nexport type StreamEventCallback = (event: StreamEvent) => void | Promise<void>;\n\nexport class VideoManager {\n private client: Client;\n private config: VideoConfig = {};\n private streamingMembers: Map<string, Map<string, StreamInfo>> = new Map(); // guildId -> Map<memberId, StreamInfo>\n private listeners: StreamEventCallback[] = [];\n private initialized = false;\n\n constructor(client: Client) {\n this.client = client;\n }\n\n /**\n * Configure and initialize the video manager\n */\n configure(config: VideoConfig): void {\n this.config = config;\n\n if (config.stream_detection && !this.initialized) {\n this.setupListener();\n this.initialized = true;\n }\n }\n\n /**\n * Set up voice state update listener for stream detection\n */\n private setupListener(): void {\n this.client.on('voiceStateUpdate', (oldState, newState) => {\n this.handleVoiceStateUpdate(oldState, newState);\n });\n }\n\n /**\n * Handle voice state update for stream detection\n */\n private async handleVoiceStateUpdate(\n oldState: VoiceState,\n newState: VoiceState\n ): Promise<void> {\n const guildId = newState.guild.id;\n const memberId = newState.member?.id;\n\n if (!memberId || !newState.member) return;\n\n // Get or create streaming map for guild\n if (!this.streamingMembers.has(guildId)) {\n this.streamingMembers.set(guildId, new Map());\n }\n const streaming = this.streamingMembers.get(guildId)!;\n\n const wasStreaming = oldState.streaming;\n const isStreaming = newState.streaming;\n\n // Stream started\n if (!wasStreaming && isStreaming && newState.channelId) {\n const streamInfo: StreamInfo = {\n memberId,\n username: newState.member.user.username,\n channelId: newState.channelId,\n startedAt: new Date(),\n };\n streaming.set(memberId, streamInfo);\n\n const event: StreamEvent = {\n type: 'start',\n member: newState.member,\n channelId: newState.channelId,\n guildId,\n timestamp: new Date(),\n };\n\n await this.emit(event);\n await this.sendNotification(event);\n }\n\n // Stream stopped\n if (wasStreaming && !isStreaming) {\n streaming.delete(memberId);\n const event: StreamEvent = {\n type: 'stop',\n member: newState.member,\n channelId: oldState.channelId ?? '',\n guildId,\n timestamp: new Date(),\n };\n await this.emit(event);\n }\n\n // Left voice while streaming\n if (wasStreaming && !newState.channelId) {\n streaming.delete(memberId);\n const event: StreamEvent = {\n type: 'stop',\n member: newState.member,\n channelId: oldState.channelId ?? '',\n guildId,\n timestamp: new Date(),\n };\n await this.emit(event);\n }\n }\n\n /**\n * Emit a stream event to all listeners\n */\n private async emit(event: StreamEvent): Promise<void> {\n for (const listener of this.listeners) {\n try {\n await listener(event);\n } catch (error) {\n console.error('Stream event listener error:', error);\n }\n }\n }\n\n /**\n * Send notification when a stream starts\n */\n private async sendNotification(event: StreamEvent): Promise<void> {\n if (event.type !== 'start' || !this.config.notify_channel) {\n return;\n }\n\n try {\n const guild = this.client.guilds.cache.get(event.guildId);\n if (!guild) return;\n\n // Resolve channel (could be an ID or expression result)\n const channelId = typeof this.config.notify_channel === 'string'\n ? this.config.notify_channel.replace(/[<#>]/g, '') // Handle mention format\n : String(this.config.notify_channel);\n\n const channel = guild.channels.cache.get(channelId) as TextChannel | undefined;\n if (!channel || !channel.isTextBased()) return;\n\n // Build notification message\n const voiceChannel = guild.channels.cache.get(event.channelId);\n const voiceChannelName = voiceChannel?.name ?? 'Unknown Channel';\n\n let content = `**${event.member.displayName}** started streaming in **${voiceChannelName}**!`;\n\n // Add role mention if configured\n if (this.config.notify_role) {\n const roleId = typeof this.config.notify_role === 'string'\n ? this.config.notify_role.replace(/[<@&>]/g, '')\n : String(this.config.notify_role);\n\n const role = guild.roles.cache.get(roleId);\n if (role) {\n content = `${role.toString()} ${content}`;\n }\n }\n\n await channel.send({\n content,\n allowedMentions: {\n roles: this.config.notify_role ? [String(this.config.notify_role).replace(/[<@&>]/g, '')] : [],\n },\n });\n } catch (error) {\n console.error('Failed to send stream notification:', error);\n }\n }\n\n /**\n * Register a stream event listener\n */\n onStreamEvent(callback: StreamEventCallback): () => void {\n this.listeners.push(callback);\n return () => {\n const index = this.listeners.indexOf(callback);\n if (index !== -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n /**\n * Get all members currently streaming in a guild\n */\n getStreamingMembers(guildId: string): StreamInfo[] {\n const streaming = this.streamingMembers.get(guildId);\n if (!streaming) return [];\n return [...streaming.values()];\n }\n\n /**\n * Get streaming info for a specific member\n */\n getStreamInfo(guildId: string, memberId: string): StreamInfo | null {\n return this.streamingMembers.get(guildId)?.get(memberId) ?? null;\n }\n\n /**\n * Check if a member is streaming\n */\n isStreaming(guildId: string, memberId: string): boolean {\n return this.streamingMembers.get(guildId)?.has(memberId) ?? false;\n }\n\n /**\n * Get stream count in a guild\n */\n getStreamCount(guildId: string): number {\n return this.streamingMembers.get(guildId)?.size ?? 0;\n }\n\n /**\n * Get total active streams across all guilds\n */\n getTotalStreamCount(): number {\n let count = 0;\n for (const streaming of this.streamingMembers.values()) {\n count += streaming.size;\n }\n return count;\n }\n\n /**\n * Get all active streams across all guilds\n */\n getAllActiveStreams(): Array<{ guildId: string; streams: StreamInfo[] }> {\n const result: Array<{ guildId: string; streams: StreamInfo[] }> = [];\n for (const [guildId, streaming] of this.streamingMembers) {\n if (streaming.size > 0) {\n result.push({ guildId, streams: [...streaming.values()] });\n }\n }\n return result;\n }\n\n /**\n * Check if stream detection is enabled\n */\n isEnabled(): boolean {\n return this.config.stream_detection ?? false;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): VideoConfig {\n return { ...this.config };\n }\n}\n\n/**\n * Create a video manager\n */\nexport function createVideoManager(client: Client): VideoManager {\n return new VideoManager(client);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,136 @@
1
+ import { VoiceConnection, AudioPlayer } from '@discordjs/voice';
2
+ import { VoiceChannel, StageChannel } from 'discord.js';
3
+
4
+ /**
5
+ * Voice connection and audio management
6
+ */
7
+
8
+ /** Voice configuration */
9
+ interface VoiceConfig {
10
+ connection?: {
11
+ self_deaf?: boolean;
12
+ self_mute?: boolean;
13
+ timeout?: string;
14
+ };
15
+ default_volume?: number;
16
+ default_loop?: QueueLoopMode;
17
+ max_queue_size?: number;
18
+ filters?: string[];
19
+ }
20
+ /** Audio filter types */
21
+ type AudioFilter = 'bassboost' | 'nightcore' | 'vaporwave' | '8d' | 'treble' | 'normalizer' | 'karaoke' | 'tremolo' | 'vibrato' | 'reverse';
22
+ /** Queue loop mode */
23
+ type QueueLoopMode = 'off' | 'track' | 'queue';
24
+ interface QueueItem {
25
+ url: string;
26
+ title: string;
27
+ duration?: number;
28
+ thumbnail?: string;
29
+ requesterId: string;
30
+ }
31
+ interface GuildVoiceState {
32
+ connection: VoiceConnection;
33
+ player: AudioPlayer;
34
+ queue: QueueItem[];
35
+ currentTrack: QueueItem | null;
36
+ volume: number;
37
+ loopMode: QueueLoopMode;
38
+ filters: Set<AudioFilter>;
39
+ paused: boolean;
40
+ }
41
+ declare class VoiceManager {
42
+ private guildStates;
43
+ private config;
44
+ /**
45
+ * Configure the voice manager
46
+ */
47
+ configure(config: VoiceConfig): void;
48
+ /**
49
+ * Join a voice channel
50
+ */
51
+ join(channel: VoiceChannel | StageChannel, options?: {
52
+ selfDeaf?: boolean;
53
+ selfMute?: boolean;
54
+ }): Promise<VoiceConnection>;
55
+ /**
56
+ * Leave a voice channel
57
+ */
58
+ leave(guildId: string): boolean;
59
+ /**
60
+ * Play audio from a URL or file
61
+ */
62
+ play(guildId: string, source: string, options?: {
63
+ volume?: number;
64
+ seek?: number;
65
+ }): Promise<void>;
66
+ /**
67
+ * Add a track to the queue
68
+ */
69
+ addToQueue(guildId: string, item: QueueItem, position?: number | 'next' | 'last'): number;
70
+ /**
71
+ * Remove a track from the queue
72
+ */
73
+ removeFromQueue(guildId: string, position: number): QueueItem | null;
74
+ /**
75
+ * Clear the queue
76
+ */
77
+ clearQueue(guildId: string): number;
78
+ /**
79
+ * Shuffle the queue
80
+ */
81
+ shuffleQueue(guildId: string): void;
82
+ /**
83
+ * Skip the current track
84
+ */
85
+ skip(guildId: string): boolean;
86
+ /**
87
+ * Pause playback
88
+ */
89
+ pause(guildId: string): boolean;
90
+ /**
91
+ * Resume playback
92
+ */
93
+ resume(guildId: string): boolean;
94
+ /**
95
+ * Stop playback
96
+ */
97
+ stop(guildId: string): boolean;
98
+ /**
99
+ * Set volume
100
+ */
101
+ setVolume(guildId: string, volume: number): boolean;
102
+ /**
103
+ * Set loop mode
104
+ */
105
+ setLoopMode(guildId: string, mode: QueueLoopMode): void;
106
+ /**
107
+ * Get the current state for a guild
108
+ */
109
+ getState(guildId: string): GuildVoiceState | undefined;
110
+ /**
111
+ * Check if connected to a guild
112
+ */
113
+ isConnected(guildId: string): boolean;
114
+ /**
115
+ * Get the queue for a guild
116
+ */
117
+ getQueue(guildId: string): QueueItem[];
118
+ /**
119
+ * Get the current track for a guild
120
+ */
121
+ getCurrentTrack(guildId: string): QueueItem | null;
122
+ /**
123
+ * Handle track end (play next or loop)
124
+ */
125
+ private handleTrackEnd;
126
+ /**
127
+ * Disconnect from all voice channels
128
+ */
129
+ disconnectAll(): void;
130
+ }
131
+ /**
132
+ * Create a voice manager
133
+ */
134
+ declare function createVoiceManager(): VoiceManager;
135
+
136
+ export { type AudioFilter, type GuildVoiceState, type QueueItem, type QueueLoopMode, type VoiceConfig, VoiceManager, createVoiceManager };
@@ -0,0 +1,258 @@
1
+ // src/voice/index.ts
2
+ import {
3
+ joinVoiceChannel,
4
+ createAudioPlayer,
5
+ createAudioResource,
6
+ AudioPlayerStatus,
7
+ VoiceConnectionStatus,
8
+ entersState,
9
+ getVoiceConnection
10
+ } from "@discordjs/voice";
11
+ var VoiceManager = class {
12
+ guildStates = /* @__PURE__ */ new Map();
13
+ config = {};
14
+ /**
15
+ * Configure the voice manager
16
+ */
17
+ configure(config) {
18
+ this.config = config;
19
+ }
20
+ /**
21
+ * Join a voice channel
22
+ */
23
+ async join(channel, options = {}) {
24
+ const guildId = channel.guild.id;
25
+ const existing = getVoiceConnection(guildId);
26
+ if (existing) {
27
+ existing.destroy();
28
+ }
29
+ const connection = joinVoiceChannel({
30
+ channelId: channel.id,
31
+ guildId,
32
+ adapterCreator: channel.guild.voiceAdapterCreator,
33
+ selfDeaf: options.selfDeaf ?? this.config.connection?.self_deaf ?? true,
34
+ selfMute: options.selfMute ?? this.config.connection?.self_mute ?? false
35
+ });
36
+ await entersState(connection, VoiceConnectionStatus.Ready, 3e4);
37
+ const player = createAudioPlayer();
38
+ player.on(AudioPlayerStatus.Idle, () => {
39
+ this.handleTrackEnd(guildId);
40
+ });
41
+ player.on("error", (error) => {
42
+ console.error(`Audio player error in ${guildId}:`, error);
43
+ this.handleTrackEnd(guildId);
44
+ });
45
+ connection.subscribe(player);
46
+ this.guildStates.set(guildId, {
47
+ connection,
48
+ player,
49
+ queue: [],
50
+ currentTrack: null,
51
+ volume: this.config.default_volume ?? 100,
52
+ loopMode: this.config.default_loop ?? "off",
53
+ filters: /* @__PURE__ */ new Set(),
54
+ paused: false
55
+ });
56
+ return connection;
57
+ }
58
+ /**
59
+ * Leave a voice channel
60
+ */
61
+ leave(guildId) {
62
+ const state = this.guildStates.get(guildId);
63
+ if (!state) return false;
64
+ state.player.stop();
65
+ state.connection.destroy();
66
+ this.guildStates.delete(guildId);
67
+ return true;
68
+ }
69
+ /**
70
+ * Play audio from a URL or file
71
+ */
72
+ async play(guildId, source, options = {}) {
73
+ const state = this.guildStates.get(guildId);
74
+ if (!state) {
75
+ throw new Error("Not connected to voice in this guild");
76
+ }
77
+ const resource = createAudioResource(source, {
78
+ inlineVolume: true
79
+ });
80
+ const volume = options.volume ?? state.volume;
81
+ resource.volume?.setVolume(volume / 100);
82
+ state.player.play(resource);
83
+ state.paused = false;
84
+ }
85
+ /**
86
+ * Add a track to the queue
87
+ */
88
+ addToQueue(guildId, item, position) {
89
+ const state = this.guildStates.get(guildId);
90
+ if (!state) {
91
+ throw new Error("Not connected to voice in this guild");
92
+ }
93
+ const maxSize = this.config.max_queue_size ?? 1e3;
94
+ if (state.queue.length >= maxSize) {
95
+ throw new Error(`Queue is full (max ${maxSize} tracks)`);
96
+ }
97
+ if (position === "next" || position === 0) {
98
+ state.queue.unshift(item);
99
+ return 0;
100
+ } else if (position === "last" || position === void 0) {
101
+ state.queue.push(item);
102
+ return state.queue.length - 1;
103
+ } else if (typeof position === "number") {
104
+ state.queue.splice(position, 0, item);
105
+ return position;
106
+ }
107
+ state.queue.push(item);
108
+ return state.queue.length - 1;
109
+ }
110
+ /**
111
+ * Remove a track from the queue
112
+ */
113
+ removeFromQueue(guildId, position) {
114
+ const state = this.guildStates.get(guildId);
115
+ if (!state) return null;
116
+ const [removed] = state.queue.splice(position, 1);
117
+ return removed ?? null;
118
+ }
119
+ /**
120
+ * Clear the queue
121
+ */
122
+ clearQueue(guildId) {
123
+ const state = this.guildStates.get(guildId);
124
+ if (!state) return 0;
125
+ const count = state.queue.length;
126
+ state.queue = [];
127
+ return count;
128
+ }
129
+ /**
130
+ * Shuffle the queue
131
+ */
132
+ shuffleQueue(guildId) {
133
+ const state = this.guildStates.get(guildId);
134
+ if (!state) return;
135
+ for (let i = state.queue.length - 1; i > 0; i--) {
136
+ const j = Math.floor(Math.random() * (i + 1));
137
+ [state.queue[i], state.queue[j]] = [state.queue[j], state.queue[i]];
138
+ }
139
+ }
140
+ /**
141
+ * Skip the current track
142
+ */
143
+ skip(guildId) {
144
+ const state = this.guildStates.get(guildId);
145
+ if (!state) return false;
146
+ state.player.stop();
147
+ return true;
148
+ }
149
+ /**
150
+ * Pause playback
151
+ */
152
+ pause(guildId) {
153
+ const state = this.guildStates.get(guildId);
154
+ if (!state) return false;
155
+ state.player.pause();
156
+ state.paused = true;
157
+ return true;
158
+ }
159
+ /**
160
+ * Resume playback
161
+ */
162
+ resume(guildId) {
163
+ const state = this.guildStates.get(guildId);
164
+ if (!state) return false;
165
+ state.player.unpause();
166
+ state.paused = false;
167
+ return true;
168
+ }
169
+ /**
170
+ * Stop playback
171
+ */
172
+ stop(guildId) {
173
+ const state = this.guildStates.get(guildId);
174
+ if (!state) return false;
175
+ state.player.stop();
176
+ state.queue = [];
177
+ state.currentTrack = null;
178
+ return true;
179
+ }
180
+ /**
181
+ * Set volume
182
+ */
183
+ setVolume(guildId, volume) {
184
+ const state = this.guildStates.get(guildId);
185
+ if (!state) return false;
186
+ state.volume = Math.max(0, Math.min(200, volume));
187
+ return true;
188
+ }
189
+ /**
190
+ * Set loop mode
191
+ */
192
+ setLoopMode(guildId, mode) {
193
+ const state = this.guildStates.get(guildId);
194
+ if (!state) return;
195
+ state.loopMode = mode;
196
+ }
197
+ /**
198
+ * Get the current state for a guild
199
+ */
200
+ getState(guildId) {
201
+ return this.guildStates.get(guildId);
202
+ }
203
+ /**
204
+ * Check if connected to a guild
205
+ */
206
+ isConnected(guildId) {
207
+ return this.guildStates.has(guildId);
208
+ }
209
+ /**
210
+ * Get the queue for a guild
211
+ */
212
+ getQueue(guildId) {
213
+ return this.guildStates.get(guildId)?.queue ?? [];
214
+ }
215
+ /**
216
+ * Get the current track for a guild
217
+ */
218
+ getCurrentTrack(guildId) {
219
+ return this.guildStates.get(guildId)?.currentTrack ?? null;
220
+ }
221
+ /**
222
+ * Handle track end (play next or loop)
223
+ */
224
+ async handleTrackEnd(guildId) {
225
+ const state = this.guildStates.get(guildId);
226
+ if (!state) return;
227
+ if (state.loopMode === "track" && state.currentTrack) {
228
+ await this.play(guildId, state.currentTrack.url);
229
+ return;
230
+ }
231
+ if (state.loopMode === "queue" && state.currentTrack) {
232
+ state.queue.push(state.currentTrack);
233
+ }
234
+ const next = state.queue.shift();
235
+ if (next) {
236
+ state.currentTrack = next;
237
+ await this.play(guildId, next.url);
238
+ } else {
239
+ state.currentTrack = null;
240
+ }
241
+ }
242
+ /**
243
+ * Disconnect from all voice channels
244
+ */
245
+ disconnectAll() {
246
+ for (const [guildId] of this.guildStates) {
247
+ this.leave(guildId);
248
+ }
249
+ }
250
+ };
251
+ function createVoiceManager() {
252
+ return new VoiceManager();
253
+ }
254
+ export {
255
+ VoiceManager,
256
+ createVoiceManager
257
+ };
258
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/voice/index.ts"],"sourcesContent":["/**\n * Voice connection and audio management\n */\n\nimport {\n joinVoiceChannel,\n createAudioPlayer,\n createAudioResource,\n AudioPlayerStatus,\n VoiceConnectionStatus,\n entersState,\n getVoiceConnection,\n type VoiceConnection,\n type AudioPlayer,\n type AudioResource,\n type DiscordGatewayAdapterCreator,\n} from '@discordjs/voice';\nimport type { VoiceChannel, StageChannel, Guild } from 'discord.js';\n\n/** Voice configuration */\nexport interface VoiceConfig {\n connection?: {\n self_deaf?: boolean;\n self_mute?: boolean;\n timeout?: string;\n };\n default_volume?: number;\n default_loop?: QueueLoopMode;\n max_queue_size?: number;\n filters?: string[];\n}\n\n/** Audio filter types */\nexport type AudioFilter =\n | 'bassboost'\n | 'nightcore'\n | 'vaporwave'\n | '8d'\n | 'treble'\n | 'normalizer'\n | 'karaoke'\n | 'tremolo'\n | 'vibrato'\n | 'reverse';\n\n/** Queue loop mode */\nexport type QueueLoopMode = 'off' | 'track' | 'queue';\n\nexport interface QueueItem {\n url: string;\n title: string;\n duration?: number;\n thumbnail?: string;\n requesterId: string;\n}\n\nexport interface GuildVoiceState {\n connection: VoiceConnection;\n player: AudioPlayer;\n queue: QueueItem[];\n currentTrack: QueueItem | null;\n volume: number;\n loopMode: QueueLoopMode;\n filters: Set<AudioFilter>;\n paused: boolean;\n}\n\nexport class VoiceManager {\n private guildStates: Map<string, GuildVoiceState> = new Map();\n private config: VoiceConfig = {};\n\n /**\n * Configure the voice manager\n */\n configure(config: VoiceConfig): void {\n this.config = config;\n }\n\n /**\n * Join a voice channel\n */\n async join(\n channel: VoiceChannel | StageChannel,\n options: { selfDeaf?: boolean; selfMute?: boolean } = {}\n ): Promise<VoiceConnection> {\n const guildId = channel.guild.id;\n\n // Leave existing connection if any\n const existing = getVoiceConnection(guildId);\n if (existing) {\n existing.destroy();\n }\n\n const connection = joinVoiceChannel({\n channelId: channel.id,\n guildId: guildId,\n adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,\n selfDeaf: options.selfDeaf ?? this.config.connection?.self_deaf ?? true,\n selfMute: options.selfMute ?? this.config.connection?.self_mute ?? false,\n });\n\n // Wait for connection to be ready\n await entersState(connection, VoiceConnectionStatus.Ready, 30000);\n\n // Create audio player\n const player = createAudioPlayer();\n\n // Set up player events\n player.on(AudioPlayerStatus.Idle, () => {\n this.handleTrackEnd(guildId);\n });\n\n player.on('error', (error) => {\n console.error(`Audio player error in ${guildId}:`, error);\n this.handleTrackEnd(guildId);\n });\n\n // Subscribe connection to player\n connection.subscribe(player);\n\n // Store state\n this.guildStates.set(guildId, {\n connection,\n player,\n queue: [],\n currentTrack: null,\n volume: this.config.default_volume ?? 100,\n loopMode: this.config.default_loop ?? 'off',\n filters: new Set(),\n paused: false,\n });\n\n return connection;\n }\n\n /**\n * Leave a voice channel\n */\n leave(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.connection.destroy();\n this.guildStates.delete(guildId);\n\n return true;\n }\n\n /**\n * Play audio from a URL or file\n */\n async play(\n guildId: string,\n source: string,\n options: { volume?: number; seek?: number } = {}\n ): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Create audio resource\n const resource = createAudioResource(source, {\n inlineVolume: true,\n });\n\n // Set volume\n const volume = options.volume ?? state.volume;\n resource.volume?.setVolume(volume / 100);\n\n // Play\n state.player.play(resource);\n state.paused = false;\n }\n\n /**\n * Add a track to the queue\n */\n addToQueue(\n guildId: string,\n item: QueueItem,\n position?: number | 'next' | 'last'\n ): number {\n const state = this.guildStates.get(guildId);\n if (!state) {\n throw new Error('Not connected to voice in this guild');\n }\n\n // Check queue size limit\n const maxSize = this.config.max_queue_size ?? 1000;\n if (state.queue.length >= maxSize) {\n throw new Error(`Queue is full (max ${maxSize} tracks)`);\n }\n\n if (position === 'next' || position === 0) {\n state.queue.unshift(item);\n return 0;\n } else if (position === 'last' || position === undefined) {\n state.queue.push(item);\n return state.queue.length - 1;\n } else if (typeof position === 'number') {\n state.queue.splice(position, 0, item);\n return position;\n }\n\n state.queue.push(item);\n return state.queue.length - 1;\n }\n\n /**\n * Remove a track from the queue\n */\n removeFromQueue(guildId: string, position: number): QueueItem | null {\n const state = this.guildStates.get(guildId);\n if (!state) return null;\n\n const [removed] = state.queue.splice(position, 1);\n return removed ?? null;\n }\n\n /**\n * Clear the queue\n */\n clearQueue(guildId: string): number {\n const state = this.guildStates.get(guildId);\n if (!state) return 0;\n\n const count = state.queue.length;\n state.queue = [];\n return count;\n }\n\n /**\n * Shuffle the queue\n */\n shuffleQueue(guildId: string): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n for (let i = state.queue.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [state.queue[i], state.queue[j]] = [state.queue[j]!, state.queue[i]!];\n }\n }\n\n /**\n * Skip the current track\n */\n skip(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n return true;\n }\n\n /**\n * Pause playback\n */\n pause(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.pause();\n state.paused = true;\n return true;\n }\n\n /**\n * Resume playback\n */\n resume(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.unpause();\n state.paused = false;\n return true;\n }\n\n /**\n * Stop playback\n */\n stop(guildId: string): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.player.stop();\n state.queue = [];\n state.currentTrack = null;\n return true;\n }\n\n /**\n * Set volume\n */\n setVolume(guildId: string, volume: number): boolean {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n state.volume = Math.max(0, Math.min(200, volume));\n return true;\n }\n\n /**\n * Set loop mode\n */\n setLoopMode(guildId: string, mode: QueueLoopMode): void {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n state.loopMode = mode;\n }\n\n /**\n * Get the current state for a guild\n */\n getState(guildId: string): GuildVoiceState | undefined {\n return this.guildStates.get(guildId);\n }\n\n /**\n * Check if connected to a guild\n */\n isConnected(guildId: string): boolean {\n return this.guildStates.has(guildId);\n }\n\n /**\n * Get the queue for a guild\n */\n getQueue(guildId: string): QueueItem[] {\n return this.guildStates.get(guildId)?.queue ?? [];\n }\n\n /**\n * Get the current track for a guild\n */\n getCurrentTrack(guildId: string): QueueItem | null {\n return this.guildStates.get(guildId)?.currentTrack ?? null;\n }\n\n /**\n * Handle track end (play next or loop)\n */\n private async handleTrackEnd(guildId: string): Promise<void> {\n const state = this.guildStates.get(guildId);\n if (!state) return;\n\n // Handle loop modes\n if (state.loopMode === 'track' && state.currentTrack) {\n await this.play(guildId, state.currentTrack.url);\n return;\n }\n\n if (state.loopMode === 'queue' && state.currentTrack) {\n state.queue.push(state.currentTrack);\n }\n\n // Play next track\n const next = state.queue.shift();\n if (next) {\n state.currentTrack = next;\n await this.play(guildId, next.url);\n } else {\n state.currentTrack = null;\n }\n }\n\n /**\n * Disconnect from all voice channels\n */\n disconnectAll(): void {\n for (const [guildId] of this.guildStates) {\n this.leave(guildId);\n }\n }\n}\n\n/**\n * Create a voice manager\n */\nexport function createVoiceManager(): VoiceManager {\n return new VoiceManager();\n}\n"],"mappings":"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","names":[]}