diff --git a/app.py b/app.py index bdaec2f..a881925 100644 --- a/app.py +++ b/app.py @@ -79,6 +79,7 @@ intents = discord.Intents.default() intents.message_content = True intents.guilds = True intents.members = True +intents.voice_states = True bot = commands.Bot(command_prefix='!', intents=intents) @@ -186,7 +187,7 @@ async def ensure_voice(ctx: commands.Context) -> Optional[discord.VoiceClient]: return None if ctx.voice_client is None: try: - return await channel.connect() + return await channel.connect(timeout=30, reconnect=True, self_deaf=True) except Exception as e: await ctx.reply(f"❌ Failed to join: {e}") return None @@ -321,6 +322,39 @@ async def cmd_queue(ctx): embed = discord.Embed(title="🎼 Queue", description=desc, color=discord.Color.purple()) await ctx.reply(embed=embed) +@bot.hybrid_command(name='voicediag', description='Diagnose voice playback environment') +async def cmd_voicediag(ctx): + details = [] + details.append(f"discord.py: {discord.__version__}") + # PyNaCl check + try: + import nacl + details.append(f"PyNaCl: {getattr(nacl, '__version__', 'present')}") + except Exception as e: + details.append(f"PyNaCl: missing ({e})") + # Opus check + try: + loaded = discord.opus.is_loaded() + details.append(f"Opus loaded: {loaded}") + except Exception as e: + details.append(f"Opus check failed: {e}") + # FFmpeg check + ff = "unknown" + try: + proc = await asyncio.create_subprocess_exec('ffmpeg', '-version', stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) + out, _ = await proc.communicate() + ff = out.decode(errors='ignore').splitlines()[0][:120] + except FileNotFoundError: + ff = "not found" + except Exception as e: + ff = f"error: {e}" + details.append(f"ffmpeg: {ff}") + # Cookie file + details.append(f"cookie.txt present: {os.path.exists(COOKIE_FILE)} at {COOKIE_FILE}") + + embed = discord.Embed(title='Voice Diagnostics', description='\n'.join(details), color=discord.Color.dark_grey()) + await ctx.reply(embed=embed) + @bot.event async def on_guild_join(guild): """Event triggered when the bot joins a server""" @@ -1564,14 +1598,14 @@ async def hoi4leaderboard(ctx, game_type: Optional[str] = "standard", limit: Opt async def on_command_error(ctx, error): """Handles command errors""" if isinstance(error, commands.CheckFailure): - await ctx.send("❌ You don't have permission to use this command!") + await _safe_send(ctx, "❌ You don't have permission to use this command!") elif isinstance(error, commands.CommandNotFound): # Silently ignore command not found errors pass elif isinstance(error, commands.MissingRequiredArgument): - await ctx.send(f"❌ Missing arguments! Command: `{ctx.command}`") + await _safe_send(ctx, f"❌ Missing arguments! Command: `{ctx.command}`") elif isinstance(error, commands.BadArgument): - await ctx.send("❌ Invalid argument!") + await _safe_send(ctx, "❌ Invalid argument!") else: # Log detailed error information print(f"❌ Unknown error in command '{ctx.command}': {type(error).__name__}: {error}") @@ -1580,9 +1614,34 @@ async def on_command_error(ctx, error): # Send detailed error to user if owner if ctx.author.id == OWNER_ID: - await ctx.send(f"❌ **Error Details (Owner only):**\n```python\n{type(error).__name__}: {str(error)[:1800]}\n```") + await _safe_send(ctx, f"❌ **Error Details (Owner only):**\n```python\n{type(error).__name__}: {str(error)[:1800]}\n```") else: - await ctx.send("❌ An unknown error occurred!") + await _safe_send(ctx, "❌ An unknown error occurred!") + +async def _safe_send(ctx: commands.Context, content: str = None, **kwargs): + """Send a message safely for both message and slash contexts, even if the interaction timed out.""" + try: + if getattr(ctx, 'interaction', None): + # If we have an interaction, try normal response first + interaction = ctx.interaction + if not interaction.response.is_done(): + await interaction.response.send_message(content=content, **kwargs) + return + # Otherwise use followup + await interaction.followup.send(content=content, **kwargs) + return + # Fallback to classic send + await ctx.send(content=content, **kwargs) + except discord.NotFound: + # Interaction unknown/expired, try channel.send + try: + channel = getattr(ctx, 'channel', None) + if channel: + await channel.send(content=content, **kwargs) + except Exception: + pass + except Exception: + pass async def main(): """Main function to start the bot"""