feat(http): use httpx with pooled connections and browser headers

Add httpx and route Bilibili and Telegram requests through pooled clients with keepalive and explicit timeouts. Align Bilibili headers with data/raw-request.txt: Chrome 155 UA, sec-ch-ua, space.bilibili.com origin, folder-specific referer, and priority header. Close replaced clients when credentials change. Add transport contract tests and live read-only validation for nav, folder sync, WBI search, and Telegram getUpdates.
This commit is contained in:
kairee committed 2026-10-31 22:28:32 +08:00
1 parent 71a0e8c1d6
commit 5b22ed1c5d
12 files changed
+430 -25

No files matched your search

+51 -1
View File
@@ -10,6 +10,8 @@ import urllib.parse
import urllib.request
from typing import Any, Mapping, Protocol, Sequence
import httpx
MAX_CANDIDATES_PER_MESSAGE = 3
@@ -64,6 +66,49 @@ def format_candidate(candidate: Mapping[str, Any]) -> str:
)
class HttpxTelegramTransport:
"""基于 httpx 连接池的 Telegram 传输。"""
def __init__(
self,
*,
timeout: float = 30,
client: httpx.Client | None = None,
) -> None:
self.timeout = timeout
self._client = client or httpx.Client(
timeout=httpx.Timeout(timeout, connect=10),
limits=httpx.Limits(
max_keepalive_connections=5,
keepalive_expiry=30,
),
follow_redirects=False,
trust_env=True,
)
def post(self, url: str, data: Mapping[str, Any], timeout: float = 30) -> Any:
try:
response = self._client.post(url, data=dict(data), timeout=timeout)
except httpx.TransportError as exc:
raise TelegramError(str(exc)) from exc
try:
payload = response.json()
except ValueError as exc:
raise TelegramError(
f"Telegram HTTP {response.status_code}: {response.text[:200]}"
) from exc
if response.status_code >= 400 and not (
isinstance(payload, Mapping) and "ok" in payload
):
raise TelegramError(
f"Telegram HTTP {response.status_code}: {str(payload)[:200]}"
)
return payload
def close(self) -> None:
self._client.close()
class TelegramClient:
def __init__(
self,
@@ -81,11 +126,16 @@ class TelegramClient:
raise ValueError("telegram.chat_id 不能为空")
self.token = token
self.chat_id = int(chat_id)
self.transport = transport or UrllibTelegramTransport()
self.transport = transport or HttpxTelegramTransport()
self.timeout = timeout
self.poll_timeout = poll_timeout
self.logger = logger
def close(self) -> None:
close = getattr(self.transport, "close", None)
if callable(close):
close()
def _call(
self,
method: str,