feat(p1): add daily snapshots and fan tracking

Implement P1: seven-metric view snapshots, relation/stat fan snapshots, failure placeholders, resume by daily completion markers, Cookie login warnings, daily SQLite backups, Shanghai daily scheduling, and CLI snapshot/auth-check commands. Real read-only run captured 795/795 videos successfully over 1124s with a verified daily backup.
This commit is contained in:
kairee committed 2026-10-31 19:19:24 +08:00
1 parent ee289bfa56
commit 5c2ab7b187
20 files changed
+1680 -54

No files matched your search

+92
View File
@@ -25,8 +25,13 @@ from fixtures import ( # noqa: E402
FOLDERS_PAYLOAD,
IDS_PAYLOAD,
INVALID_INFO,
NAV_PAYLOAD,
RELATION_PAYLOAD,
VALID_INFO,
VIEW_PAYLOAD,
VIEW_UNAVAILABLE,
info_for,
view_for,
)
@@ -274,6 +279,88 @@ def test_timeout_and_method_forwarded():
assert call["timeout"] == 30
def test_view_seven_metrics():
transport = QueueTransport([http_json(VIEW_PAYLOAD)])
client, _ = make_client(transport)
stat = client.view(117530286889638)
assert stat.aid == 117530286889638
assert stat.bvid == "BV1xvxi6LEZD"
assert stat.metrics() == {
"view": 819, "like": 44, "favorite": 43, "coin": 9,
"share": 1, "reply": 16, "danmaku": 7,
}
assert "aid=117530286889638" in transport.calls[0]["url"]
def test_view_unavailable_and_malformed():
transport = QueueTransport([
http_json(VIEW_UNAVAILABLE), http_json(VIEW_UNAVAILABLE),
])
client, _ = make_client(transport)
try:
client.view(1)
except BilibiliAPIError as exc:
assert exc.code == 62012
else:
raise AssertionError("不可访问视频应抛业务错误")
payload = view_for(1)
payload["data"]["stat"].pop("coin")
bad = http_json(payload)
transport = QueueTransport([bad, bad])
client, _ = make_client(transport)
try:
client.view(1)
except BilibiliProtocolError as exc:
assert "coin" in str(exc)
else:
raise AssertionError("缺少 stat 字段应报协议错误")
def test_relation_stat():
transport = QueueTransport([http_json(RELATION_PAYLOAD)])
client, _ = make_client(transport)
fan = client.relation_stat(37754047)
assert fan.mid == 37754047
assert fan.follower == 2767268
assert fan.following == 95
assert "vmid=37754047" in transport.calls[0]["url"]
def test_check_login():
transport = QueueTransport([http_json(NAV_PAYLOAD)])
client, _ = make_client(transport)
state = client.check_login()
assert state.is_login is True
assert state.mid == 28802351
assert state.uname == "冷萃编译器"
assert "SESSDATA=secret" in transport.calls[0]["headers"]["cookie"]
transport = QueueTransport([http_json({
"code": 0, "message": "0", "ttl": 1,
"data": {"isLogin": False, "mid": 0, "uname": ""},
})])
client, _ = make_client(transport)
state = client.check_login()
assert state.is_login is False and state.mid == 0
transport = QueueTransport([http_json({"code": -101, "message": "账号未登录", "data": None})])
client, _ = make_client(transport)
state = client.check_login()
assert state.is_login is False
assert state.code == -101
assert "账号未登录" in state.message
def test_check_login_without_cookie_makes_no_request():
transport = QueueTransport([])
client, _ = make_client(transport, cookie="")
state = client.check_login()
assert state.is_login is False
assert state.message == "未配置 cookie"
assert transport.calls == []
def main():
test_fav_ids_parse_and_anonymous_header()
test_anonymous_failure_falls_back_to_full_cookie()
@@ -288,6 +375,11 @@ def main():
test_fav_infos_chunk_size_validation()
test_pacer_delays_after_first_request()
test_timeout_and_method_forwarded()
test_view_seven_metrics()
test_view_unavailable_and_malformed()
test_relation_stat()
test_check_login()
test_check_login_without_cookie_makes_no_request()
print("OK: B 站只读客户端契约与重试测试全部通过")