114 lines
3.4 KiB
Python
114 lines
3.4 KiB
Python
from enum import Enum
|
|
import os
|
|
from pprint import pprint
|
|
from typing import Optional, Set
|
|
import discord
|
|
from discord.ext import commands
|
|
from threading import Thread
|
|
from dotenv import load_dotenv
|
|
from fastapi.concurrency import asynccontextmanager
|
|
from pydantic import BaseModel
|
|
import asyncio
|
|
import websockets
|
|
import json
|
|
import time
|
|
from src.models import BotResponse, BotStatus, MessageType, PlaybackInformation
|
|
from src.my_voice_client import get_voice_client, set_voice_client
|
|
from src.playback_service import (
|
|
get_filename_and_starttime,
|
|
get_status,
|
|
handle_message,
|
|
handle_new_song_on_queue,
|
|
pause_song,
|
|
play_current_song,
|
|
start_time_now,
|
|
)
|
|
from src.song_queue import add_to_queue, get_current_metadata, handle_song_end, has_current_song, move_to_last_song_in_queue
|
|
|
|
load_dotenv()
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
|
|
bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
|
|
connected_clients: Set[websockets.WebSocketServerProtocol] = set()
|
|
|
|
async def broadcast_bot_response(response: BotResponse):
|
|
if connected_clients:
|
|
await asyncio.wait(
|
|
[
|
|
asyncio.create_task(client.send(response.model_dump_json()))
|
|
for client in connected_clients
|
|
]
|
|
)
|
|
else:
|
|
raise TypeError("Passing coroutines is forbidden, use tasks explicitly.")
|
|
|
|
async def send_response_message(
|
|
websocket: websockets.WebSocketServerProtocol, response: BotResponse
|
|
):
|
|
await websocket.send(response.model_dump_json())
|
|
|
|
async def websocket_handler(websocket: websockets.WebSocketServerProtocol, path: str):
|
|
connected_clients.add(websocket)
|
|
try:
|
|
async for message in websocket:
|
|
data = json.loads(message)
|
|
response = handle_message(data)
|
|
await send_response_message(websocket, response)
|
|
|
|
except websockets.ConnectionClosedError as e:
|
|
print(f"Connection closed with error: {e}")
|
|
except Exception as e:
|
|
print(f"Unexpected error: {e}")
|
|
raise e
|
|
finally:
|
|
connected_clients.remove(websocket)
|
|
print("WebSocket connection closed")
|
|
|
|
@bot.event
|
|
async def on_ready():
|
|
print("Bot is ready")
|
|
|
|
@bot.command(name="play", pass_context=True)
|
|
async def play(ctx: commands.Context, url: str):
|
|
print("playing", url)
|
|
channel = ctx.message.author.voice.channel
|
|
|
|
if ctx.voice_client is None:
|
|
set_voice_client(await channel.connect())
|
|
add_to_queue(url)
|
|
handle_new_song_on_queue()
|
|
|
|
@bot.command(pass_context=True)
|
|
async def stop(ctx: commands.Context):
|
|
voice_client = get_voice_client()
|
|
if voice_client and voice_client.is_playing():
|
|
voice_client.stop()
|
|
await voice_client.disconnect()
|
|
await ctx.send("Stopped playing")
|
|
|
|
@bot.command(pass_context=True)
|
|
async def pause(ctx: commands.Context):
|
|
pause_song()
|
|
|
|
def run_websocket():
|
|
print("started websocket")
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
start_server = websockets.serve(websocket_handler, "0.0.0.0", 5678)
|
|
loop.run_until_complete(start_server)
|
|
loop.run_forever()
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
Thread(target=run_websocket).start()
|
|
Thread(target=lambda: bot.run(os.getenv("DISCORD_SECRET"))).start()
|
|
yield
|
|
|
|
app = FastAPI(lifespan=lifespan)
|
|
|
|
app.mount("/", StaticFiles(directory="./client", html=True), name="static") |