add logging and reconnection logic

This commit is contained in:
2025-02-22 02:52:02 -05:00
parent 818079775e
commit 0fb1a155b5

View File

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