diff --git a/app/bot.py b/app/bot.py index f355b73..7852563 100644 --- a/app/bot.py +++ b/app/bot.py @@ -1,31 +1,32 @@ import asyncio -from typing import Optional, Dict - -from fastapi import FastAPI, HTTPException -from pydantic import BaseModel +import logging import discord from discord.ext import commands +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from typing import Optional, Dict from NoiseGatev2 import NoiseGate import op25_controller import pulse +# Initialize logging +logging.basicConfig(level=logging.INFO) +LOGGER = logging.getLogger(__name__) + # Define FastAPI app app = FastAPI() -# Discord Bot Setup intents = discord.Intents.default() intents.voice_states = True intents.guilds = True -# Models for API requests class BotConfig(BaseModel): - token: str # Discord Bot Token + token: str class VoiceChannelRequest(BaseModel): guild_id: int channel_id: int -# Discord Bot Manager class DiscordBotManager: def __init__(self): self.bot: Optional[commands.Bot] = None @@ -37,7 +38,7 @@ class DiscordBotManager: async def start_bot(self, token: str): async with self.lock: - if self.bot and self.bot.is_closed(): + if self.bot and not self.bot.is_closed(): raise RuntimeError("Bot is already running.") if self.bot_task and not self.bot_task.done(): raise RuntimeError("Bot is already running.") @@ -47,17 +48,18 @@ class DiscordBotManager: @self.bot.event async def on_ready(): - print(f'Logged in as {self.bot.user}') + LOGGER.info(f'Logged in as {self.bot.user}') - # Handle graceful shutdown when all voice connections are closed @self.bot.event async def on_voice_state_update(member, before, after): - # Check if all voice clients are disconnected - await asyncio.sleep(1) # Give time for the state to update - if not self.voice_clients: - await self.stop_bot() + # Check if the bot was disconnected + if member == self.bot.user and after.channel is None: + guild_id = before.channel.guild.id + LOGGER.info(f"Bot was disconnected from channel in guild {guild_id}. Attempting to reconnect...") + # Attempt to reconnect to the channel after a brief pause + await asyncio.sleep(2) + await self.join_voice_channel(guild_id, before.channel.id) - # Start the bot in the background self.bot_task = self.loop.create_task(self.bot.start(token)) async def stop_bot(self): @@ -69,7 +71,7 @@ class DiscordBotManager: await self.bot_task self.bot_task = None self.voice_clients.clear() - print("Bot has been stopped.") + LOGGER.info("Bot has been stopped.") async def join_voice_channel(self, guild_id: int, channel_id: int, ng_threshold: int = 50, device_id: int = 4): if not self.bot: @@ -86,15 +88,17 @@ class DiscordBotManager: if guild_id in self.voice_clients: raise RuntimeError("Already connected to this guild's voice channel.") - voice_client = await channel.connect() - streamHandler = NoiseGate( + try: + voice_client = await channel.connect(timeout=60.0, reconnect=True) + streamHandler = NoiseGate( _input_device_index=device_id, _voice_connection=voice_client, _noise_gate_threshold=ng_threshold) - # Start the audio stream - streamHandler.run() - self.voice_clients[guild_id] = voice_client - print(f"Joined guild {guild_id} voice channel {channel_id}.") + streamHandler.run() + self.voice_clients[guild_id] = voice_client + LOGGER.info(f"Joined guild {guild_id} voice channel {channel_id}.") + except Exception as e: + LOGGER.error(f"Failed to connect to voice channel: {e}") async def leave_voice_channel(self, guild_id: int): if not self.bot: @@ -106,9 +110,8 @@ class DiscordBotManager: await voice_client.disconnect() del self.voice_clients[guild_id] - print(f"Left guild {guild_id} voice channel.") + LOGGER.info(f"Left guild {guild_id} voice channel.") -# Initialize Discord Bot Manager bot_manager = DiscordBotManager() # API Endpoints @@ -118,6 +121,7 @@ async def start_bot(config: BotConfig): await bot_manager.start_bot(config.token) return {"status": "Bot started successfully."} except Exception as e: + LOGGER.error(f"Error starting bot: {e}") raise HTTPException(status_code=400, detail=str(e)) @app.post("/stop_bot") @@ -126,6 +130,7 @@ async def stop_bot(): await bot_manager.stop_bot() return {"status": "Bot stopped successfully."} except Exception as e: + LOGGER.error(f"Error stopping bot: {e}") raise HTTPException(status_code=400, detail=str(e)) @app.post("/join_voice") @@ -134,6 +139,7 @@ async def join_voice_channel(request: VoiceChannelRequest): await bot_manager.join_voice_channel(request.guild_id, request.channel_id) return {"status": f"Joined guild {request.guild_id} voice channel {request.channel_id}."} except Exception as e: + LOGGER.error(f"Error joining voice channel: {e}") raise HTTPException(status_code=400, detail=str(e)) @app.post("/leave_voice") @@ -142,6 +148,7 @@ async def leave_voice_channel(request: VoiceChannelRequest): await bot_manager.leave_voice_channel(request.guild_id) return {"status": f"Left guild {request.guild_id} voice channel."} except Exception as e: + LOGGER.error(f"Error leaving voice channel: {e}") raise HTTPException(status_code=400, detail=str(e)) @app.get("/status") @@ -152,6 +159,5 @@ async def get_status(): } return status - app.include_router(op25_controller.router, prefix="/op25") app.include_router(pulse.router, prefix="/pulse") \ No newline at end of file