297 lines
10 KiB
Python
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")
|