diff --git a/README.md b/README.md index ad39e0d..1f4ebe0 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,6 @@ ```bash cd electron pnpm install -pnpm run prepare-easytier # 下载 Windows easytier 二进制(可选,见 package.json) pnpm dev ``` diff --git a/config/URL_config.ini b/config/URL_config.ini index 8c2c124..8491383 100644 --- a/config/URL_config.ini +++ b/config/URL_config.ini @@ -1 +1 @@ -https://live.douyin.com/116645105013,主播: 张火火摆摊日记 +https://live.douyin.com/11664523242 \ No newline at end of file diff --git a/config/easytier_network.example.json b/config/easytier_network.example.json deleted file mode 100644 index 6219949..0000000 --- a/config/easytier_network.example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "networkName": "lr_自动生成", - "networkSecret": "自动生成请勿手填", - "peerUrls": [] -} diff --git a/config/relay_pro/url_config.ch_1774436412.ini b/config/relay_pro/url_config.ch_1774436412.ini index f9fe112..5bef370 100644 --- a/config/relay_pro/url_config.ch_1774436412.ini +++ b/config/relay_pro/url_config.ch_1774436412.ini @@ -1,3 +1 @@ -# 每行一个抖音直播间地址,例如: -# https://live.douyin.com/123456789 - +https://live.douyin.com/11664523242 \ No newline at end of file diff --git a/config/relay_pro/url_config.ch_1774438395.ini b/config/relay_pro/url_config.ch_1774438395.ini deleted file mode 100644 index 2152f61..0000000 --- a/config/relay_pro/url_config.ch_1774438395.ini +++ /dev/null @@ -1,2 +0,0 @@ -https://live.douyin.com/28358181657,主播: 罗瑞和夜市_印度飞饼_(阿泽) - diff --git a/config/relay_pro/url_config.ch_default.ini b/config/relay_pro/url_config.ch_default.ini deleted file mode 100644 index a6f92c9..0000000 --- a/config/relay_pro/url_config.ch_default.ini +++ /dev/null @@ -1 +0,0 @@ -# 每行一个抖音直播间地址 diff --git a/config/relay_pro/youtube.ch_default.ini b/config/relay_pro/youtube.ch_1774436412.ini similarity index 70% rename from config/relay_pro/youtube.ch_default.ini rename to config/relay_pro/youtube.ch_1774436412.ini index 02073bb..4dd77ee 100644 --- a/config/relay_pro/youtube.ch_default.ini +++ b/config/relay_pro/youtube.ch_1774436412.ini @@ -1,5 +1,5 @@ [youtube] -key = ue78-1c3e-mr9g-14mz-9r4z +key = 112313123124124124124124124df fast_audio = 1 output_resolution = 720p use_gpu = 无 diff --git a/config/relay_pro/youtube.ch_1774438395.ini b/config/relay_pro/youtube.ch_1774438395.ini deleted file mode 100644 index 8ceadcb..0000000 --- a/config/relay_pro/youtube.ch_1774438395.ini +++ /dev/null @@ -1,7 +0,0 @@ -[youtube] -key = h018-hqur-uhtv-5bt2-1x3q -fast_audio = 1 -output_resolution = 720p -use_gpu = 无 -audio_denoise = 否 - diff --git a/config/youtube.ini b/config/youtube.ini index f4ea821..fe96b16 100644 --- a/config/youtube.ini +++ b/config/youtube.ini @@ -1,5 +1,5 @@ [youtube] -key = ue78-1c3e-mr9g-14mz-9r4z +key = 112313123124124124124124124df fast_audio = 1 output_resolution = 720p use_gpu = 无 diff --git a/config/youtube_channels.json b/config/youtube_channels.json index 84bfd51..9ead28d 100644 --- a/config/youtube_channels.json +++ b/config/youtube_channels.json @@ -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", diff --git a/web2.py b/web2.py index eca11e5..016793b 100644 --- a/web2.py +++ b/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) diff --git a/web2_client_overview.py b/web2_client_overview.py index e0b601f..a4351df 100644 --- a/web2_client_overview.py +++ b/web2_client_overview.py @@ -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 获取", } diff --git a/web2_host_caps.py b/web2_host_caps.py index 0d2e911..7adbe64 100644 --- a/web2_host_caps.py +++ b/web2_host_caps.py @@ -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(), + } diff --git a/web2_pocketbase.py b/web2_pocketbase.py new file mode 100644 index 0000000..68ac1c6 --- /dev/null +++ b/web2_pocketbase.py @@ -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