Files
drb-client-discord-bot/app/bot.py
2025-01-25 02:31:27 -05:00

157 lines
5.3 KiB
Python

import asyncio
from typing import Optional, Dict
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import discord
from discord.ext import commands
from NoiseGatev2 import NoiseGate
import op25_controller
import pulse
# 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
class VoiceChannelRequest(BaseModel):
guild_id: int
channel_id: int
# Discord Bot Manager
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 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():
print(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()
# Start the bot in the background
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()
print("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.")
voice_client = await channel.connect()
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}.")
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]
print(f"Left guild {guild_id} voice channel.")
# Initialize Discord Bot Manager
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:
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:
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:
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:
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")