aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/voice.py
diff options
context:
space:
mode:
Diffstat (limited to 'cogs/voice.py')
-rw-r--r--cogs/voice.py140
1 files changed, 111 insertions, 29 deletions
diff --git a/cogs/voice.py b/cogs/voice.py
index dd5b74a..8ee3699 100644
--- a/cogs/voice.py
+++ b/cogs/voice.py
@@ -10,33 +10,117 @@ import asyncio
10 10
11logger = logging.getLogger(__name__) 11logger = logging.getLogger(__name__)
12 12
13_voice_manager = None
14
15def get_voice_manager(bot):
16 global _voice_manager
17 if _voice_manager is None:
18 _voice_manager = VoiceManager(bot)
19 return _voice_manager
20
21
22class VoiceManager:
23 def __init__(self, bot):
24 self.bot = bot
25 self._voice_lock = asyncio.Lock()
26
27 async def join_channel(self, channel):
28 """Join a voice channel by channel object."""
29 async with self._voice_lock:
30 guild = channel.guild
31 voice_client = discord.utils.get(self.bot.voice_clients, guild=guild)
32
33 if voice_client and voice_client.is_connected():
34 if voice_client.channel == channel:
35 logger.info(f"Already connected to {channel}")
36 return voice_client
37 try:
38 await voice_client.move_to(channel)
39 logger.info(f"Moved to voice channel: {channel}")
40 return voice_client
41 except Exception as e:
42 logger.error(f"Failed to move voice client: {e}")
43 try:
44 await voice_client.disconnect()
45 except Exception:
46 pass
47
48 await asyncio.sleep(0.3)
49 try:
50 voice_client = await channel.connect(timeout=30.0, reconnect=False)
51 logger.info(f"Connected to voice channel: {channel}")
52 return voice_client
53 except Exception as e:
54 logger.error(f"Failed to connect to voice channel: {e}")
55 return None
56
57 async def join_by_channel_id(self, channel_id):
58 """Join a voice channel by channel ID."""
59 channel = self.bot.get_channel(channel_id)
60 if not channel:
61 logger.error(f"Channel {channel_id} not found")
62 return None
63 if not isinstance(channel, discord.VoiceChannel):
64 logger.error(f"Channel {channel_id} is not a voice channel")
65 return None
66 return await self.join_channel(channel)
67
68 async def get_voice_client(self, guild):
69 """Get existing voice client for a guild."""
70 return discord.utils.get(self.bot.voice_clients, guild=guild)
71
72 async def play_audio(self, channel, audio_source, volume=1.0):
73 """Play audio in a voice channel."""
74 voice_client = await self.join_channel(channel)
75 if not voice_client:
76 return False
77
78 if volume != 1.0:
79 audio_source = discord.PCMVolumeTransformer(audio_source)
80 audio_source.volume = volume
81
82 voice_client.play(audio_source)
83
84 while voice_client.is_playing():
85 await asyncio.sleep(0.1)
86 return True
87
88 async def stop(self, guild):
89 """Stop playing and disconnect from voice."""
90 voice_client = await self.get_voice_client(guild)
91 if voice_client:
92 voice_client.stop()
93 await voice_client.disconnect()
94 logger.info(f"Stopped and disconnected from guild {guild}")
95
96
13class Voice(commands.Cog): 97class Voice(commands.Cog):
14 def __init__(self, bot): 98 def __init__(self, bot):
15 self.bot = bot 99 self.bot = bot
100 self._voice_lock = asyncio.Lock()
16 101
17 async def _validate_user_voice_state(self, ctx): 102 async def _validate_user_voice_state(self, ctx):
18 """Validate that user is in a voice channel."""
19 if not ctx.author.voice or not ctx.author.voice.channel: 103 if not ctx.author.voice or not ctx.author.voice.channel:
20 await ctx.send("You need to be in a voice channel first!") 104 await ctx.send("You need to be in a voice channel first!")
21 return False 105 return False
22 return True 106 return True
23 107
24 async def _create_voice_client(self, ctx): 108 async def _create_voice_client(self, ctx):
25 """Create a new voice client connection.""" 109 async with self._voice_lock:
26 try: 110 try:
27 voice_client = await ctx.author.voice.channel.connect(timeout=30.0, reconnect=True) 111 await asyncio.sleep(0.5)
28 logger.info(f"Connected to voice channel: {ctx.author.voice.channel}") 112 voice_client = await ctx.author.voice.channel.connect(timeout=30.0, reconnect=False)
29 return voice_client 113 logger.info(f"Connected to voice channel: {ctx.author.voice.channel}")
30 except asyncio.TimeoutError: 114 return voice_client
31 await ctx.send("Connection to voice channel timed out.") 115 except asyncio.TimeoutError:
32 return None 116 await ctx.send("Connection to voice channel timed out.")
33 except discord.ClientException as e: 117 return None
34 logger.error(f"Failed to connect to voice channel: {e}") 118 except discord.ClientException as e:
35 await ctx.send("Failed to connect to voice channel. Please try again.", ephemeral=True) 119 logger.error(f"Failed to connect to voice channel: {e}")
36 return None 120 await ctx.send("Failed to connect to voice channel. Please try again.", ephemeral=True)
121 return None
37 122
38 async def _move_voice_client(self, voice_client, ctx): 123 async def _move_voice_client(self, voice_client, ctx):
39 """Move existing voice client to user's channel."""
40 try: 124 try:
41 await voice_client.move_to(ctx.author.voice.channel) 125 await voice_client.move_to(ctx.author.voice.channel)
42 logger.info(f"Moved to voice channel: {ctx.author.voice.channel}") 126 logger.info(f"Moved to voice channel: {ctx.author.voice.channel}")
@@ -50,10 +134,9 @@ class Voice(commands.Cog):
50 return None 134 return None
51 135
52 async def _verify_connection(self, voice_client): 136 async def _verify_connection(self, voice_client):
53 """Verify and repair voice client connection if needed."""
54 if not voice_client.is_connected(): 137 if not voice_client.is_connected():
55 try: 138 try:
56 await voice_client.connect(reconnect=True, timeout=10.0) 139 await voice_client.connect(reconnect=False, timeout=10.0)
57 return voice_client 140 return voice_client
58 except Exception as e: 141 except Exception as e:
59 logger.error(f"Failed to reconnect to voice: {e}") 142 logger.error(f"Failed to reconnect to voice: {e}")
@@ -61,31 +144,30 @@ class Voice(commands.Cog):
61 return voice_client 144 return voice_client
62 145
63 async def ensure_voice_client(self, ctx): 146 async def ensure_voice_client(self, ctx):
64 """Ensure we have a valid voice client connection."""
65 if not await self._validate_user_voice_state(ctx): 147 if not await self._validate_user_voice_state(ctx):
66 return None 148 return None
67 149
68 voice_client = discord.utils.get(self.bot.voice_clients, guild=ctx.guild) 150 voice_client = discord.utils.get(self.bot.voice_clients, guild=ctx.guild)
69 151
70 # If no voice client exists, create one 152 if voice_client and not voice_client.is_connected():
153 try:
154 await voice_client.disconnect()
155 except Exception:
156 pass
157 voice_client = None
158
71 if not voice_client or not voice_client.is_connected(): 159 if not voice_client or not voice_client.is_connected():
160 if not await self._validate_user_voice_state(ctx):
161 return None
72 return await self._create_voice_client(ctx) 162 return await self._create_voice_client(ctx)
73 163
74 # If we're in a different channel, move to the user's channel
75 if voice_client.channel != ctx.author.voice.channel: 164 if voice_client.channel != ctx.author.voice.channel:
76 return await self._move_voice_client(voice_client, ctx) 165 return await self._move_voice_client(voice_client, ctx)
77 166
78 # Verify the connection is healthy
79 return await self._verify_connection(voice_client) 167 return await self._verify_connection(voice_client)
80 168
81 async def connect(self, ctx): 169 async def connect(self, ctx):
82 """Connect to the user's voice channel with improved connection handling."""
83 voice_client = await self.ensure_voice_client(ctx) 170 voice_client = await self.ensure_voice_client(ctx)
84 if not voice_client: 171 if not voice_client:
85 return None 172 return None
86 173 return voice_client \ No newline at end of file
87 return voice_client
88
89
90def setup(bot):
91 bot.add_cog(Voice(bot)) \ No newline at end of file