s
This commit is contained in:
@@ -1 +1 @@
|
|||||||
https://live.douyin.com/147650297461,主播: 娱乐摆摊记
|
https://live.douyin.com/35932729226,主播: 内向小学生
|
||||||
|
|||||||
4
dist/douyin_srs_ffplay.py
vendored
4
dist/douyin_srs_ffplay.py
vendored
File diff suppressed because one or more lines are too long
2
dist/pyarmor_runtime_000000/__init__.py
vendored
2
dist/pyarmor_runtime_000000/__init__.py
vendored
@@ -1,2 +1,2 @@
|
|||||||
# Pyarmor 9.2.3 (trial), 000000, 2026-01-22T02:45:58.114587
|
# Pyarmor 9.2.3 (trial), 000000, 2026-01-24T06:52:53.934396
|
||||||
from .pyarmor_runtime import __pyarmor__
|
from .pyarmor_runtime import __pyarmor__
|
||||||
|
|||||||
10
index.html
10
index.html
@@ -64,11 +64,11 @@ function scrollLog(){logEl.scrollTop = logEl.scrollHeight}
|
|||||||
|
|
||||||
function updateStatus(process_status){
|
function updateStatus(process_status){
|
||||||
let emoji = '⚪', text = '未知';
|
let emoji = '⚪', text = '未知';
|
||||||
if(process_status === 'online'){ emoji = '🟢'; text = '运行中'; }
|
if(process_status === 'online'){ emoji = ' '; text = '运行中'; }
|
||||||
else if(process_status === 'stopped'){ emoji = '🔴'; text = '已停止'; }
|
else if(process_status === 'stopped'){ emoji = ' '; text = '已停止'; }
|
||||||
else if(process_status === 'errored'){ emoji = '🔥'; text = '错误'; }
|
else if(process_status === 'errored'){ emoji = ' '; text = '错误'; }
|
||||||
else if(process_status === 'launching' || process_status === 'starting'){ emoji = '🟡'; text = '启动中'; }
|
else if(process_status === 'launching' || process_status === 'starting'){ emoji = ' '; text = '启动中'; }
|
||||||
else if(process_status === 'waiting restart'){ emoji = '🟡'; text = '等待重启'; }
|
else if(process_status === 'waiting restart'){ emoji = ' '; text = '等待重启'; }
|
||||||
else if(process_status === 'not_found'){ emoji = '❌'; text = '未注册到 PM2'; }
|
else if(process_status === 'not_found'){ emoji = '❌'; text = '未注册到 PM2'; }
|
||||||
statusIndicator.innerHTML = `${emoji} ${text}`;
|
statusIndicator.innerHTML = `${emoji} ${text}`;
|
||||||
}
|
}
|
||||||
|
|||||||
176
web.py
176
web.py
@@ -15,128 +15,128 @@ MAX_LOG_LINES = 300
|
|||||||
|
|
||||||
# CORS
|
# CORS
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"],
|
allow_origins=["*"],
|
||||||
allow_methods=["GET"],
|
allow_methods=["GET"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---------------- 工具 ----------------
|
# ---------------- 工具 ----------------
|
||||||
def strip_ansi_codes(text: str) -> str:
|
def strip_ansi_codes(text: str) -> str:
|
||||||
return re.sub(r'\x1b\[[0-9;]*m', '', text)
|
return re.sub(r'\x1b\[[0-9;]*m', '', text)
|
||||||
|
|
||||||
async def run_pm2(cmd: str) -> str:
|
async def run_pm2(cmd: str) -> str:
|
||||||
process = await asyncio.create_subprocess_shell(
|
process = await asyncio.create_subprocess_shell(
|
||||||
f"pm2 {cmd}",
|
f"pm2 {cmd}",
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
stderr=asyncio.subprocess.PIPE,
|
stderr=asyncio.subprocess.PIPE,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=15.0)
|
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=15.0)
|
||||||
return (stdout or stderr).decode(errors="ignore").strip()
|
return (stdout or stderr).decode(errors="ignore").strip()
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
process.kill()
|
process.kill()
|
||||||
return "ERROR: PM2 命令超时"
|
return "ERROR: PM2 命令超时"
|
||||||
|
|
||||||
async def read_log_path(path: str, lines: int) -> str:
|
async def read_log_path(path: str, lines: int) -> str:
|
||||||
if not path:
|
if not path:
|
||||||
return "无日志路径\n"
|
return "无日志路径\n"
|
||||||
if "/dev/null" in path:
|
if "/dev/null" in path:
|
||||||
return "日志输出被禁用(PM2 配置为无日志)\n"
|
return "日志输出被禁用(PM2 配置为无日志)\n"
|
||||||
log_file = Path(path)
|
log_file = Path(path)
|
||||||
if not log_file.exists():
|
if not log_file.exists():
|
||||||
return f"日志文件不存在: {path}\n"
|
return f"日志文件不存在: {path}\n"
|
||||||
try:
|
try:
|
||||||
with open(log_file, "r", encoding="utf-8", errors="ignore") as f:
|
with open(log_file, "r", encoding="utf-8", errors="ignore") as f:
|
||||||
content_lines = f.readlines()
|
content_lines = f.readlines()
|
||||||
return "".join(content_lines[-lines:]) if content_lines else "无日志内容\n"
|
return "".join(content_lines[-lines:]) if content_lines else "无日志内容\n"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"读取日志失败 ({path}): {str(e)}\n"
|
return f"读取日志失败 ({path}): {str(e)}\n"
|
||||||
|
|
||||||
async def get_hdmi_info() -> dict:
|
async def get_hdmi_info() -> dict:
|
||||||
describe_raw = await run_pm2(f"describe {BOT_NAME}")
|
describe_raw = await run_pm2(f"describe {BOT_NAME}")
|
||||||
describe_clean = strip_ansi_codes(describe_raw)
|
describe_clean = strip_ansi_codes(describe_raw)
|
||||||
|
|
||||||
if ("No such process" in describe_clean or
|
if ("No such process" in describe_clean or
|
||||||
"Unknown process" in describe_clean or
|
"Unknown process" in describe_clean or
|
||||||
"not found" in describe_clean.lower() or
|
"not found" in describe_clean.lower() or
|
||||||
not describe_clean.strip()):
|
not describe_clean.strip()):
|
||||||
return {
|
return {
|
||||||
"process_status": "not_found",
|
"process_status": "not_found",
|
||||||
"out_path": None,
|
"out_path": None,
|
||||||
"err_path": None,
|
"err_path": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
status = "unknown"
|
status = "unknown"
|
||||||
out_path = None
|
out_path = None
|
||||||
err_path = None
|
err_path = None
|
||||||
|
|
||||||
for line in describe_raw.splitlines():
|
for line in describe_raw.splitlines():
|
||||||
line_clean = strip_ansi_codes(line)
|
line_clean = strip_ansi_codes(line)
|
||||||
if line_clean.count('│') < 2:
|
if line_clean.count('│') < 2:
|
||||||
continue
|
continue
|
||||||
parts = [p.strip() for p in line_clean.split('│') if p.strip()]
|
parts = [p.strip() for p in line_clean.split('│') if p.strip()]
|
||||||
|
|
||||||
if len(parts) >= 2:
|
if len(parts) >= 2:
|
||||||
key = parts[0].lower()
|
key = parts[0].lower()
|
||||||
value = parts[1]
|
value = parts[1]
|
||||||
|
|
||||||
if key == "status":
|
if key == "status":
|
||||||
status = value.lower()
|
status = value.lower()
|
||||||
elif key == "out_log_path":
|
elif key == "out_log_path":
|
||||||
out_path = value
|
out_path = value
|
||||||
elif key == "err_log_path":
|
elif key == "err_log_path":
|
||||||
err_path = value
|
err_path = value
|
||||||
|
|
||||||
# 备用默认路径(仅当 describe 未提供时)
|
# 备用默认路径(仅当 describe 未提供时)
|
||||||
if not out_path:
|
if not out_path:
|
||||||
out_path = str(DEFAULT_LOG_DIR / f"{BOT_NAME}-out.log")
|
out_path = str(DEFAULT_LOG_DIR / f"{BOT_NAME}-out.log")
|
||||||
if not err_path:
|
if not err_path:
|
||||||
err_path = str(DEFAULT_LOG_DIR / f"{BOT_NAME}-error.log")
|
err_path = str(DEFAULT_LOG_DIR / f"{BOT_NAME}-error.log")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"process_status": status,
|
"process_status": status,
|
||||||
"out_path": out_path,
|
"out_path": out_path,
|
||||||
"err_path": err_path,
|
"err_path": err_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------- 路由 ----------------
|
# ---------------- 路由 ----------------
|
||||||
@app.get("/start")
|
@app.get("/start")
|
||||||
async def start():
|
async def start():
|
||||||
output = await run_pm2(f"start {BOT_NAME}")
|
output = await run_pm2(f"start {BOT_NAME}")
|
||||||
return JSONResponse({"output": output})
|
return JSONResponse({"output": output})
|
||||||
|
|
||||||
@app.get("/stop")
|
@app.get("/stop")
|
||||||
async def stop():
|
async def stop():
|
||||||
output = await run_pm2(f"stop {BOT_NAME}")
|
output = await run_pm2(f"stop {BOT_NAME}")
|
||||||
return JSONResponse({"output": output})
|
return JSONResponse({"output": output})
|
||||||
|
|
||||||
@app.get("/restart")
|
@app.get("/restart")
|
||||||
async def restart():
|
async def restart():
|
||||||
output = await run_pm2(f"restart {BOT_NAME}")
|
output = await run_pm2(f"restart {BOT_NAME}")
|
||||||
return JSONResponse({"output": output})
|
return JSONResponse({"output": output})
|
||||||
|
|
||||||
@app.get("/status")
|
@app.get("/status")
|
||||||
async def status():
|
async def status():
|
||||||
full_status_raw = await run_pm2("status")
|
full_status_raw = await run_pm2("status")
|
||||||
full_status = strip_ansi_codes(full_status_raw) # 去除颜色代码,前端显示更干净
|
full_status = strip_ansi_codes(full_status_raw) # 去除颜色代码,前端显示更干净
|
||||||
|
|
||||||
info = await get_hdmi_info()
|
info = await get_hdmi_info()
|
||||||
|
|
||||||
recent_log = await read_log_path(info["out_path"], MAX_LOG_LINES)
|
recent_log = await read_log_path(info["out_path"], MAX_LOG_LINES)
|
||||||
recent_error = await read_log_path(info["err_path"], MAX_LOG_LINES)
|
recent_error = await read_log_path(info["err_path"], MAX_LOG_LINES)
|
||||||
|
|
||||||
if info["process_status"] == "not_found":
|
if info["process_status"] == "not_found":
|
||||||
recent_log = "进程未注册到 PM2(可能被 pm2 delete 删除或从未保存)\n"
|
recent_log = "进程未注册到 PM2(可能被 pm2 delete 删除或从未保存)\n"
|
||||||
recent_error = ""
|
recent_error = ""
|
||||||
|
|
||||||
return JSONResponse({
|
return JSONResponse({
|
||||||
"raw_status": full_status,
|
"raw_status": full_status,
|
||||||
"process_status": info["process_status"],
|
"process_status": info["process_status"],
|
||||||
"recent_log": recent_log,
|
"recent_log": recent_log,
|
||||||
"recent_error": recent_error,
|
"recent_error": recent_error,
|
||||||
})
|
})
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def index():
|
async def index():
|
||||||
return FileResponse(BASE_DIR / "index.html")
|
return FileResponse(BASE_DIR / "index.html")
|
||||||
|
|||||||
Reference in New Issue
Block a user