Files
zyrarbot/web.py
T

297 lines
10 KiB
Python

from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import List, Optional
import asyncio
# Import bot instance and YTDL class
from bot import bot
from ytdl import YTDLSource
# Initialize FastAPI application
app = FastAPI(title="VibeBot Queue Controller")
# FastAPI Request Models
class PlayRequest(BaseModel):
query: str
class VolumeRequest(BaseModel):
volume: int
class LoopRequest(BaseModel):
loop: bool
class RemoveRequest(BaseModel):
id: str
class ReorderRequest(BaseModel):
queue_ids: List[str]
class ConnectRequest(BaseModel):
channel_id: str
class TextChannelRequest(BaseModel):
channel_id: str
# FastAPI Endpoints
@app.get("/api/status")
async def get_status():
music_cog = bot.get_cog("MusicBot")
if not music_cog:
return {"error": "MusicBot cog not loaded"}
voice_client = bot.voice_clients[0] if bot.voice_clients else None
active_vc = None
if voice_client and voice_client.channel:
active_vc = {
"channel_name": voice_client.channel.name,
"channel_id": str(voice_client.channel.id),
"guild_name": voice_client.channel.guild.name,
"guild_id": str(voice_client.channel.guild.id)
}
# Gather voice channels available to the bot across all guilds
channels = []
for guild in bot.guilds:
for channel in guild.voice_channels:
channels.append({
"guild_name": guild.name,
"guild_id": str(guild.id),
"channel_name": channel.name,
"channel_id": str(channel.id)
})
# Gather text channels available to the bot across all guilds where it has send permissions
text_channels = []
for guild in bot.guilds:
for channel in guild.text_channels:
if channel.permissions_for(guild.me).send_messages:
text_channels.append({
"guild_name": guild.name,
"guild_id": str(guild.id),
"channel_name": channel.name,
"channel_id": str(channel.id)
})
active_tc_id = None
active_tc = music_cog.get_notification_channel()
if active_tc:
active_tc_id = str(active_tc.id)
playing = voice_client.is_playing() if voice_client else False
paused = voice_client.is_paused() if voice_client else False
return {
"playing": playing,
"paused": paused,
"loop_single": music_cog.loop_current,
"volume": int(music_cog.volume * 100),
"current": {
"title": music_cog.current.title,
"url": music_cog.current.data.get("webpage_url") or music_cog.current.url,
"id": music_cog.current.id
} if music_cog.current else None,
"queue": [
{
"title": item.title,
"url": item.data.get("webpage_url") or item.url,
"id": item.id
} for item in music_cog.queue
],
"voice_channels": channels,
"active_voice_channel": active_vc,
"text_channels": text_channels,
"active_text_channel_id": active_tc_id
}
@app.post("/api/play")
async def api_play(req: PlayRequest):
music_cog = bot.get_cog("MusicBot")
if not music_cog:
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
voice_client = bot.voice_clients[0] if bot.voice_clients else None
if not voice_client:
raise HTTPException(status_code=400, detail="Bot is not connected to a voice channel")
try:
player = await YTDLSource.from_url(req.query, loop=bot.loop, stream=True)
if voice_client.is_playing() or voice_client.is_paused():
music_cog.queue.append(player)
await music_cog.send_to_last_channel(f"Added to queue (Web): {player.title}")
return {"status": "queued", "title": player.title}
else:
music_cog.current = player
voice_client.play(
player,
after=lambda e: asyncio.run_coroutine_threadsafe(
music_cog.play_next(),
bot.loop
)
)
await music_cog.send_to_last_channel(f"Now playing (Web): {player.title}")
return {"status": "playing", "title": player.title}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/pause")
async def api_pause():
voice_client = bot.voice_clients[0] if bot.voice_clients else None
if voice_client and voice_client.is_playing():
voice_client.pause()
return {"status": "paused"}
raise HTTPException(status_code=400, detail="Bot is not playing")
@app.post("/api/resume")
async def api_resume():
voice_client = bot.voice_clients[0] if bot.voice_clients else None
if voice_client and voice_client.is_paused():
voice_client.resume()
return {"status": "resumed"}
raise HTTPException(status_code=400, detail="Bot is not paused")
@app.post("/api/skip")
async def api_skip():
voice_client = bot.voice_clients[0] if bot.voice_clients else None
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
voice_client.stop()
return {"status": "skipped"}
raise HTTPException(status_code=400, detail="Bot is not playing anything")
@app.post("/api/stop")
async def api_stop():
music_cog = bot.get_cog("MusicBot")
if not music_cog:
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
voice_client = bot.voice_clients[0] if bot.voice_clients else None
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
voice_client.stop()
music_cog.queue.clear()
music_cog.current = None
return {"status": "stopped"}
@app.post("/api/volume")
async def api_volume(req: VolumeRequest):
music_cog = bot.get_cog("MusicBot")
if not music_cog:
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
if 0 <= req.volume <= 100:
music_cog.volume = req.volume / 100
voice_client = bot.voice_clients[0] if bot.voice_clients else None
if voice_client and voice_client.source:
voice_client.source.volume = music_cog.volume
return {"status": "success", "volume": req.volume}
raise HTTPException(status_code=400, detail="Volume must be between 0 and 100")
@app.post("/api/loop")
async def api_loop(req: LoopRequest):
music_cog = bot.get_cog("MusicBot")
if not music_cog:
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
music_cog.loop_current = req.loop
return {"status": "success", "loop": req.loop}
@app.post("/api/remove")
async def api_remove(req: RemoveRequest):
music_cog = bot.get_cog("MusicBot")
if not music_cog:
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
initial_len = len(music_cog.queue)
music_cog.queue = [item for item in music_cog.queue if item.id != req.id]
if len(music_cog.queue) < initial_len:
return {"status": "removed"}
raise HTTPException(status_code=404, detail="Track not found in queue")
@app.post("/api/reorder")
async def api_reorder(req: ReorderRequest):
music_cog = bot.get_cog("MusicBot")
if not music_cog:
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
queue_map = {item.id: item for item in music_cog.queue}
new_queue = []
for track_id in req.queue_ids:
if track_id in queue_map:
new_queue.append(queue_map[track_id])
# Retain any items not present in the reordered IDs list
for item in music_cog.queue:
if item.id not in req.queue_ids:
new_queue.append(item)
music_cog.queue = new_queue
return {"status": "success"}
@app.post("/api/connect")
async def api_connect(req: ConnectRequest):
try:
channel_id = int(req.channel_id)
except ValueError:
raise HTTPException(status_code=400, detail="Invalid channel ID")
channel = bot.get_channel(channel_id)
if not channel:
raise HTTPException(status_code=404, detail="Voice channel not found")
if bot.voice_clients:
for vc in bot.voice_clients:
await vc.disconnect()
await channel.connect()
music_cog = bot.get_cog("MusicBot")
if music_cog and not music_cog.last_channel:
# Save first accessible text channel to send updates
for tc in channel.guild.text_channels:
if tc.permissions_for(channel.guild.me).send_messages:
music_cog.last_channel = tc
break
return {"status": "connected", "channel": channel.name}
@app.post("/api/disconnect")
async def api_disconnect():
if bot.voice_clients:
for vc in bot.voice_clients:
await vc.disconnect()
music_cog = bot.get_cog("MusicBot")
if music_cog:
music_cog.queue.clear()
music_cog.current = None
return {"status": "disconnected"}
raise HTTPException(status_code=400, detail="Bot not connected to voice")
@app.post("/api/settings/text_channel")
async def api_set_text_channel(req: TextChannelRequest):
music_cog = bot.get_cog("MusicBot")
if not music_cog:
raise HTTPException(status_code=500, detail="MusicBot cog not loaded")
channel = bot.get_channel(int(req.channel_id))
if not channel:
raise HTTPException(status_code=404, detail="Text channel not found")
music_cog.last_channel = channel
music_cog.save_channel_id(int(req.channel_id))
# Send test message to confirm
try:
await channel.send(f"🔔 Ten kanał został ustawiony jako główny kanał powiadomień dla VibeBot z panelu webowego.")
except Exception as e:
print(f"Failed to send test message: {e}")
return {"status": "success", "channel_id": req.channel_id}
# Serve UI Files (Must be registered after API routes)
app.mount("/", StaticFiles(directory="static", html=True), name="static")