Files
gitlab-instance-0a899031_do…/tiktok/tiktok.py
2026-01-26 23:16:25 -06:00

143 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from fastapi import FastAPI
from fastapi.responses import FileResponse, JSONResponse
from pathlib import Path
import asyncio
import re
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(title="HDMI 录制控制台")
# ---------------- 配置 ----------------
BOT_NAME = "hdmi"
BASE_DIR = Path(__file__).parent
DEFAULT_LOG_DIR = BASE_DIR.parent / ".pm2" / "logs"
MAX_LOG_LINES = 300
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["GET"],
allow_headers=["*"],
)
# ---------------- 工具 ----------------
def strip_ansi_codes(text: str) -> str:
return re.sub(r'\x1b\[[0-9;]*m', '', text)
async def run_pm2(cmd: str) -> str:
process = await asyncio.create_subprocess_shell(
f"pm2 {cmd}",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
try:
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=15.0)
return (stdout or stderr).decode(errors="ignore").strip()
except asyncio.TimeoutError:
process.kill()
return "ERROR: PM2 命令超时"
async def read_log_path(path: str, lines: int) -> str:
if not path:
return "无日志路径\n"
if "/dev/null" in path:
return "日志输出被禁用PM2 配置为无日志)\n"
log_file = Path(path)
if not log_file.exists():
return f"日志文件不存在: {path}\n"
try:
with open(log_file, "r", encoding="utf-8", errors="ignore") as f:
content_lines = f.readlines()
return "".join(content_lines[-lines:]) if content_lines else "无日志内容\n"
except Exception as e:
return f"读取日志失败 ({path}): {str(e)}\n"
async def get_hdmi_info() -> dict:
describe_raw = await run_pm2(f"describe {BOT_NAME}")
describe_clean = strip_ansi_codes(describe_raw)
if ("No such process" in describe_clean or
"Unknown process" in describe_clean or
"not found" in describe_clean.lower() or
not describe_clean.strip()):
return {
"process_status": "not_found",
"out_path": None,
"err_path": None,
}
status = "unknown"
out_path = None
err_path = None
for line in describe_raw.splitlines():
line_clean = strip_ansi_codes(line)
if line_clean.count('') < 2:
continue
parts = [p.strip() for p in line_clean.split('') if p.strip()]
if len(parts) >= 2:
key = parts[0].lower()
value = parts[1]
if key == "status":
status = value.lower()
elif key == "out_log_path":
out_path = value
elif key == "err_log_path":
err_path = value
# 备用默认路径(仅当 describe 未提供时)
if not out_path:
out_path = str(DEFAULT_LOG_DIR / f"{BOT_NAME}-out.log")
if not err_path:
err_path = str(DEFAULT_LOG_DIR / f"{BOT_NAME}-error.log")
return {
"process_status": status,
"out_path": out_path,
"err_path": err_path,
}
# ---------------- 路由 ----------------
@app.get("/start")
async def start():
output = await run_pm2(f"start {BOT_NAME}")
return JSONResponse({"output": output})
@app.get("/stop")
async def stop():
output = await run_pm2(f"stop {BOT_NAME}")
return JSONResponse({"output": output})
@app.get("/restart")
async def restart():
output = await run_pm2(f"restart {BOT_NAME}")
return JSONResponse({"output": output})
@app.get("/status")
async def status():
full_status_raw = await run_pm2("status")
full_status = strip_ansi_codes(full_status_raw) # 去除颜色代码,前端显示更干净
info = await get_hdmi_info()
recent_log = await read_log_path(info["out_path"], MAX_LOG_LINES)
recent_error = await read_log_path(info["err_path"], MAX_LOG_LINES)
if info["process_status"] == "not_found":
recent_log = "进程未注册到 PM2可能被 pm2 delete 删除或从未保存)\n"
recent_error = ""
return JSONResponse({
"raw_status": full_status,
"process_status": info["process_status"],
"recent_log": recent_log,
"recent_error": recent_error,
})
@app.get("/")
async def index():
return FileResponse(BASE_DIR / "index.html")