diff --git a/main.py b/main.py index 23b9b67..a0f2b2e 100644 --- a/main.py +++ b/main.py @@ -144,6 +144,12 @@ async def stop_tg_bot(): from web.api import app as web_app +async def restart_tg_bot_runtime(): + """供 Web API 调用:只重启 TG Bot,不重启 Web。""" + await stop_tg_bot() + asyncio.create_task(start_tg_bot_safe()) + + async def start_tg_bot_safe(): """启动 TG Bot;初始化阶段网络失败时压缩日志并自动重试。""" delay = 10 @@ -168,6 +174,8 @@ async def lifespan(app): # 初始化数据库 await init_db() # 启动 TG Bot + import web.api as web_api + web_api.set_restart_bot_callback(restart_tg_bot_runtime) task = asyncio.create_task(start_tg_bot_safe()) def log_task_result(t: asyncio.Task): diff --git a/web/api.py b/web/api.py index 1e11d9f..c456e6b 100644 --- a/web/api.py +++ b/web/api.py @@ -26,6 +26,12 @@ _sse_queues: list[asyncio.Queue] = [] _log_buffer: list[dict] = [] MAX_LOG_BUFFER = 300 MAX_LOG_MESSAGE_CHARS = 1200 +_restart_bot_callback = None + + +def set_restart_bot_callback(callback): + global _restart_bot_callback + _restart_bot_callback = callback def compact_log_message(record: logging.LogRecord) -> str: @@ -154,6 +160,16 @@ async def api_logs(limit: int = Query(default=100, ge=1, le=300)): return JSONResponse(_log_buffer[-limit:]) +@app.post("/api/restart-bot") +async def api_restart_bot(): + """只重启 Telegram Bot,不重启 Web 服务。""" + if not _restart_bot_callback: + return JSONResponse({"ok": False, "error": "Bot 重启回调未就绪"}, status_code=503) + logging.getLogger("spam_guard").warning("🔄 Web 管理端请求重启 TG Bot") + asyncio.create_task(_restart_bot_callback()) + return JSONResponse({"ok": True, "message": "TG Bot 正在重启"}) + + @app.post("/api/restart") async def api_restart(): """重启服务:返回响应后退出当前进程,由 Docker restart 策略拉起。""" diff --git a/web/static/index.html b/web/static/index.html index ae744b8..bc05b1f 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -184,7 +184,8 @@
{{ sseConnected ? '实时连接' : '连接中...' }} - 重启 + 重启Bot + 重启服务
@@ -296,6 +297,7 @@ new Vue({ newMessage: '', sending: false, restarting: false, + restartingBot: false, stats: { today_bans: 0, total_bans: 0, active_bans: 0 }, } }, @@ -377,6 +379,28 @@ new Vue({ async loadStats() { try { this.stats = await (await fetch('/api/stats')).json() } catch (e) {} }, + async restartBot() { + this.$confirm('确认只重启 Telegram Bot?Web 管理页不会重启。', '重启 TG Bot', { + confirmButtonText: '确认重启', + cancelButtonText: '取消', + type: 'warning' + }).then(async () => { + this.restartingBot = true + try { + const data = await (await fetch('/api/restart-bot', { method: 'POST' })).json() + if (data.ok) { + this.$message.success('✅ TG Bot 正在重启') + setTimeout(() => { this.restartingBot = false; this.loadLogs() }, 5000) + } else { + this.$message.error(`❌ 重启失败: ${data.error || '未知错误'}`) + this.restartingBot = false + } + } catch (e) { + this.$message.error('❌ 网络错误') + this.restartingBot = false + } + }).catch(() => {}) + }, async restartService() { this.$confirm('确认重启 TG Spam Guard 服务?重启期间 Web 和 Bot 会短暂不可用。', '重启服务', { confirmButtonText: '确认重启',