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")