's'
This commit is contained in:
@@ -44,7 +44,6 @@
|
||||
```bash
|
||||
cd electron
|
||||
pnpm install
|
||||
pnpm run prepare-easytier # 下载 Windows easytier 二进制(可选,见 package.json)
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
https://live.douyin.com/116645105013,主播: 张火火摆摊日记
|
||||
https://live.douyin.com/11664523242
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"networkName": "lr_自动生成",
|
||||
"networkSecret": "自动生成请勿手填",
|
||||
"peerUrls": []
|
||||
}
|
||||
@@ -1,3 +1 @@
|
||||
# 每行一个抖音直播间地址,例如:
|
||||
# https://live.douyin.com/123456789
|
||||
|
||||
https://live.douyin.com/11664523242
|
||||
@@ -1,2 +0,0 @@
|
||||
https://live.douyin.com/28358181657,主播: 罗瑞和夜市_印度飞饼_(阿泽)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# 每行一个抖音直播间地址
|
||||
@@ -1,5 +1,5 @@
|
||||
[youtube]
|
||||
key = ue78-1c3e-mr9g-14mz-9r4z
|
||||
key = 112313123124124124124124124df
|
||||
fast_audio = 1
|
||||
output_resolution = 720p
|
||||
use_gpu = 无
|
||||
@@ -1,7 +0,0 @@
|
||||
[youtube]
|
||||
key = h018-hqur-uhtv-5bt2-1x3q
|
||||
fast_audio = 1
|
||||
output_resolution = 720p
|
||||
use_gpu = 无
|
||||
audio_denoise = 否
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[youtube]
|
||||
key = ue78-1c3e-mr9g-14mz-9r4z
|
||||
key = 112313123124124124124124124df
|
||||
fast_audio = 1
|
||||
output_resolution = 720p
|
||||
use_gpu = 无
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
"version": 1,
|
||||
"autoManaged": true,
|
||||
"channels": [
|
||||
{
|
||||
"id": "ch_default",
|
||||
"name": "默认线路",
|
||||
"douyinUrl": "",
|
||||
"youtubeKey": "ue78-1c3e-mr9g-14mz-9r4z"
|
||||
},
|
||||
{
|
||||
"id": "ch_1774436412",
|
||||
"name": "频道 2",
|
||||
"douyinUrl": "",
|
||||
"youtubeKey": ""
|
||||
"youtubeKey": "112313123124124124124124124df"
|
||||
},
|
||||
{
|
||||
"id": "ch_1774436414",
|
||||
@@ -98,12 +92,6 @@
|
||||
"douyinUrl": "",
|
||||
"youtubeKey": ""
|
||||
},
|
||||
{
|
||||
"id": "ch_1774438395",
|
||||
"name": "频道 17",
|
||||
"douyinUrl": "",
|
||||
"youtubeKey": "h018-hqur-uhtv-5bt2-1x3q"
|
||||
},
|
||||
{
|
||||
"id": "ch_1774439571",
|
||||
"name": "频道 18",
|
||||
|
||||
25
web2.py
25
web2.py
@@ -46,8 +46,9 @@ from web2_relay_pro import (
|
||||
write_url_config_for_channel,
|
||||
)
|
||||
from web2_youtube_ini import parse_youtube_ini, serialize_youtube_ini
|
||||
from web2_host_caps import gpu_status_payload
|
||||
from web2_host_caps import gpu_status_payload, machine_summary_payload
|
||||
from web2_douyin_preview import fetch_douyin_anchor_name
|
||||
from web2_pocketbase import REQUIRE_PB, pb_auth_refresh_and_validate
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -243,7 +244,7 @@ register_wechat_chatbot_routes(app, BASE_DIR)
|
||||
|
||||
@app.get("/client/overview")
|
||||
async def client_overview():
|
||||
"""Electron 工作台:频道列表、微信摘要(EasyTier 节点由主进程 easytier-cli 查询)。"""
|
||||
"""Electron 工作台:频道列表、微信摘要。"""
|
||||
return build_client_overview(CONFIG_DIR, BASE_DIR)
|
||||
|
||||
|
||||
@@ -385,6 +386,12 @@ async def host_gpu_status():
|
||||
return gpu_status_payload()
|
||||
|
||||
|
||||
@app.get("/host/machine_summary")
|
||||
async def host_machine_summary():
|
||||
"""本机 hostname / OS / 架构等,供会员遥测写入 PocketBase。"""
|
||||
return machine_summary_payload()
|
||||
|
||||
|
||||
@app.post("/douyin/room_previews")
|
||||
async def douyin_room_previews(request: Request):
|
||||
try:
|
||||
@@ -580,7 +587,12 @@ async def _stop_legacy_youtube_if_online() -> None:
|
||||
|
||||
# ---------------- PM2 控制路由 ----------------
|
||||
@app.get("/start")
|
||||
async def start(process: str = Query(..., description="进程名")):
|
||||
async def start(request: Request, process: str = Query(..., description="进程名")):
|
||||
if REQUIRE_PB:
|
||||
tok = (request.headers.get("x-pb-token") or "").strip()
|
||||
ok, msg, _ = await pb_auth_refresh_and_validate(tok)
|
||||
if not ok:
|
||||
return JSONResponse({"output": msg}, status_code=403)
|
||||
if not process_valid(process):
|
||||
return JSONResponse({"output": f"无效进程名: {process}"}, status_code=400)
|
||||
cid = relay_channel_id_from_pm2_name(process)
|
||||
@@ -633,7 +645,12 @@ async def stop(process: str = Query(..., description="进程名")):
|
||||
|
||||
|
||||
@app.get("/restart")
|
||||
async def restart(process: str = Query(..., description="进程名")):
|
||||
async def restart(request: Request, process: str = Query(..., description="进程名")):
|
||||
if REQUIRE_PB:
|
||||
tok = (request.headers.get("x-pb-token") or "").strip()
|
||||
ok, msg, _ = await pb_auth_refresh_and_validate(tok)
|
||||
if not ok:
|
||||
return JSONResponse({"output": msg}, status_code=403)
|
||||
if not process_valid(process):
|
||||
return JSONResponse({"output": f"无效进程名: {process}"}, status_code=400)
|
||||
cid = relay_channel_id_from_pm2_name(process)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
工作台 /client/overview:YouTube 频道摘要、微信绑定摘要(EasyTier 节点数由 Electron 主进程补充)。
|
||||
工作台 /client/overview:YouTube 频道摘要、微信绑定摘要。
|
||||
|
||||
多路转播语义:每条线路为「一个抖音直播间 ↔ 一个 YouTube 串流」一对一;
|
||||
禁止同一抖音直播间同时向多个 YouTube 频道推流;禁止多条线路共用同一串流密钥(避免串台)。
|
||||
@@ -251,5 +251,4 @@ def build_client_overview(config_dir: Path, base_dir: Path) -> dict[str, Any]:
|
||||
"relayValidationErrors": relay_errors,
|
||||
"relayNotes": relay_notes,
|
||||
"wechat": wechat,
|
||||
"easyTierPeersNote": "peer 数量由 Electron 调用 easytier-cli peer list 获取",
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import platform
|
||||
import socket
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
@@ -39,3 +40,14 @@ def gpu_status_payload() -> dict[str, Any]:
|
||||
"nvidia_detected": nv,
|
||||
"nvenc_available": enc,
|
||||
}
|
||||
|
||||
|
||||
def machine_summary_payload() -> dict[str, Any]:
|
||||
"""供远程 App 遥测:本机标识与系统信息。"""
|
||||
return {
|
||||
"hostname": socket.gethostname(),
|
||||
"os": platform.system(),
|
||||
"os_release": platform.release(),
|
||||
"machine": platform.machine(),
|
||||
"python": platform.python_version(),
|
||||
}
|
||||
|
||||
39
web2_pocketbase.py
Normal file
39
web2_pocketbase.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""PocketBase:校验用户令牌与 live_allowed;供 /start /restart 可选强制。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Tuple
|
||||
|
||||
import httpx
|
||||
|
||||
PB_URL = os.environ.get("WEB2_PB_URL", "https://pocketbase.hackrobot.cn").rstrip("/")
|
||||
REQUIRE_PB = os.environ.get("WEB2_REQUIRE_PB_AUTH", "").strip().lower() in ("1", "true", "yes")
|
||||
|
||||
|
||||
async def pb_auth_refresh_and_validate(token: str) -> Tuple[bool, str, dict[str, Any]]:
|
||||
"""
|
||||
使用 auth-refresh 校验令牌并拉取最新用户记录。
|
||||
返回 (ok, error_message, record)
|
||||
"""
|
||||
t = (token or "").strip()
|
||||
if not t:
|
||||
return False, "缺少 PocketBase 授权(请在手机端登录会员账号)", {}
|
||||
url = f"{PB_URL}/api/collections/users/auth-refresh"
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=20.0) as client:
|
||||
r = await client.post(url, headers={"Authorization": f"Bearer {t}"})
|
||||
except Exception as e:
|
||||
return False, f"PocketBase 不可达: {e}", {}
|
||||
try:
|
||||
data = r.json()
|
||||
except Exception:
|
||||
return False, "PocketBase 响应无效", {}
|
||||
if r.status_code != 200:
|
||||
msg = str(data.get("message") or "授权无效或已过期")
|
||||
return False, msg, {}
|
||||
rec = data.get("record") or {}
|
||||
if not isinstance(rec, dict):
|
||||
return False, "用户记录异常", {}
|
||||
if rec.get("live_allowed") is False:
|
||||
return False, "账号已被管理员停用远程直播权限", {}
|
||||
return True, "", rec
|
||||
Reference in New Issue
Block a user