feat: add bot restart functionality via API and Discord command
This commit is contained in:
7 files changed
+172
-5
No files matched your search
+33
-1
@@ -46,7 +46,15 @@ class Application:
|
||||
self.queues = QueueRepository(os.path.join(self.settings.config_dir, "queues"))
|
||||
self.tracks = TrackFactory(self.uploads, MediaProbe())
|
||||
|
||||
self.bot = MusicBot(self.settings, self.tracks, self.uploads, self.config, self.history)
|
||||
self._server: uvicorn.Server | None = None
|
||||
self.bot = MusicBot(
|
||||
self.settings,
|
||||
self.tracks,
|
||||
self.uploads,
|
||||
self.config,
|
||||
self.history,
|
||||
restart_callback=self.schedule_restart,
|
||||
)
|
||||
self.guilds = GuildResolver(self.bot)
|
||||
self.player = MusicPlayer(
|
||||
bot=self.bot,
|
||||
@@ -60,6 +68,7 @@ class Application:
|
||||
self.bot.player = self.player
|
||||
|
||||
self.web = FastAPI(title="ŻyrarBOT")
|
||||
self.web.state.restart = self.schedule_restart
|
||||
broadcaster = mount_api(
|
||||
self.web,
|
||||
settings=self.settings,
|
||||
@@ -99,6 +108,28 @@ class Application:
|
||||
"/", StaticFiles(directory=self.settings.static_dir, html=True), name="static"
|
||||
)
|
||||
|
||||
def schedule_restart(self, delay: float = 0.5) -> None:
|
||||
"""Zaplanuj restart bota (zakończenie procesu Pythona)."""
|
||||
asyncio.create_task(self._restart_worker(delay))
|
||||
|
||||
async def _restart_worker(self, delay: float) -> None:
|
||||
logger.info("Zaplanowano restart aplikacji za %.1f s...", delay)
|
||||
await asyncio.sleep(delay)
|
||||
logger.info("Kończenie pracy aplikacji w celu restartu...")
|
||||
try:
|
||||
if not self.bot.is_closed():
|
||||
await self.bot.close()
|
||||
except Exception as error: # noqa: BLE001
|
||||
logger.error("Błąd podczas zamykania bota: %s", error)
|
||||
|
||||
if self._server is not None:
|
||||
self._server.should_exit = True
|
||||
|
||||
# Bezpiecznik: jeśli proces nie zakończy się w ciągu 2.0 s, wymuś wyjście
|
||||
await asyncio.sleep(2.0)
|
||||
logger.warning("Wymuszanie zakończenia procesu (os._exit)")
|
||||
os._exit(0)
|
||||
|
||||
async def run(self) -> None:
|
||||
"""Startuje serwer web, a następnie bota; panel działa też bez Discorda."""
|
||||
config = uvicorn.Config(
|
||||
@@ -108,6 +139,7 @@ class Application:
|
||||
log_level="info",
|
||||
)
|
||||
server = uvicorn.Server(config)
|
||||
self._server = server
|
||||
|
||||
async with self.bot:
|
||||
server_task = asyncio.create_task(server.serve())
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
import discord
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
|
||||
from zyrarbot.music.music_player import MusicPlayer
|
||||
from zyrarbot.ui.api.dependencies import get_active_guild, get_player, get_status_service
|
||||
@@ -23,3 +23,11 @@ async def get_status(
|
||||
@router.get("/health")
|
||||
async def get_health(player: MusicPlayer = Depends(get_player)) -> dict[str, Any]:
|
||||
return {"status": "ok", "ready": player.is_ready}
|
||||
|
||||
|
||||
@router.post("/restart")
|
||||
async def restart_bot(request: Request) -> dict[str, Any]:
|
||||
restart_action = getattr(request.app.state, "restart", None)
|
||||
if restart_action:
|
||||
restart_action()
|
||||
return {"status": "restarting"}
|
||||
@@ -5,6 +5,8 @@ import logging
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from zyrarbot.audio.track_factory import TrackFactory
|
||||
from zyrarbot.music.music_player import MusicPlayer
|
||||
from zyrarbot.settings import Settings
|
||||
@@ -35,6 +37,7 @@ class MusicBot(commands.Bot):
|
||||
uploads: UploadsRepository,
|
||||
config: GuildConfigRepository,
|
||||
history: HistoryRepository,
|
||||
restart_callback: Callable[[], None] | None = None,
|
||||
):
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
@@ -47,6 +50,22 @@ class MusicBot(commands.Bot):
|
||||
self.config = config
|
||||
self.history = history
|
||||
self.player: MusicPlayer | None = None
|
||||
self._restart_callback = restart_callback
|
||||
|
||||
def schedule_restart(self) -> None:
|
||||
"""Żąda restartu bota za pomocą zarejestrowanego wywołania zwrotnego."""
|
||||
if self._restart_callback is not None:
|
||||
self._restart_callback()
|
||||
else:
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
async def _default_restart() -> None:
|
||||
await asyncio.sleep(0.5)
|
||||
await self.close()
|
||||
os._exit(0)
|
||||
|
||||
asyncio.create_task(_default_restart())
|
||||
|
||||
def _create_music_cogs(self) -> tuple[commands.Cog, ...]:
|
||||
if self.player is None:
|
||||
|
||||
@@ -122,3 +122,11 @@ class ControlCog(MusicCogBase, name="Control"):
|
||||
return
|
||||
title = discord.utils.escape_markdown(current.title or "")
|
||||
await interaction.response.send_message(embed=message_embed("Teraz odtwarzane", title))
|
||||
|
||||
@app_commands.command(name="restart", description="Zrestartuj bota (restart kontenera)")
|
||||
async def restart(self, interaction: discord.Interaction) -> None:
|
||||
await interaction.response.send_message(
|
||||
embed=message_embed("Restartowanie", "Bot jest restartowany...")
|
||||
)
|
||||
if hasattr(self.bot, "schedule_restart"):
|
||||
self.bot.schedule_restart()
|
||||
Reference in new issue
Block a user