aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/voice.py
blob: dd5b74a4b2205ee19d0481dd4ca2558f66f52687 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# This file is part of Senator Sperma
# Licensed under the GPL-3.0 License
# Copyright (C) 2026 Salmonik @ salmonik@tuta.io
# https://codeberg.org/salmonik/senator-sperma

import discord
from discord.ext import commands
import logging
import asyncio

logger = logging.getLogger(__name__)

class Voice(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    async def _validate_user_voice_state(self, ctx):
        """Validate that user is in a voice channel."""
        if not ctx.author.voice or not ctx.author.voice.channel:
            await ctx.send("You need to be in a voice channel first!")
            return False
        return True

    async def _create_voice_client(self, ctx):
        """Create a new voice client connection."""
        try:
            voice_client = await ctx.author.voice.channel.connect(timeout=30.0, reconnect=True)
            logger.info(f"Connected to voice channel: {ctx.author.voice.channel}")
            return voice_client
        except asyncio.TimeoutError:
            await ctx.send("Connection to voice channel timed out.")
            return None
        except discord.ClientException as e:
            logger.error(f"Failed to connect to voice channel: {e}")
            await ctx.send("Failed to connect to voice channel. Please try again.", ephemeral=True)
            return None

    async def _move_voice_client(self, voice_client, ctx):
        """Move existing voice client to user's channel."""
        try:
            await voice_client.move_to(ctx.author.voice.channel)
            logger.info(f"Moved to voice channel: {ctx.author.voice.channel}")
            return voice_client
        except asyncio.TimeoutError:
            await ctx.send("Connection to voice channel timed out.")
            return None
        except discord.ClientException as e:
            logger.error(f"Failed to move to voice channel: {e}")
            await ctx.send("Failed to move to your voice channel. Please try again.", ephemeral=True)
            return None

    async def _verify_connection(self, voice_client):
        """Verify and repair voice client connection if needed."""
        if not voice_client.is_connected():
            try:
                await voice_client.connect(reconnect=True, timeout=10.0)
                return voice_client
            except Exception as e:
                logger.error(f"Failed to reconnect to voice: {e}")
                return None
        return voice_client

    async def ensure_voice_client(self, ctx):
        """Ensure we have a valid voice client connection."""
        if not await self._validate_user_voice_state(ctx):
            return None
            
        voice_client = discord.utils.get(self.bot.voice_clients, guild=ctx.guild)
        
        # If no voice client exists, create one
        if not voice_client or not voice_client.is_connected():
            return await self._create_voice_client(ctx)
        
        # If we're in a different channel, move to the user's channel
        if voice_client.channel != ctx.author.voice.channel:
            return await self._move_voice_client(voice_client, ctx)
        
        # Verify the connection is healthy
        return await self._verify_connection(voice_client)

    async def connect(self, ctx):
        """Connect to the user's voice channel with improved connection handling."""
        voice_client = await self.ensure_voice_client(ctx)
        if not voice_client:
            return None
            
        return voice_client


def setup(bot):
    bot.add_cog(Voice(bot))