fix: close data integrity and hot-reload review findings

Backfill same-day snapshots for newly added or restored videos, preserve successful fan snapshots on resume, persist incomplete state before forced re-runs, and split collection completion from backup completion so failed backups can retry without API calls. Reject missing data payloads instead of treating them as empty folders, guard against mass-unfav on suspicious empty remote folders, rebuild Bilibili clients when credentials change, recompute primary folder ownership from active relations with per-folder fav_time, preserve original titles for dropped invalid videos, poll config every 60 seconds, and fix favsort verify interval and mixed resource types.
This commit is contained in:
kairee committed 2026-10-31 20:58:36 +08:00
1 parent 178ab64b18
commit 6a21b5d905
21 files changed
+895 -82

No files matched your search

+16 -10
View File
@@ -262,6 +262,20 @@ class BilibiliClient:
raise self._api_error(payload)
return payload
@staticmethod
def _data_list(payload: Mapping[str, Any], name: str) -> list[Any]:
"""区分“真实空数组”与“协议缺字段”。
实测空收藏夹返回 ``{"code":0,"data":[]}``;缺少 ``data`` 或为 ``null``
都不是已知契约,必须中止,避免被误判成空收藏夹后批量改状态。
"""
if "data" not in payload:
raise BilibiliProtocolError(f"{name} 响应缺少 data 字段")
data = payload["data"]
if not isinstance(data, list):
raise BilibiliProtocolError(f"{name} data 不是数组")
return data
# ------------------------------------------------------------ fav
def fav_ids(self, media_id: int) -> list[FavId]:
@@ -269,11 +283,7 @@ class BilibiliClient:
f"{BASE}/x/v3/fav/resource/ids?media_id={int(media_id)}&platform=web"
)
payload = self.get_json(url)
data = payload.get("data")
if data is None:
data = []
if not isinstance(data, list):
raise BilibiliProtocolError("fav/resource/ids data 不是数组")
data = self._data_list(payload, "fav/resource/ids")
out: list[FavId] = []
for index, item in enumerate(data):
if not isinstance(item, Mapping):
@@ -320,11 +330,7 @@ class BilibiliClient:
f"&platform=web&folder_mid={fmid}&folder_id={int(media_id)}"
)
payload = self.get_json(url)
data = payload.get("data")
if data is None:
data = []
if not isinstance(data, list):
raise BilibiliProtocolError("fav/resource/infos data 不是数组")
data = self._data_list(payload, "fav/resource/infos")
chunk_set = set(chunk)
for item in data:
if not isinstance(item, Mapping):