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

+21
View File
@@ -13,6 +13,7 @@ sys.stdout.reconfigure(encoding="utf-8")
from reaction_board.bilibili.auth import ( # noqa: E402
cookie_value,
cookies_from_set_cookie,
make_headers,
merge_cookie,
parse_cookie,
sessdata_expiry,
@@ -78,10 +79,30 @@ def test_qr_png():
assert path.read_bytes().startswith(b"\x89PNG\r\n\x1a\n")
def test_make_headers_matches_raw_request():
headers = make_headers({"cookie": "SESSDATA=x; buvid3=y"}, auth=False)
assert headers["origin"] == "https://space.bilibili.com"
assert headers["referer"] == "https://space.bilibili.com/"
assert headers["priority"] == "u=1, i"
assert headers["user-agent"].endswith("Chrome/155.0.0.0 Safari/537.36")
assert headers["sec-ch-ua"].startswith('"Google Chrome";v="155"')
assert "SESSDATA" not in headers["cookie"]
assert "buvid3=y" in headers["cookie"]
full = make_headers({"cookie": "SESSDATA=x"}, auth=True)
assert full["cookie"] == "SESSDATA=x"
custom = make_headers(
{"cookie": ""}, referer="https://space.bilibili.com/1/favlist?fid=2"
)
assert custom["referer"] == "https://space.bilibili.com/1/favlist?fid=2"
def main():
test_cookie_helpers()
test_finish_updates_only_selected_account_and_rolls_back()
test_qr_png()
test_make_headers_matches_raw_request()
print("OK: 双账号 auth Cookie 合并、写回隔离与回滚测试全部通过")
+4
View File
@@ -99,6 +99,10 @@ def test_fav_ids_parse_and_anonymous_header():
assert "media_id=3700023351" in call["url"]
assert "SESSDATA" not in call["headers"]["cookie"]
assert "buvid3=device" in call["headers"]["cookie"]
assert call["headers"]["origin"] == "https://space.bilibili.com"
assert call["headers"]["priority"] == "u=1, i"
assert "Chrome/155.0.0.0" in call["headers"]["user-agent"]
assert "fid=3700023351" in call["headers"]["referer"]
def test_anonymous_failure_falls_back_to_full_cookie():
+145
View File
@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
"""httpx 传输层契约测试(不联网): uv run python tests/test_httpx_transport.py"""
import os
import sys
import httpx
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.stdout.reconfigure(encoding="utf-8")
from reaction_board.bilibili.client import ( # noqa: E402
HttpxTransport,
TransportError,
)
from reaction_board.notify.telegram import ( # noqa: E402
HttpxTelegramTransport,
TelegramError,
)
def test_bilibili_httpx_transport():
captured = {}
def handler(request: httpx.Request) -> httpx.Response:
captured["method"] = request.method
captured["url"] = str(request.url)
captured["ua"] = request.headers["user-agent"]
return httpx.Response(
200,
json={"code": 0, "message": "OK", "data": []},
headers={"content-type": "application/json"},
)
client = httpx.Client(transport=httpx.MockTransport(handler))
transport = HttpxTransport(client=client)
try:
response = transport.request(
"GET",
"https://api.bilibili.com/x/test",
{"user-agent": "UA-TEST"},
data=None,
timeout=30,
)
finally:
transport.close()
assert response.status == 200
assert response.json()["code"] == 0
assert captured == {
"method": "GET",
"url": "https://api.bilibili.com/x/test",
"ua": "UA-TEST",
}
def test_bilibili_httpx_transport_maps_errors():
def handler(request):
raise httpx.ConnectError("boom")
transport = HttpxTransport(
client=httpx.Client(transport=httpx.MockTransport(handler))
)
try:
transport.request("GET", "https://api.bilibili.com/x/test", {})
except TransportError as exc:
assert "boom" in str(exc)
else:
raise AssertionError("httpx 连接错误应映射为 TransportError")
finally:
transport.close()
def test_telegram_httpx_transport():
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, json={"ok": True, "result": [{"update_id": 1}]})
transport = HttpxTelegramTransport(
client=httpx.Client(transport=httpx.MockTransport(handler))
)
try:
payload = transport.post(
"https://api.telegram.org/botx/getUpdates", {"timeout": 0}, 30
)
finally:
transport.close()
assert payload["ok"] is True
assert payload["result"][0]["update_id"] == 1
def test_telegram_httpx_transport_error_shapes():
def bad_json(request):
return httpx.Response(
400, json={"ok": False, "description": "query is too old"}
)
transport = HttpxTelegramTransport(
client=httpx.Client(transport=httpx.MockTransport(bad_json))
)
try:
payload = transport.post("https://api.telegram.org/botx/x", {}, 30)
finally:
transport.close()
assert payload["description"] == "query is too old"
def html_error(request):
return httpx.Response(500, text="bad gateway")
transport = HttpxTelegramTransport(
client=httpx.Client(transport=httpx.MockTransport(html_error))
)
try:
transport.post("https://api.telegram.org/botx/x", {}, 30)
except TelegramError as exc:
assert "500" in str(exc)
else:
raise AssertionError("非 JSON 错误应抛出 TelegramError")
finally:
transport.close()
def network_error(request):
raise httpx.ConnectError("offline")
transport = HttpxTelegramTransport(
client=httpx.Client(transport=httpx.MockTransport(network_error))
)
try:
transport.post("https://api.telegram.org/botx/x", {}, 30)
except TelegramError as exc:
assert "offline" in str(exc)
else:
raise AssertionError("网络错误应抛出 TelegramError")
finally:
transport.close()
def main():
test_bilibili_httpx_transport()
test_bilibili_httpx_transport_maps_errors()
test_telegram_httpx_transport()
test_telegram_httpx_transport_error_shapes()
print("OK: httpx B 站与 Telegram 传输层测试全部通过")
if __name__ == "__main__":
main()