This commit is contained in:
2026-04-27 01:56:43 +08:00
parent dfb5fe0c89
commit d4378afbc9
2 changed files with 98 additions and 55 deletions

View File

@@ -32,6 +32,25 @@ os.environ.setdefault("TELEGRAM_WEB_UI", "1")
logger = logging.getLogger("uvicorn.error")
def web_url_prefix() -> str:
"""反代子路径时设置 WEB_URL_PREFIX=/前缀(勿尾斜杠),与 nginx location 一致。"""
return (os.getenv("WEB_URL_PREFIX") or "").strip().rstrip("/")
def with_url_prefix(path: str) -> str:
path = path if path.startswith("/") else f"/{path}"
pre = web_url_prefix()
return f"{pre}{path}" if pre else path
def app_home_url(*, needauth: bool = False) -> str:
pre = web_url_prefix()
base = f"{pre}/" if pre else "/"
if needauth:
return f"{base}?needauth=1"
return base
WEB_CONSOLE_AUTH_KEY = "web_console_authenticated"
@@ -53,7 +72,7 @@ def is_console_authed(request: Request) -> bool:
def redirect_if_console_unauthed(request: Request) -> Optional[RedirectResponse]:
if is_console_authed(request):
return None
return RedirectResponse(url="/?needauth=1", status_code=303)
return RedirectResponse(url=app_home_url(needauth=True), status_code=303)
def json_if_console_unauthed(request: Request) -> Optional[JSONResponse]:
@@ -179,6 +198,7 @@ class WebScraperService:
self.logs: deque[str] = deque(maxlen=1200)
self.continuous_task: Optional[asyncio.Task] = None
self.continuous_started_at: Optional[float] = None
self.continuous_start_lock = asyncio.Lock()
def _append(self, text: str) -> None:
ts = time.strftime("%Y-%m-%d %H:%M:%S")
@@ -425,25 +445,26 @@ class WebScraperService:
return await self.start_job(f"补抓媒体({len(channels)}个频道)", runner())
async def start_continuous(self) -> str:
await self.ensure_ready()
if self.is_continuous_running():
return "持续抓取已在运行中"
if not self.scraper.state.get("channels"):
raise ValueError("当前没有监控频道,请先添加频道。")
async with self.continuous_start_lock:
await self.ensure_ready()
if self.is_continuous_running():
return "持续抓取已在运行中"
if not self.scraper.state.get("channels"):
raise ValueError("当前没有监控频道,请先添加频道。")
async def runner():
try:
await self._run_and_capture(self.scraper.continuous_scraping())
except asyncio.CancelledError:
self._append("持续抓取任务已取消。")
raise
except Exception as e:
self._append(f"持续抓取异常:{e}")
async def runner():
try:
await self._run_and_capture(self.scraper.continuous_scraping())
except asyncio.CancelledError:
self._append("持续抓取任务已取消。")
raise
except Exception as e:
self._append(f"持续抓取异常:{e}")
self.continuous_started_at = time.time()
self.continuous_task = asyncio.create_task(runner())
self._append("已启动持续抓取(含心跳逻辑)。")
return "持续抓取已启动"
self.continuous_started_at = time.time()
self.continuous_task = asyncio.create_task(runner())
self._append("已启动持续抓取(含心跳逻辑)。")
return "持续抓取已启动"
async def stop_continuous(self) -> str:
if not self.is_continuous_running():
@@ -678,7 +699,7 @@ async def auth_console_login(request: Request):
@app.post("/auth/console/logout")
async def auth_console_logout(request: Request):
request.session.pop(WEB_CONSOLE_AUTH_KEY, None)
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.get("/api/stats/overview")
@@ -727,6 +748,9 @@ async def index(request: Request):
"account_channels": account_channels,
"console_authed": authed,
"need_auth_banner": request.query_params.get("needauth") == "1",
"app_url": with_url_prefix,
"url_prefix": web_url_prefix(),
"app_home": app_home_url(),
"connected": service.is_connected(),
"job_running": service.is_job_running(),
"job_name": service.job_name,
@@ -773,7 +797,7 @@ async def save_config(request: Request):
else:
updates[k] = str(form.get(k, "")).strip()
write_env_updates(updates)
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/start")
@@ -785,7 +809,7 @@ async def start_scraper(request: Request):
await service.ensure_ready()
except Exception as e:
service._append(f"初始化失败:{e}")
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/stop")
@@ -794,7 +818,7 @@ async def stop_scraper(request: Request):
if redir:
return redir
await service.disconnect()
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/channels/add")
@@ -807,7 +831,7 @@ async def add_channel(request: Request, channel_spec: str = Form(...)):
service._append(msg)
except Exception as e:
service._append(f"添加频道失败:{e}")
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/channels/add-selected")
@@ -820,14 +844,14 @@ async def add_channels_selected(request: Request):
ids = [str(x).strip() for x in raw_ids if str(x).strip()]
if not ids:
service._append("未选择任何频道,请在列表中勾选后再提交。")
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
for spec in ids:
try:
msg = await service.add_channel(spec)
service._append(msg)
except Exception as e:
service._append(f"添加失败({spec}{e}")
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/channels/remove")
@@ -840,7 +864,7 @@ async def remove_channel(request: Request, channel_spec: str = Form(...)):
service._append(msg)
except Exception as e:
service._append(f"移除频道失败:{e}")
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/jobs/scrape")
@@ -853,7 +877,7 @@ async def start_scrape(request: Request, selection: str = Form("all")):
service._append(msg)
except Exception as e:
service._append(f"启动抓取失败:{e}")
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/jobs/export")
@@ -866,7 +890,7 @@ async def start_export(request: Request, selection: str = Form("all")):
service._append(msg)
except Exception as e:
service._append(f"启动导出失败:{e}")
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/jobs/rescrape")
@@ -879,20 +903,24 @@ async def start_rescrape(request: Request, selection: str = Form("all")):
service._append(msg)
except Exception as e:
service._append(f"启动补抓失败:{e}")
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/jobs/continuous/start")
async def start_continuous(request: Request):
async def start_continuous_route(request: Request):
redir = redirect_if_console_unauthed(request)
if redir:
return redir
try:
msg = await service.start_continuous()
service._append(msg)
except Exception as e:
service._append(f"启动持续抓取失败:{e}")
return RedirectResponse(url="/", status_code=303)
async def _run_start_continuous() -> None:
try:
await service.start_continuous()
except Exception as e:
service._append(f"启动持续抓取失败:{e}")
asyncio.create_task(_run_start_continuous())
service._append("已接收「启动持续抓取」:正在后台连接 Telegram 并启动(请勿重复点击;进度见运行日志)。")
return RedirectResponse(url=app_home_url(), status_code=303)
@app.post("/jobs/continuous/stop")
@@ -905,7 +933,7 @@ async def stop_continuous(request: Request):
service._append(msg)
except Exception as e:
service._append(f"停止持续抓取失败:{e}")
return RedirectResponse(url="/", status_code=303)
return RedirectResponse(url=app_home_url(), status_code=303)
@app.get("/status")