8.6 KiB
8.6 KiB
Astro Bot Web Dashboard Architecture
Overview
A real-time web dashboard for controlling the Discord music bot from a browser. Users can manage queues, adjust settings, and control playback without Discord.
Tech Stack
Backend (Flask/FastAPI)
Recommended: FastAPI (modern, async, WebSocket support built-in)
# Dependencies
fastapi==0.104.1
uvicorn[standard]==0.24.0
websockets==12.0
python-socketio==5.10.0 # For real-time updates
aioredis==2.0.1 # For session management
Why FastAPI:
- Native async support (works well with discord.py)
- Built-in WebSocket support
- Auto-generated API docs
- Fast performance
Frontend
Recommended: React + Tailwind CSS
React 18
Tailwind CSS
Socket.IO client (for real-time)
Axios (for API calls)
Alternative (simpler): Vanilla JS + Tailwind if you want less complexity
Architecture Diagram
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Browser │◄───────►│ FastAPI │◄───────►│ Discord Bot │
│ (React UI) │ HTTP/WS │ Backend │ IPC │ (Python) │
└─────────────┘ └──────────────┘ └─────────────┘
│
▼
┌──────────────┐
│ Database │
│ (SQLite) │
└──────────────┘
Communication Flow
1. Bot → Web (Status Updates)
Discord bot sends real-time updates to web backend via:
- Shared Database (simplest) - Bot writes to DB, web reads
- Redis Pub/Sub (better) - Bot publishes events, web subscribes
- WebSocket/Socket.IO (best) - Direct real-time connection
2. Web → Bot (Commands)
Web backend sends commands to bot via:
- Database flags (simplest) - Web writes commands, bot polls
- Redis Queue (better) - Web publishes, bot consumes
- Direct IPC (best) - Web calls bot functions directly
Detailed Implementation
Phase 1: Database-Based (Easiest Start)
How it works:
- Bot writes current state to database
- Web reads database and displays
- Web writes commands to "commands" table
- Bot polls table every second
Pros:
- Simple to implement
- No new dependencies
- Works immediately
Cons:
- Not truly real-time (polling delay)
- Database writes on every update
Phase 2: Redis-Based (Production Ready)
How it works:
- Bot publishes events to Redis:
PUBLISH bot:status {"song": "...", "queue": [...]} - Web subscribes to Redis channel
- Web publishes commands:
PUBLISH bot:commands {"action": "skip"} - Bot subscribes and executes
Pros:
- True real-time
- Fast and efficient
- Decoupled architecture
Cons:
- Requires Redis server
- More complex setup
API Endpoints
GET Endpoints (Read)
GET /api/servers # List all servers bot is in
GET /api/servers/{id}/queue # Get current queue
GET /api/servers/{id}/status # Get playback status
GET /api/servers/{id}/settings # Get volume/loop/effect
POST Endpoints (Write)
POST /api/servers/{id}/play # Add song to queue
POST /api/servers/{id}/skip # Skip current song
POST /api/servers/{id}/volume # Set volume
POST /api/servers/{id}/loop # Set loop mode
POST /api/servers/{id}/effect # Set audio effect
POST /api/servers/{id}/shuffle # Shuffle queue
POST /api/servers/{id}/clear # Clear queue
WebSocket Events
ws://localhost:8000/ws/{server_id}
# Bot → Web
{"event": "song_changed", "data": {...}}
{"event": "queue_updated", "data": [...]}
{"event": "volume_changed", "data": 150}
# Web → Bot
{"action": "skip"}
{"action": "volume", "value": 120}
Example Code Structure
Backend (FastAPI)
# main.py
from fastapi import FastAPI, WebSocket
from fastapi.middleware.cors import CORSMiddleware
import asyncio
import sqlite3
app = FastAPI()
# Allow frontend to connect
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# WebSocket connection for real-time updates
@app.websocket("/ws/{server_id}")
async def websocket_endpoint(websocket: WebSocket, server_id: str):
await websocket.accept()
# Send updates every second
while True:
# Read from database
queue_data = get_queue_from_db(server_id)
await websocket.send_json(queue_data)
await asyncio.sleep(1)
# API endpoint to skip song
@app.post("/api/servers/{server_id}/skip")
async def skip_song(server_id: str):
# Write command to database
conn = sqlite3.connect("./data/music.db")
cursor = conn.cursor()
cursor.execute("INSERT INTO commands (server_id, action) VALUES (?, ?)",
(server_id, "skip"))
conn.commit()
conn.close()
return {"status": "ok"}
Bot Integration
# In your bot code, add command polling
@tasks.loop(seconds=1)
async def process_web_commands():
"""Check for commands from web dashboard"""
conn = sqlite3.connect("./data/music.db")
cursor = conn.cursor()
cursor.execute("SELECT * FROM commands WHERE processed = 0")
commands = cursor.fetchall()
for cmd in commands:
server_id, action, data = cmd[1], cmd[2], cmd[3]
# Execute command
if action == "skip":
guild = bot.get_guild(int(server_id))
if guild and guild.voice_client:
guild.voice_client.stop()
# Mark as processed
cursor.execute("UPDATE commands SET processed = 1 WHERE id = ?", (cmd[0],))
conn.commit()
conn.close()
Frontend (React)
// Dashboard.jsx
import { useEffect, useState } from 'react';
function Dashboard({ serverId }) {
const [queue, setQueue] = useState([]);
const [ws, setWs] = useState(null);
useEffect(() => {
// Connect to WebSocket
const websocket = new WebSocket(`ws://localhost:8000/ws/${serverId}`);
websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
setQueue(data.queue);
};
setWs(websocket);
return () => websocket.close();
}, [serverId]);
const skipSong = async () => {
await fetch(`http://localhost:8000/api/servers/${serverId}/skip`, {
method: 'POST',
});
};
return (
<div>
<h1>Now Playing: {queue[0]?.title}</h1>
<button onClick={skipSong}>Skip</button>
<ul>
{queue.map((song, i) => (
<li key={i}>{song.title}</li>
))}
</ul>
</div>
);
}
Authentication (Important!)
Problem: Anyone with the URL can control your bot.
Solutions:
-
Discord OAuth2 (Recommended)
- Users log in with Discord
- Check if user is in the server
- Only show servers they're members of
-
API Keys
- Generate unique key per server
- Server admins share key with trusted users
-
IP Whitelist
- Only allow specific IPs to access
Deployment
Development
# Backend
cd backend
uvicorn main:app --reload --port 8000
# Frontend
cd frontend
npm run dev
Production
# Backend (systemd service)
uvicorn main:app --host 0.0.0.0 --port 8000
# Frontend (build static files)
npm run build
# Serve with nginx or deploy to Vercel/Netlify
File Structure
astro-bot/
├── bot.py # Discord bot
├── cogs/
│ └── music/
│ ├── main.py
│ ├── queue.py
│ └── util.py
├── web/
│ ├── backend/
│ │ ├── main.py # FastAPI app
│ │ ├── routes/
│ │ │ ├── servers.py
│ │ │ └── playback.py
│ │ └── websockets.py
│ └── frontend/
│ ├── src/
│ │ ├── components/
│ │ │ ├── Queue.jsx
│ │ │ ├── Controls.jsx
│ │ │ └── NowPlaying.jsx
│ │ ├── App.jsx
│ │ └── main.jsx
│ └── package.json
└── data/
└── music.db
Next Steps
- Start with database-based approach - Get it working first
- Add WebSocket for real-time - Once basic functionality works
- Build simple UI - Focus on core features (play, queue, skip)
- Add authentication - Discord OAuth2
- Polish and deploy - Make it production-ready