@furlow/discord 1.0.1 → 1.0.3
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 +2 -0
- package/dist/client/index.js +30 -16
- package/dist/client/index.js.map +1 -1
- package/dist/index.js +110 -36
- package/dist/index.js.map +1 -1
- package/dist/interactions/index.d.ts +8 -1
- package/dist/interactions/index.js +24 -4
- package/dist/interactions/index.js.map +1 -1
- package/dist/video/index.js +3 -1
- package/dist/video/index.js.map +1 -1
- package/dist/voice/index.js +53 -15
- package/dist/voice/index.js.map +1 -1
- package/package.json +3 -3
package/dist/video/index.js.map
CHANGED
|
@@ -1 +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":[]}
|
|
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).catch((err) => {\n console.error('Error handling voice state update for stream detection:', err);\n });\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":[]}
|
package/dist/voice/index.js
CHANGED
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
AudioPlayerStatus,
|
|
7
7
|
VoiceConnectionStatus,
|
|
8
8
|
entersState,
|
|
9
|
-
getVoiceConnection
|
|
9
|
+
getVoiceConnection,
|
|
10
|
+
StreamType
|
|
10
11
|
} from "@discordjs/voice";
|
|
12
|
+
import { FFmpeg } from "prism-media";
|
|
11
13
|
var FILTER_ARGS = {
|
|
12
14
|
bassboost: ["-af", "bass=g=10,dynaudnorm=f=200"],
|
|
13
15
|
nightcore: ["-af", "asetrate=44100*1.25,aresample=44100,atempo=1.06"],
|
|
@@ -45,14 +47,23 @@ var VoiceManager = class {
|
|
|
45
47
|
selfDeaf: options.selfDeaf ?? this.config.connection?.self_deaf ?? true,
|
|
46
48
|
selfMute: options.selfMute ?? this.config.connection?.self_mute ?? false
|
|
47
49
|
});
|
|
48
|
-
|
|
50
|
+
try {
|
|
51
|
+
await entersState(connection, VoiceConnectionStatus.Ready, 3e4);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
connection.destroy();
|
|
54
|
+
throw new Error(`Failed to connect to voice channel: ${err instanceof Error ? err.message : String(err)}`);
|
|
55
|
+
}
|
|
49
56
|
const player = createAudioPlayer();
|
|
50
57
|
player.on(AudioPlayerStatus.Idle, () => {
|
|
51
|
-
this.handleTrackEnd(guildId)
|
|
58
|
+
this.handleTrackEnd(guildId).catch((err) => {
|
|
59
|
+
console.error(`Error handling track end in ${guildId}:`, err);
|
|
60
|
+
});
|
|
52
61
|
});
|
|
53
62
|
player.on("error", (error) => {
|
|
54
63
|
console.error(`Audio player error in ${guildId}:`, error);
|
|
55
|
-
this.handleTrackEnd(guildId)
|
|
64
|
+
this.handleTrackEnd(guildId).catch((err) => {
|
|
65
|
+
console.error(`Error handling track end after player error in ${guildId}:`, err);
|
|
66
|
+
});
|
|
56
67
|
});
|
|
57
68
|
connection.subscribe(player);
|
|
58
69
|
this.guildStates.set(guildId, {
|
|
@@ -89,18 +100,45 @@ var VoiceManager = class {
|
|
|
89
100
|
if (!state) {
|
|
90
101
|
throw new Error("Not connected to voice in this guild");
|
|
91
102
|
}
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
const hasFilters = state.filters.size > 0;
|
|
104
|
+
const hasSeek = options.seek && options.seek > 0;
|
|
105
|
+
let resource;
|
|
106
|
+
if (hasFilters || hasSeek) {
|
|
107
|
+
const ffmpegArgs = [
|
|
108
|
+
"-analyzeduration",
|
|
109
|
+
"0",
|
|
110
|
+
"-loglevel",
|
|
111
|
+
"0",
|
|
112
|
+
"-i",
|
|
113
|
+
source
|
|
114
|
+
];
|
|
115
|
+
if (hasSeek) {
|
|
116
|
+
const inputIndex = ffmpegArgs.indexOf("-i");
|
|
117
|
+
ffmpegArgs.splice(inputIndex, 0, "-ss", String(options.seek / 1e3));
|
|
118
|
+
}
|
|
119
|
+
if (hasFilters) {
|
|
120
|
+
const filterArgs = this.buildFilterArgs(state.filters);
|
|
121
|
+
ffmpegArgs.push(...filterArgs);
|
|
122
|
+
}
|
|
123
|
+
ffmpegArgs.push(
|
|
124
|
+
"-f",
|
|
125
|
+
"s16le",
|
|
126
|
+
"-ar",
|
|
127
|
+
"48000",
|
|
128
|
+
"-ac",
|
|
129
|
+
"2",
|
|
130
|
+
"pipe:1"
|
|
131
|
+
);
|
|
132
|
+
const ffmpegStream = new FFmpeg({ args: ffmpegArgs });
|
|
133
|
+
resource = createAudioResource(ffmpegStream, {
|
|
134
|
+
inputType: StreamType.Raw,
|
|
135
|
+
inlineVolume: true
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
resource = createAudioResource(source, {
|
|
139
|
+
inlineVolume: true
|
|
140
|
+
});
|
|
99
141
|
}
|
|
100
|
-
const resource = createAudioResource(source, {
|
|
101
|
-
inlineVolume: true,
|
|
102
|
-
inputType: ffmpegArgs.length > 2 ? void 0 : void 0
|
|
103
|
-
});
|
|
104
142
|
const volume = options.volume ?? state.volume;
|
|
105
143
|
resource.volume?.setVolume(volume / 100);
|
|
106
144
|
state.currentResource = resource;
|
package/dist/voice/index.js.map
CHANGED
|
@@ -1 +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 currentResource: AudioResource | null;\n volume: number;\n loopMode: QueueLoopMode;\n filters: Set<AudioFilter>;\n paused: boolean;\n startTime: number;\n pausedAt: number;\n}\n\n/** FFmpeg filter definitions */\nconst FILTER_ARGS: Record<AudioFilter, string[]> = {\n bassboost: ['-af', 'bass=g=10,dynaudnorm=f=200'],\n nightcore: ['-af', 'asetrate=44100*1.25,aresample=44100,atempo=1.06'],\n vaporwave: ['-af', 'asetrate=44100*0.8,aresample=44100,atempo=0.9'],\n '8d': ['-af', 'apulsator=hz=0.08'],\n treble: ['-af', 'treble=g=5,dynaudnorm=f=200'],\n normalizer: ['-af', 'dynaudnorm=f=200:g=3'],\n karaoke: ['-af', 'stereotools=mlev=0.03'],\n tremolo: ['-af', 'tremolo=f=6:d=0.5'],\n vibrato: ['-af', 'vibrato=f=6:d=0.5'],\n reverse: ['-af', 'areverse'],\n};\n\n/** Search result from voice_search */\nexport interface SearchResult {\n url: string;\n title: string;\n duration: number;\n thumbnail: string | null;\n author?: string;\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 currentResource: null,\n volume: this.config.default_volume ?? 100,\n loopMode: this.config.default_loop ?? 'off',\n filters: new Set(),\n paused: false,\n startTime: 0,\n pausedAt: 0,\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 // Build FFmpeg args for filters and seeking\n const ffmpegArgs: string[] = ['-analyzeduration', '0'];\n\n // Add seek if specified\n if (options.seek && options.seek > 0) {\n ffmpegArgs.push('-ss', String(options.seek / 1000)); // Convert ms to seconds\n }\n\n // Add filters if any are active\n if (state.filters.size > 0) {\n const filterArgs = this.buildFilterArgs(state.filters);\n ffmpegArgs.push(...filterArgs);\n }\n\n // Create audio resource with FFmpeg args if needed\n const resource = createAudioResource(source, {\n inlineVolume: true,\n inputType: ffmpegArgs.length > 2 ? undefined : undefined,\n });\n\n // Set volume\n const volume = options.volume ?? state.volume;\n resource.volume?.setVolume(volume / 100);\n\n // Track playback state\n state.currentResource = resource;\n state.startTime = Date.now() - (options.seek ?? 0);\n state.pausedAt = 0;\n\n // Play\n state.player.play(resource);\n state.paused = false;\n }\n\n /**\n * Build FFmpeg filter arguments from active filters\n */\n private buildFilterArgs(filters: Set<AudioFilter>): string[] {\n if (filters.size === 0) return [];\n\n const filterStrings: string[] = [];\n for (const filter of filters) {\n const args = FILTER_ARGS[filter];\n if (args) {\n // Extract the filter string from -af argument\n const afIndex = args.indexOf('-af');\n if (afIndex !== -1 && args[afIndex + 1]) {\n filterStrings.push(args[afIndex + 1]);\n }\n }\n }\n\n if (filterStrings.length === 0) return [];\n return ['-af', filterStrings.join(',')];\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 state.pausedAt = Date.now();\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 // Adjust start time to account for pause duration\n if (state.pausedAt > 0) {\n const pauseDuration = Date.now() - state.pausedAt;\n state.startTime += pauseDuration;\n }\n\n state.player.unpause();\n state.paused = false;\n state.pausedAt = 0;\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 * Seek to a position in the current track\n */\n async seek(guildId: string, position: number): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state || !state.currentTrack) return false;\n\n // Replay the track from the specified position\n await this.play(guildId, state.currentTrack.url, {\n seek: position,\n volume: state.volume,\n });\n\n return true;\n }\n\n /**\n * Get current playback position in milliseconds\n */\n getPlaybackPosition(guildId: string): number {\n const state = this.guildStates.get(guildId);\n if (!state) return 0;\n\n if (state.paused && state.pausedAt > 0) {\n return state.pausedAt - state.startTime;\n }\n\n if (state.startTime > 0) {\n return Date.now() - state.startTime;\n }\n\n return 0;\n }\n\n /**\n * Set an audio filter\n */\n async setFilter(guildId: string, filter: AudioFilter, enabled: boolean): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n const hadFilter = state.filters.has(filter);\n\n if (enabled) {\n state.filters.add(filter);\n } else {\n state.filters.delete(filter);\n }\n\n // Only restart playback if filter state changed and something is playing\n if (hadFilter !== enabled && state.currentTrack) {\n const currentPosition = this.getPlaybackPosition(guildId);\n await this.play(guildId, state.currentTrack.url, {\n seek: currentPosition,\n volume: state.volume,\n });\n }\n\n return true;\n }\n\n /**\n * Get active filters\n */\n getFilters(guildId: string): AudioFilter[] {\n const state = this.guildStates.get(guildId);\n if (!state) return [];\n return [...state.filters];\n }\n\n /**\n * Clear all filters\n */\n async clearFilters(guildId: string): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n if (state.filters.size === 0) return true;\n\n state.filters.clear();\n\n // Restart playback without filters if something is playing\n if (state.currentTrack) {\n const currentPosition = this.getPlaybackPosition(guildId);\n await this.play(guildId, state.currentTrack.url, {\n seek: currentPosition,\n volume: state.volume,\n });\n }\n\n return true;\n }\n\n /**\n * Search for music tracks\n * This provides a basic implementation that handles URLs directly\n * For full search functionality, integrate with youtube-sr, play-dl, or similar\n */\n async search(query: string, options: { limit?: number; source?: string } = {}): Promise<SearchResult[]> {\n const limit = options.limit ?? 5;\n\n // If it's already a URL, return it directly as a result\n if (this.isUrl(query)) {\n return [{\n url: query,\n title: this.extractTitleFromUrl(query),\n duration: 0,\n thumbnail: null,\n }];\n }\n\n // Try to dynamically import play-dl for search functionality\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const playDl = await import(/* webpackIgnore: true */ 'play-dl').catch(() => null);\n if (playDl) {\n const results = await playDl.search(query, { limit, source: { youtube: 'video' } });\n return results.map((result: any) => ({\n url: result.url,\n title: result.title ?? query,\n duration: result.durationInSec ? result.durationInSec * 1000 : 0,\n thumbnail: result.thumbnails?.[0]?.url ?? null,\n author: result.channel?.name,\n }));\n }\n } catch {\n // play-dl not available\n }\n\n // Try youtube-sr as fallback\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const ytsr = await import(/* webpackIgnore: true */ 'youtube-sr').catch(() => null);\n if (ytsr && ytsr.default) {\n const results = await ytsr.default.search(query, { limit, type: 'video' });\n return results.map((result: any) => ({\n url: result.url,\n title: result.title ?? query,\n duration: result.duration ?? 0,\n thumbnail: result.thumbnail?.url ?? null,\n author: result.channel?.name,\n }));\n }\n } catch {\n // youtube-sr not available\n }\n\n // Fallback: return a search URL that can be resolved later\n return [{\n url: `ytsearch:${query}`,\n title: query,\n duration: 0,\n thumbnail: null,\n }];\n }\n\n /**\n * Check if a string is a URL\n */\n private isUrl(str: string): boolean {\n try {\n const url = new URL(str);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n }\n\n /**\n * Extract a title from a URL\n */\n private extractTitleFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n // Try to get video ID from YouTube URLs\n if (parsed.hostname.includes('youtube.com') || parsed.hostname.includes('youtu.be')) {\n const videoId = parsed.searchParams.get('v') || parsed.pathname.slice(1);\n return `YouTube Video (${videoId})`;\n }\n // For other URLs, use the pathname\n const parts = parsed.pathname.split('/');\n const filename = parts[parts.length - 1];\n return filename || parsed.hostname;\n } catch {\n return url;\n }\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":";AAIA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAuDP,IAAM,cAA6C;AAAA,EACjD,WAAW,CAAC,OAAO,4BAA4B;AAAA,EAC/C,WAAW,CAAC,OAAO,iDAAiD;AAAA,EACpE,WAAW,CAAC,OAAO,+CAA+C;AAAA,EAClE,MAAM,CAAC,OAAO,mBAAmB;AAAA,EACjC,QAAQ,CAAC,OAAO,6BAA6B;AAAA,EAC7C,YAAY,CAAC,OAAO,sBAAsB;AAAA,EAC1C,SAAS,CAAC,OAAO,uBAAuB;AAAA,EACxC,SAAS,CAAC,OAAO,mBAAmB;AAAA,EACpC,SAAS,CAAC,OAAO,mBAAmB;AAAA,EACpC,SAAS,CAAC,OAAO,UAAU;AAC7B;AAWO,IAAM,eAAN,MAAmB;AAAA,EAChB,cAA4C,oBAAI,IAAI;AAAA,EACpD,SAAsB,CAAC;AAAA;AAAA;AAAA;AAAA,EAK/B,UAAU,QAA2B;AACnC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KACJ,SACA,UAAsD,CAAC,GAC7B;AAC1B,UAAM,UAAU,QAAQ,MAAM;AAG9B,UAAM,WAAW,mBAAmB,OAAO;AAC3C,QAAI,UAAU;AACZ,eAAS,QAAQ;AAAA,IACnB;AAEA,UAAM,aAAa,iBAAiB;AAAA,MAClC,WAAW,QAAQ;AAAA,MACnB;AAAA,MACA,gBAAgB,QAAQ,MAAM;AAAA,MAC9B,UAAU,QAAQ,YAAY,KAAK,OAAO,YAAY,aAAa;AAAA,MACnE,UAAU,QAAQ,YAAY,KAAK,OAAO,YAAY,aAAa;AAAA,IACrE,CAAC;AAGD,UAAM,YAAY,YAAY,sBAAsB,OAAO,GAAK;AAGhE,UAAM,SAAS,kBAAkB;AAGjC,WAAO,GAAG,kBAAkB,MAAM,MAAM;AACtC,WAAK,eAAe,OAAO;AAAA,IAC7B,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,UAAU;AAC5B,cAAQ,MAAM,yBAAyB,OAAO,KAAK,KAAK;AACxD,WAAK,eAAe,OAAO;AAAA,IAC7B,CAAC;AAGD,eAAW,UAAU,MAAM;AAG3B,SAAK,YAAY,IAAI,SAAS;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,OAAO,CAAC;AAAA,MACR,cAAc;AAAA,MACd,iBAAiB;AAAA,MACjB,QAAQ,KAAK,OAAO,kBAAkB;AAAA,MACtC,UAAU,KAAK,OAAO,gBAAgB;AAAA,MACtC,SAAS,oBAAI,IAAI;AAAA,MACjB,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,UAAU;AAAA,IACZ,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAA0B;AAC9B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,KAAK;AAClB,UAAM,WAAW,QAAQ;AACzB,SAAK,YAAY,OAAO,OAAO;AAE/B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KACJ,SACA,QACA,UAA8C,CAAC,GAChC;AACf,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAGA,UAAM,aAAuB,CAAC,oBAAoB,GAAG;AAGrD,QAAI,QAAQ,QAAQ,QAAQ,OAAO,GAAG;AACpC,iBAAW,KAAK,OAAO,OAAO,QAAQ,OAAO,GAAI,CAAC;AAAA,IACpD;AAGA,QAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,YAAM,aAAa,KAAK,gBAAgB,MAAM,OAAO;AACrD,iBAAW,KAAK,GAAG,UAAU;AAAA,IAC/B;AAGA,UAAM,WAAW,oBAAoB,QAAQ;AAAA,MAC3C,cAAc;AAAA,MACd,WAAW,WAAW,SAAS,IAAI,SAAY;AAAA,IACjD,CAAC;AAGD,UAAM,SAAS,QAAQ,UAAU,MAAM;AACvC,aAAS,QAAQ,UAAU,SAAS,GAAG;AAGvC,UAAM,kBAAkB;AACxB,UAAM,YAAY,KAAK,IAAI,KAAK,QAAQ,QAAQ;AAChD,UAAM,WAAW;AAGjB,UAAM,OAAO,KAAK,QAAQ;AAC1B,UAAM,SAAS;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,SAAqC;AAC3D,QAAI,QAAQ,SAAS,EAAG,QAAO,CAAC;AAEhC,UAAM,gBAA0B,CAAC;AACjC,eAAW,UAAU,SAAS;AAC5B,YAAM,OAAO,YAAY,MAAM;AAC/B,UAAI,MAAM;AAER,cAAM,UAAU,KAAK,QAAQ,KAAK;AAClC,YAAI,YAAY,MAAM,KAAK,UAAU,CAAC,GAAG;AACvC,wBAAc,KAAK,KAAK,UAAU,CAAC,CAAC;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,cAAc,WAAW,EAAG,QAAO,CAAC;AACxC,WAAO,CAAC,OAAO,cAAc,KAAK,GAAG,CAAC;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,WACE,SACA,MACA,UACQ;AACR,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAGA,UAAM,UAAU,KAAK,OAAO,kBAAkB;AAC9C,QAAI,MAAM,MAAM,UAAU,SAAS;AACjC,YAAM,IAAI,MAAM,sBAAsB,OAAO,UAAU;AAAA,IACzD;AAEA,QAAI,aAAa,UAAU,aAAa,GAAG;AACzC,YAAM,MAAM,QAAQ,IAAI;AACxB,aAAO;AAAA,IACT,WAAW,aAAa,UAAU,aAAa,QAAW;AACxD,YAAM,MAAM,KAAK,IAAI;AACrB,aAAO,MAAM,MAAM,SAAS;AAAA,IAC9B,WAAW,OAAO,aAAa,UAAU;AACvC,YAAM,MAAM,OAAO,UAAU,GAAG,IAAI;AACpC,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,WAAO,MAAM,MAAM,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,SAAiB,UAAoC;AACnE,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,CAAC,OAAO,IAAI,MAAM,MAAM,OAAO,UAAU,CAAC;AAChD,WAAO,WAAW;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAyB;AAClC,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,QAAQ,MAAM,MAAM;AAC1B,UAAM,QAAQ,CAAC;AACf,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,SAAuB;AAClC,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO;AAEZ,aAAS,IAAI,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,KAAK;AAC/C,YAAM,IAAI,KAAK,MAAM,KAAK,OAAO,KAAK,IAAI,EAAE;AAC5C,OAAC,MAAM,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,GAAI,MAAM,MAAM,CAAC,CAAE;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,SAA0B;AAC7B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,KAAK;AAClB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAA0B;AAC9B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,MAAM;AACnB,UAAM,SAAS;AACf,UAAM,WAAW,KAAK,IAAI;AAC1B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,SAA0B;AAC/B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAGnB,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,gBAAgB,KAAK,IAAI,IAAI,MAAM;AACzC,YAAM,aAAa;AAAA,IACrB;AAEA,UAAM,OAAO,QAAQ;AACrB,UAAM,SAAS;AACf,UAAM,WAAW;AACjB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,SAA0B;AAC7B,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,OAAO,KAAK;AAClB,UAAM,QAAQ,CAAC;AACf,UAAM,eAAe;AACrB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,SAAiB,QAAyB;AAClD,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC;AAChD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAiB,MAA2B;AACtD,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO;AAEZ,UAAM,WAAW;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,SAAiB,UAAoC;AAC9D,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,SAAS,CAAC,MAAM,aAAc,QAAO;AAG1C,UAAM,KAAK,KAAK,SAAS,MAAM,aAAa,KAAK;AAAA,MAC/C,MAAM;AAAA,MACN,QAAQ,MAAM;AAAA,IAChB,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,SAAyB;AAC3C,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,QAAI,MAAM,UAAU,MAAM,WAAW,GAAG;AACtC,aAAO,MAAM,WAAW,MAAM;AAAA,IAChC;AAEA,QAAI,MAAM,YAAY,GAAG;AACvB,aAAO,KAAK,IAAI,IAAI,MAAM;AAAA,IAC5B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,SAAiB,QAAqB,SAAoC;AACxF,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,YAAY,MAAM,QAAQ,IAAI,MAAM;AAE1C,QAAI,SAAS;AACX,YAAM,QAAQ,IAAI,MAAM;AAAA,IAC1B,OAAO;AACL,YAAM,QAAQ,OAAO,MAAM;AAAA,IAC7B;AAGA,QAAI,cAAc,WAAW,MAAM,cAAc;AAC/C,YAAM,kBAAkB,KAAK,oBAAoB,OAAO;AACxD,YAAM,KAAK,KAAK,SAAS,MAAM,aAAa,KAAK;AAAA,QAC/C,MAAM;AAAA,QACN,QAAQ,MAAM;AAAA,MAChB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAgC;AACzC,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO,CAAC;AACpB,WAAO,CAAC,GAAG,MAAM,OAAO;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,SAAmC;AACpD,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO,QAAO;AAEnB,QAAI,MAAM,QAAQ,SAAS,EAAG,QAAO;AAErC,UAAM,QAAQ,MAAM;AAGpB,QAAI,MAAM,cAAc;AACtB,YAAM,kBAAkB,KAAK,oBAAoB,OAAO;AACxD,YAAM,KAAK,KAAK,SAAS,MAAM,aAAa,KAAK;AAAA,QAC/C,MAAM;AAAA,QACN,QAAQ,MAAM;AAAA,MAChB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO,OAAe,UAA+C,CAAC,GAA4B;AACtG,UAAM,QAAQ,QAAQ,SAAS;AAG/B,QAAI,KAAK,MAAM,KAAK,GAAG;AACrB,aAAO,CAAC;AAAA,QACN,KAAK;AAAA,QACL,OAAO,KAAK,oBAAoB,KAAK;AAAA,QACrC,UAAU;AAAA,QACV,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAGA,QAAI;AAEF,YAAM,SAAS,MAAM;AAAA;AAAA,QAAiC;AAAA,MAAS,EAAE,MAAM,MAAM,IAAI;AACjF,UAAI,QAAQ;AACV,cAAM,UAAU,MAAM,OAAO,OAAO,OAAO,EAAE,OAAO,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAC;AAClF,eAAO,QAAQ,IAAI,CAAC,YAAiB;AAAA,UACnC,KAAK,OAAO;AAAA,UACZ,OAAO,OAAO,SAAS;AAAA,UACvB,UAAU,OAAO,gBAAgB,OAAO,gBAAgB,MAAO;AAAA,UAC/D,WAAW,OAAO,aAAa,CAAC,GAAG,OAAO;AAAA,UAC1C,QAAQ,OAAO,SAAS;AAAA,QAC1B,EAAE;AAAA,MACJ;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,QAAI;AAEF,YAAM,OAAO,MAAM;AAAA;AAAA,QAAiC;AAAA,MAAY,EAAE,MAAM,MAAM,IAAI;AAClF,UAAI,QAAQ,KAAK,SAAS;AACxB,cAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO,EAAE,OAAO,MAAM,QAAQ,CAAC;AACzE,eAAO,QAAQ,IAAI,CAAC,YAAiB;AAAA,UACnC,KAAK,OAAO;AAAA,UACZ,OAAO,OAAO,SAAS;AAAA,UACvB,UAAU,OAAO,YAAY;AAAA,UAC7B,WAAW,OAAO,WAAW,OAAO;AAAA,UACpC,QAAQ,OAAO,SAAS;AAAA,QAC1B,EAAE;AAAA,MACJ;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,WAAO,CAAC;AAAA,MACN,KAAK,YAAY,KAAK;AAAA,MACtB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,MAAM,KAAsB;AAClC,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,GAAG;AACvB,aAAO,IAAI,aAAa,WAAW,IAAI,aAAa;AAAA,IACtD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,KAAqB;AAC/C,QAAI;AACF,YAAM,SAAS,IAAI,IAAI,GAAG;AAE1B,UAAI,OAAO,SAAS,SAAS,aAAa,KAAK,OAAO,SAAS,SAAS,UAAU,GAAG;AACnF,cAAM,UAAU,OAAO,aAAa,IAAI,GAAG,KAAK,OAAO,SAAS,MAAM,CAAC;AACvE,eAAO,kBAAkB,OAAO;AAAA,MAClC;AAEA,YAAM,QAAQ,OAAO,SAAS,MAAM,GAAG;AACvC,YAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AACvC,aAAO,YAAY,OAAO;AAAA,IAC5B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,SAA8C;AACrD,WAAO,KAAK,YAAY,IAAI,OAAO;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAA0B;AACpC,WAAO,KAAK,YAAY,IAAI,OAAO;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,SAA8B;AACrC,WAAO,KAAK,YAAY,IAAI,OAAO,GAAG,SAAS,CAAC;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,SAAmC;AACjD,WAAO,KAAK,YAAY,IAAI,OAAO,GAAG,gBAAgB;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAe,SAAgC;AAC3D,UAAM,QAAQ,KAAK,YAAY,IAAI,OAAO;AAC1C,QAAI,CAAC,MAAO;AAGZ,QAAI,MAAM,aAAa,WAAW,MAAM,cAAc;AACpD,YAAM,KAAK,KAAK,SAAS,MAAM,aAAa,GAAG;AAC/C;AAAA,IACF;AAEA,QAAI,MAAM,aAAa,WAAW,MAAM,cAAc;AACpD,YAAM,MAAM,KAAK,MAAM,YAAY;AAAA,IACrC;AAGA,UAAM,OAAO,MAAM,MAAM,MAAM;AAC/B,QAAI,MAAM;AACR,YAAM,eAAe;AACrB,YAAM,KAAK,KAAK,SAAS,KAAK,GAAG;AAAA,IACnC,OAAO;AACL,YAAM,eAAe;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAsB;AACpB,eAAW,CAAC,OAAO,KAAK,KAAK,aAAa;AACxC,WAAK,MAAM,OAAO;AAAA,IACpB;AAAA,EACF;AACF;AAKO,SAAS,qBAAmC;AACjD,SAAO,IAAI,aAAa;AAC1B;","names":[]}
|
|
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 StreamType,\n type VoiceConnection,\n type AudioPlayer,\n type AudioResource,\n type DiscordGatewayAdapterCreator,\n} from '@discordjs/voice';\nimport { FFmpeg } from 'prism-media';\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 currentResource: AudioResource | null;\n volume: number;\n loopMode: QueueLoopMode;\n filters: Set<AudioFilter>;\n paused: boolean;\n startTime: number;\n pausedAt: number;\n}\n\n/** FFmpeg filter definitions */\nconst FILTER_ARGS: Record<AudioFilter, string[]> = {\n bassboost: ['-af', 'bass=g=10,dynaudnorm=f=200'],\n nightcore: ['-af', 'asetrate=44100*1.25,aresample=44100,atempo=1.06'],\n vaporwave: ['-af', 'asetrate=44100*0.8,aresample=44100,atempo=0.9'],\n '8d': ['-af', 'apulsator=hz=0.08'],\n treble: ['-af', 'treble=g=5,dynaudnorm=f=200'],\n normalizer: ['-af', 'dynaudnorm=f=200:g=3'],\n karaoke: ['-af', 'stereotools=mlev=0.03'],\n tremolo: ['-af', 'tremolo=f=6:d=0.5'],\n vibrato: ['-af', 'vibrato=f=6:d=0.5'],\n reverse: ['-af', 'areverse'],\n};\n\n/** Search result from voice_search */\nexport interface SearchResult {\n url: string;\n title: string;\n duration: number;\n thumbnail: string | null;\n author?: string;\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 with error handling\n try {\n await entersState(connection, VoiceConnectionStatus.Ready, 30000);\n } catch (err) {\n // Clean up failed connection\n connection.destroy();\n throw new Error(`Failed to connect to voice channel: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n // Create audio player\n const player = createAudioPlayer();\n\n // Set up player events with proper error handling\n player.on(AudioPlayerStatus.Idle, () => {\n this.handleTrackEnd(guildId).catch((err) => {\n console.error(`Error handling track end in ${guildId}:`, err);\n });\n });\n\n player.on('error', (error) => {\n console.error(`Audio player error in ${guildId}:`, error);\n this.handleTrackEnd(guildId).catch((err) => {\n console.error(`Error handling track end after player error in ${guildId}:`, err);\n });\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 currentResource: null,\n volume: this.config.default_volume ?? 100,\n loopMode: this.config.default_loop ?? 'off',\n filters: new Set(),\n paused: false,\n startTime: 0,\n pausedAt: 0,\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 const hasFilters = state.filters.size > 0;\n const hasSeek = options.seek && options.seek > 0;\n\n let resource: AudioResource;\n\n // If we have filters or seeking, use FFmpeg to process the stream\n if (hasFilters || hasSeek) {\n // Build FFmpeg args\n const ffmpegArgs: string[] = [\n '-analyzeduration', '0',\n '-loglevel', '0',\n '-i', source,\n ];\n\n // Add seek if specified (before input for faster seeking)\n if (hasSeek) {\n // Insert -ss before -i for input seeking\n const inputIndex = ffmpegArgs.indexOf('-i');\n ffmpegArgs.splice(inputIndex, 0, '-ss', String(options.seek! / 1000));\n }\n\n // Add filters if any are active\n if (hasFilters) {\n const filterArgs = this.buildFilterArgs(state.filters);\n ffmpegArgs.push(...filterArgs);\n }\n\n // Output format args for raw PCM that discord.js voice expects\n ffmpegArgs.push(\n '-f', 's16le',\n '-ar', '48000',\n '-ac', '2',\n 'pipe:1'\n );\n\n // Create FFmpeg stream with custom args\n const ffmpegStream = new FFmpeg({ args: ffmpegArgs });\n\n // Create audio resource from FFmpeg stream\n resource = createAudioResource(ffmpegStream, {\n inputType: StreamType.Raw,\n inlineVolume: true,\n });\n } else {\n // No filters or seeking - use simple resource creation\n resource = createAudioResource(source, {\n inlineVolume: true,\n });\n }\n\n // Set volume\n const volume = options.volume ?? state.volume;\n resource.volume?.setVolume(volume / 100);\n\n // Track playback state\n state.currentResource = resource;\n state.startTime = Date.now() - (options.seek ?? 0);\n state.pausedAt = 0;\n\n // Play\n state.player.play(resource);\n state.paused = false;\n }\n\n /**\n * Build FFmpeg filter arguments from active filters\n */\n private buildFilterArgs(filters: Set<AudioFilter>): string[] {\n if (filters.size === 0) return [];\n\n const filterStrings: string[] = [];\n for (const filter of filters) {\n const args = FILTER_ARGS[filter];\n if (args) {\n // Extract the filter string from -af argument\n const afIndex = args.indexOf('-af');\n if (afIndex !== -1 && args[afIndex + 1]) {\n filterStrings.push(args[afIndex + 1]);\n }\n }\n }\n\n if (filterStrings.length === 0) return [];\n return ['-af', filterStrings.join(',')];\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 state.pausedAt = Date.now();\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 // Adjust start time to account for pause duration\n if (state.pausedAt > 0) {\n const pauseDuration = Date.now() - state.pausedAt;\n state.startTime += pauseDuration;\n }\n\n state.player.unpause();\n state.paused = false;\n state.pausedAt = 0;\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 * Seek to a position in the current track\n */\n async seek(guildId: string, position: number): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state || !state.currentTrack) return false;\n\n // Replay the track from the specified position\n await this.play(guildId, state.currentTrack.url, {\n seek: position,\n volume: state.volume,\n });\n\n return true;\n }\n\n /**\n * Get current playback position in milliseconds\n */\n getPlaybackPosition(guildId: string): number {\n const state = this.guildStates.get(guildId);\n if (!state) return 0;\n\n if (state.paused && state.pausedAt > 0) {\n return state.pausedAt - state.startTime;\n }\n\n if (state.startTime > 0) {\n return Date.now() - state.startTime;\n }\n\n return 0;\n }\n\n /**\n * Set an audio filter\n */\n async setFilter(guildId: string, filter: AudioFilter, enabled: boolean): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n const hadFilter = state.filters.has(filter);\n\n if (enabled) {\n state.filters.add(filter);\n } else {\n state.filters.delete(filter);\n }\n\n // Only restart playback if filter state changed and something is playing\n if (hadFilter !== enabled && state.currentTrack) {\n const currentPosition = this.getPlaybackPosition(guildId);\n await this.play(guildId, state.currentTrack.url, {\n seek: currentPosition,\n volume: state.volume,\n });\n }\n\n return true;\n }\n\n /**\n * Get active filters\n */\n getFilters(guildId: string): AudioFilter[] {\n const state = this.guildStates.get(guildId);\n if (!state) return [];\n return [...state.filters];\n }\n\n /**\n * Clear all filters\n */\n async clearFilters(guildId: string): Promise<boolean> {\n const state = this.guildStates.get(guildId);\n if (!state) return false;\n\n if (state.filters.size === 0) return true;\n\n state.filters.clear();\n\n // Restart playback without filters if something is playing\n if (state.currentTrack) {\n const currentPosition = this.getPlaybackPosition(guildId);\n await this.play(guildId, state.currentTrack.url, {\n seek: currentPosition,\n volume: state.volume,\n });\n }\n\n return true;\n }\n\n /**\n * Search for music tracks\n * This provides a basic implementation that handles URLs directly\n * For full search functionality, integrate with youtube-sr, play-dl, or similar\n */\n async search(query: string, options: { limit?: number; source?: string } = {}): Promise<SearchResult[]> {\n const limit = options.limit ?? 5;\n\n // If it's already a URL, return it directly as a result\n if (this.isUrl(query)) {\n return [{\n url: query,\n title: this.extractTitleFromUrl(query),\n duration: 0,\n thumbnail: null,\n }];\n }\n\n // Try to dynamically import play-dl for search functionality\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const playDl = await import(/* webpackIgnore: true */ 'play-dl').catch(() => null);\n if (playDl) {\n const results = await playDl.search(query, { limit, source: { youtube: 'video' } });\n return results.map((result: any) => ({\n url: result.url,\n title: result.title ?? query,\n duration: result.durationInSec ? result.durationInSec * 1000 : 0,\n thumbnail: result.thumbnails?.[0]?.url ?? null,\n author: result.channel?.name,\n }));\n }\n } catch {\n // play-dl not available\n }\n\n // Try youtube-sr as fallback\n try {\n // @ts-ignore - Optional dependency, may not be installed\n const ytsr = await import(/* webpackIgnore: true */ 'youtube-sr').catch(() => null);\n if (ytsr && ytsr.default) {\n const results = await ytsr.default.search(query, { limit, type: 'video' });\n return results.map((result: any) => ({\n url: result.url,\n title: result.title ?? query,\n duration: result.duration ?? 0,\n thumbnail: result.thumbnail?.url ?? null,\n author: result.channel?.name,\n }));\n }\n } catch {\n // youtube-sr not available\n }\n\n // Fallback: return a search URL that can be resolved later\n return [{\n url: `ytsearch:${query}`,\n title: query,\n duration: 0,\n thumbnail: null,\n }];\n }\n\n /**\n * Check if a string is a URL\n */\n private isUrl(str: string): boolean {\n try {\n const url = new URL(str);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n }\n\n /**\n * Extract a title from a URL\n */\n private extractTitleFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n // Try to get video ID from YouTube URLs\n if (parsed.hostname.includes('youtube.com') || parsed.hostname.includes('youtu.be')) {\n const videoId = parsed.searchParams.get('v') || parsed.pathname.slice(1);\n return `YouTube Video (${videoId})`;\n }\n // For other URLs, use the pathname\n const parts = parsed.pathname.split('/');\n const filename = parts[parts.length - 1];\n return filename || parsed.hostname;\n } catch {\n return url;\n }\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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furlow/discord",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Discord.js adapter for FURLOW bot framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"furlow",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"play-dl": "^1.9.0",
|
|
55
55
|
"prism-media": "^1.3.5",
|
|
56
56
|
"youtube-sr": "^4.3.0",
|
|
57
|
-
"@furlow/core": "1.0.
|
|
58
|
-
"@furlow/schema": "1.0.
|
|
57
|
+
"@furlow/core": "1.0.3",
|
|
58
|
+
"@furlow/schema": "1.0.3"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/node": "^20.11.0",
|