fixed queue skip error, implemented more /commands.
This commit is contained in:
@@ -80,7 +80,8 @@ class music(commands.Cog):
|
|||||||
# HYBRID COMMAND - works as both =play and /play
|
# HYBRID COMMAND - works as both =play and /play
|
||||||
@commands.hybrid_command(
|
@commands.hybrid_command(
|
||||||
name="play",
|
name="play",
|
||||||
description="Queue a song to play")
|
description="Queue a song to play",
|
||||||
|
aliases=['p'])
|
||||||
@app_commands.describe(query="YouTube URL, Spotify link, or search query")
|
@app_commands.describe(query="YouTube URL, Spotify link, or search query")
|
||||||
async def play(self, ctx: Context, *, query: str):
|
async def play(self, ctx: Context, *, query: str):
|
||||||
"""Queues a song into the bot"""
|
"""Queues a song into the bot"""
|
||||||
@@ -187,7 +188,8 @@ class music(commands.Cog):
|
|||||||
|
|
||||||
@commands.hybrid_command(
|
@commands.hybrid_command(
|
||||||
name="queue",
|
name="queue",
|
||||||
description="Display the current music queue")
|
description="Display the current music queue",
|
||||||
|
aliases=['q', 'songs'])
|
||||||
async def queue_cmd(self, ctx: Context):
|
async def queue_cmd(self, ctx: Context):
|
||||||
"""Display the current music queue"""
|
"""Display the current music queue"""
|
||||||
server = ctx.guild
|
server = ctx.guild
|
||||||
@@ -211,7 +213,8 @@ class music(commands.Cog):
|
|||||||
|
|
||||||
@commands.hybrid_command(
|
@commands.hybrid_command(
|
||||||
name="skip",
|
name="skip",
|
||||||
description="Skip the current song")
|
description="Skip the current song",
|
||||||
|
aliases=['s'])
|
||||||
@app_commands.describe(count="Number of songs to skip (default: 1)")
|
@app_commands.describe(count="Number of songs to skip (default: 1)")
|
||||||
async def skip(self, ctx: Context, count: int = 1):
|
async def skip(self, ctx: Context, count: int = 1):
|
||||||
"""Skips the current song that is playing"""
|
"""Skips the current song that is playing"""
|
||||||
@@ -251,7 +254,8 @@ class music(commands.Cog):
|
|||||||
|
|
||||||
@commands.hybrid_command(
|
@commands.hybrid_command(
|
||||||
name="loop",
|
name="loop",
|
||||||
description="Toggle loop mode")
|
description="Toggle loop mode",
|
||||||
|
aliases=['l', 'repeat'])
|
||||||
@app_commands.describe(mode="Loop mode: off, song, or queue")
|
@app_commands.describe(mode="Loop mode: off, song, or queue")
|
||||||
@app_commands.choices(mode=[
|
@app_commands.choices(mode=[
|
||||||
app_commands.Choice(name="Off", value="off"),
|
app_commands.Choice(name="Off", value="off"),
|
||||||
@@ -318,7 +322,8 @@ class music(commands.Cog):
|
|||||||
|
|
||||||
@commands.hybrid_command(
|
@commands.hybrid_command(
|
||||||
name="volume",
|
name="volume",
|
||||||
description="Set playback volume")
|
description="Set playback volume",
|
||||||
|
aliases=['vol', 'v'])
|
||||||
@app_commands.describe(level="Volume level (0-200%, default shows current)")
|
@app_commands.describe(level="Volume level (0-200%, default shows current)")
|
||||||
async def volume(self, ctx: Context, level: int = None):
|
async def volume(self, ctx: Context, level: int = None):
|
||||||
"""Set or display the current volume"""
|
"""Set or display the current volume"""
|
||||||
@@ -360,7 +365,8 @@ class music(commands.Cog):
|
|||||||
|
|
||||||
@commands.hybrid_command(
|
@commands.hybrid_command(
|
||||||
name="effect",
|
name="effect",
|
||||||
description="Apply audio effects to playback")
|
description="Apply audio effects to playback",
|
||||||
|
aliases=['fx', 'filter'])
|
||||||
@app_commands.describe(effect_name="The audio effect to apply (leave empty to see list)")
|
@app_commands.describe(effect_name="The audio effect to apply (leave empty to see list)")
|
||||||
async def effect(self, ctx: Context, effect_name: str = None):
|
async def effect(self, ctx: Context, effect_name: str = None):
|
||||||
"""Apply or list audio effects"""
|
"""Apply or list audio effects"""
|
||||||
|
|||||||
@@ -35,10 +35,20 @@ def get_effect_options(effect_name):
|
|||||||
**BASE_FFMPEG_OPTS,
|
**BASE_FFMPEG_OPTS,
|
||||||
'options': '-vn -af "atempo=0.8,asetrate=48000*0.8,aecho=0.8:0.9:1000:0.3"'
|
'options': '-vn -af "atempo=0.8,asetrate=48000*0.8,aecho=0.8:0.9:1000:0.3"'
|
||||||
},
|
},
|
||||||
|
'earrape': {
|
||||||
|
**BASE_FFMPEG_OPTS,
|
||||||
|
# Aggressive compression + hard clipping + bitcrushing for maximum distortion
|
||||||
|
'options': '-vn -af "volume=8,acompressor=threshold=0.001:ratio=30:attack=0.1:release=5,acrusher=bits=8:mix=0.7,volume=2,alimiter=limit=0.8"'
|
||||||
|
},
|
||||||
|
'deepfry': {
|
||||||
|
**BASE_FFMPEG_OPTS,
|
||||||
|
# Extreme bitcrushing + bass boost + compression (meme audio effect)
|
||||||
|
'options': '-vn -af "acrusher=bits=4:mode=log:aa=1,bass=g=15,acompressor=threshold=0.001:ratio=20,volume=3"'
|
||||||
|
},
|
||||||
'distortion': {
|
'distortion': {
|
||||||
**BASE_FFMPEG_OPTS,
|
**BASE_FFMPEG_OPTS,
|
||||||
# Pure bitcrushing distortion
|
# Pure bitcrushing distortion
|
||||||
'options': '-vn -af "acrusher=bits=6:mix=0.9,acompressor=threshold=0.01:ratio=15"'
|
'options': '-vn -af "acrusher=bits=6:mix=0.9,acompressor=threshold=0.01:ratio=15,volume=2"'
|
||||||
},
|
},
|
||||||
'reverse': {
|
'reverse': {
|
||||||
**BASE_FFMPEG_OPTS,
|
**BASE_FFMPEG_OPTS,
|
||||||
@@ -76,6 +86,16 @@ def get_effect_options(effect_name):
|
|||||||
**BASE_FFMPEG_OPTS,
|
**BASE_FFMPEG_OPTS,
|
||||||
'options': '-vn -af "aecho=0.8:0.88:60:0.4"'
|
'options': '-vn -af "aecho=0.8:0.88:60:0.4"'
|
||||||
},
|
},
|
||||||
|
'phone': {
|
||||||
|
**BASE_FFMPEG_OPTS,
|
||||||
|
# Sounds like a phone call (bandpass filter)
|
||||||
|
'options': '-vn -af "bandpass=f=1500:width_type=h:w=1000,volume=2"'
|
||||||
|
},
|
||||||
|
'megaphone': {
|
||||||
|
**BASE_FFMPEG_OPTS,
|
||||||
|
# Megaphone/radio effect
|
||||||
|
'options': '-vn -af "highpass=f=300,lowpass=f=3000,volume=2,acompressor=threshold=0.1:ratio=8"'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return effects.get(effect_name, effects['none'])
|
return effects.get(effect_name, effects['none'])
|
||||||
@@ -202,7 +222,12 @@ async def add_song(server_id, details, queued_by):
|
|||||||
|
|
||||||
|
|
||||||
# Pop song from server (respects loop mode)
|
# Pop song from server (respects loop mode)
|
||||||
async def pop(server_id, ignore=False):
|
async def pop(server_id, ignore=False, skip_mode=False):
|
||||||
|
"""
|
||||||
|
Pop next song from queue
|
||||||
|
ignore: Skip the song without returning URL
|
||||||
|
skip_mode: True when called from skip command (affects loop song behavior)
|
||||||
|
"""
|
||||||
# Connect to db
|
# Connect to db
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(db_path)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -624,6 +649,8 @@ def get_effect_emoji(effect_name):
|
|||||||
'bassboost': '🔉💥',
|
'bassboost': '🔉💥',
|
||||||
'nightcore': '⚡🎀',
|
'nightcore': '⚡🎀',
|
||||||
'slowed': '🐌💤',
|
'slowed': '🐌💤',
|
||||||
|
'earrape': '💀📢',
|
||||||
|
'deepfry': '🍟💥',
|
||||||
'distortion': '⚡🔊',
|
'distortion': '⚡🔊',
|
||||||
'reverse': '⏪🔄',
|
'reverse': '⏪🔄',
|
||||||
'chipmunk': '🐿️',
|
'chipmunk': '🐿️',
|
||||||
@@ -634,6 +661,8 @@ def get_effect_emoji(effect_name):
|
|||||||
'vibrato': '〰️',
|
'vibrato': '〰️',
|
||||||
'tremolo': '📳',
|
'tremolo': '📳',
|
||||||
'echo': '🗣️💭',
|
'echo': '🗣️💭',
|
||||||
|
'phone': '📞',
|
||||||
|
'megaphone': '📢📣'
|
||||||
}
|
}
|
||||||
return emojis.get(effect_name, '🔊')
|
return emojis.get(effect_name, '🔊')
|
||||||
|
|
||||||
@@ -645,6 +674,8 @@ def get_effect_description(effect_name):
|
|||||||
'bassboost': 'MAXIMUM BASS 🔊',
|
'bassboost': 'MAXIMUM BASS 🔊',
|
||||||
'nightcore': 'Speed + pitch up (anime vibes)',
|
'nightcore': 'Speed + pitch up (anime vibes)',
|
||||||
'slowed': 'Slowed + reverb (TikTok aesthetic)',
|
'slowed': 'Slowed + reverb (TikTok aesthetic)',
|
||||||
|
'earrape': '⚠️ Aggressive compression + distortion + clipping ⚠️',
|
||||||
|
'deepfry': '🍟 EXTREME bitcrushing + bass (meme audio) 🍟',
|
||||||
'distortion': 'Heavy bitcrushing distortion',
|
'distortion': 'Heavy bitcrushing distortion',
|
||||||
'reverse': 'Plays audio BACKWARDS',
|
'reverse': 'Plays audio BACKWARDS',
|
||||||
'chipmunk': 'High pitched and fast (Alvin mode)',
|
'chipmunk': 'High pitched and fast (Alvin mode)',
|
||||||
@@ -655,6 +686,8 @@ def get_effect_description(effect_name):
|
|||||||
'vibrato': 'Warbling pitch effect',
|
'vibrato': 'Warbling pitch effect',
|
||||||
'tremolo': 'Volume oscillation',
|
'tremolo': 'Volume oscillation',
|
||||||
'echo': 'Echo/reverb effect',
|
'echo': 'Echo/reverb effect',
|
||||||
|
'phone': 'Sounds like a phone call',
|
||||||
|
'megaphone': 'Megaphone/radio effect'
|
||||||
}
|
}
|
||||||
return descriptions.get(effect_name, 'Unknown effect')
|
return descriptions.get(effect_name, 'Unknown effect')
|
||||||
|
|
||||||
|
|||||||
@@ -211,12 +211,14 @@ async def display_server_queue(ctx: Context, songs, n):
|
|||||||
loop_emoji = loop_emojis.get(loop_mode, '')
|
loop_emoji = loop_emojis.get(loop_mode, '')
|
||||||
effect_emoji = queue.get_effect_emoji(effect)
|
effect_emoji = queue.get_effect_emoji(effect)
|
||||||
|
|
||||||
# Progress bar (━ for filled, ─ for empty)
|
# Progress bar - using Unicode block characters for smooth look
|
||||||
progress_bar = ""
|
progress_bar = ""
|
||||||
if duration > 0:
|
if duration > 0:
|
||||||
bar_length = 15
|
bar_length = 20 # Increased from 15 for smoother display
|
||||||
filled = int((percentage / 100) * bar_length)
|
filled = int((percentage / 100) * bar_length)
|
||||||
progress_bar = f"\n{'━' * filled}{'─' * (bar_length - filled)} `{format_time(elapsed)} / {format_time(duration)}`"
|
|
||||||
|
# Use block characters: █ for filled, ░ for empty
|
||||||
|
progress_bar = f"\n{'█' * filled}{'░' * (bar_length - filled)} `{format_time(elapsed)} / {format_time(duration)}`"
|
||||||
|
|
||||||
# Now playing section
|
# Now playing section
|
||||||
now_playing = f"### 🔊 Now Playing\n**{current_song}** {loop_emoji}{progress_bar}\n"
|
now_playing = f"### 🔊 Now Playing\n**{current_song}** {loop_emoji}{progress_bar}\n"
|
||||||
|
|||||||
353
web_dhasboard_plan.md
Normal file
353
web_dhasboard_plan.md
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
# 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)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 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:**
|
||||||
|
1. Bot writes current state to database
|
||||||
|
2. Web reads database and displays
|
||||||
|
3. Web writes commands to "commands" table
|
||||||
|
4. 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:**
|
||||||
|
1. Bot publishes events to Redis: `PUBLISH bot:status {"song": "...", "queue": [...]}`
|
||||||
|
2. Web subscribes to Redis channel
|
||||||
|
3. Web publishes commands: `PUBLISH bot:commands {"action": "skip"}`
|
||||||
|
4. 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)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 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
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 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:**
|
||||||
|
|
||||||
|
1. **Discord OAuth2** (Recommended)
|
||||||
|
- Users log in with Discord
|
||||||
|
- Check if user is in the server
|
||||||
|
- Only show servers they're members of
|
||||||
|
|
||||||
|
2. **API Keys**
|
||||||
|
- Generate unique key per server
|
||||||
|
- Server admins share key with trusted users
|
||||||
|
|
||||||
|
3. **IP Whitelist**
|
||||||
|
- Only allow specific IPs to access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
# Backend
|
||||||
|
cd backend
|
||||||
|
uvicorn main:app --reload --port 8000
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
cd frontend
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
|
||||||
|
1. **Start with database-based approach** - Get it working first
|
||||||
|
2. **Add WebSocket for real-time** - Once basic functionality works
|
||||||
|
3. **Build simple UI** - Focus on core features (play, queue, skip)
|
||||||
|
4. **Add authentication** - Discord OAuth2
|
||||||
|
5. **Polish and deploy** - Make it production-ready
|
||||||
Reference in New Issue
Block a user