Files
drb-client-discord-bot/app/bot.py

167 lines
6.0 KiB
Python

import asyncio
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()
intents = discord.Intents.default()
intents.voice_states = True
intents.guilds = True
class BotConfig(BaseModel):
token: str
class VoiceChannelRequest(BaseModel):
guild_id: int
channel_id: int
class DiscordBotManager:
def __init__(self):
self.bot: Optional[commands.Bot] = None
self.bot_task: Optional[asyncio.Task] = None
self.voice_clients: Dict[int, discord.VoiceClient] = {}
self.token: Optional[str] = None
self.loop = asyncio.get_event_loop()
self.lock = asyncio.Lock()
async def start_bot(self, token: str):
async with self.lock:
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.")
self.token = token
self.bot = commands.Bot(command_prefix="!", intents=intents)
@self.bot.event
async def on_ready():
LOGGER.info(f'Logged in as {self.bot.user}')
@self.bot.event
async def on_voice_state_update(member, before, after):
# 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...")
try:
leave_voice_channel(guild_id)
except Exception as e:
LOGGER.warning(f"Error to leave voice channel", e)
# Attempt to reconnect to the channel after a brief pause
await asyncio.sleep(2)
await self.join_voice_channel(guild_id, before.channel.id)
self.bot_task = self.loop.create_task(self.bot.start(token))
async def stop_bot(self):
async with self.lock:
if self.bot:
await self.bot.close()
self.bot = None
if self.bot_task:
await self.bot_task
self.bot_task = None
self.voice_clients.clear()
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:
raise RuntimeError("Bot is not running.")
guild = self.bot.get_guild(guild_id)
if not guild:
raise ValueError("Guild not found.")
channel = guild.get_channel(channel_id)
if not isinstance(channel, discord.VoiceChannel):
raise ValueError("Channel is not a voice channel.")
if guild_id in self.voice_clients:
raise RuntimeError("Already connected to this guild's voice channel.")
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)
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:
raise RuntimeError("Bot is not running.")
voice_client = self.voice_clients.get(guild_id)
if not voice_client:
raise RuntimeError("Not connected to the specified guild's voice channel.")
await voice_client.disconnect()
del self.voice_clients[guild_id]
LOGGER.info(f"Left guild {guild_id} voice channel.")
bot_manager = DiscordBotManager()
# API Endpoints
@app.post("/start_bot")
async def start_bot(config: BotConfig):
try:
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")
async def stop_bot():
try:
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")
async def join_voice_channel(request: VoiceChannelRequest):
try:
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")
async def leave_voice_channel(request: VoiceChannelRequest):
try:
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")
async def get_status():
status = {
"bot_running": bot_manager.bot is not None and not bot_manager.bot.is_closed(),
"connected_guilds": list(bot_manager.voice_clients.keys())
}
return status
app.include_router(op25_controller.router, prefix="/op25")
app.include_router(pulse.router, prefix="/pulse")