diff options
| author | salmonik <raisersoft@proton.me> | 2026-03-18 15:29:51 +0100 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-03-18 15:29:51 +0100 |
| commit | c0053ca98912901b5d387d58fbc57f9b7da91f38 (patch) | |
| tree | 14fe7a7beae1ca2745333dcd7032a6ced28de0f7 /cogs/voice.py | |
| parent | fix tts (diff) | |
| download | senator-sperma-c0053ca98912901b5d387d58fbc57f9b7da91f38.tar.gz senator-sperma-c0053ca98912901b5d387d58fbc57f9b7da91f38.zip | |
voice doesn't work due to DAVE, but we got copyparty search
Diffstat (limited to 'cogs/voice.py')
| -rw-r--r-- | cogs/voice.py | 140 |
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 | ||
| 11 | logger = logging.getLogger(__name__) | 11 | logger = logging.getLogger(__name__) |
| 12 | 12 | ||
| 13 | _voice_manager = None | ||
| 14 | |||
| 15 | def 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 | |||
| 22 | class 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 | |||
| 13 | class Voice(commands.Cog): | 97 | class 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 | |||
| 90 | def setup(bot): | ||
| 91 | bot.add_cog(Voice(bot)) \ No newline at end of file | ||
