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

+23 -2
View File
@@ -346,6 +346,7 @@ def cmd_plan(cfg, args):
"current_order": cur,
"target_order": tgt,
"moves": moves,
"types": {v["avid"]: v["type"] for v in applied},
"titles": {
v["avid"]: {
"title": v["title"],
@@ -395,8 +396,12 @@ def cmd_run(cfg, args):
plan = load_plan(media_id)
target = plan["target_order"]
titles = plan["titles"]
types = plan.get("types") or {}
sc = cfg["sleep"]
verify_every = args.verify_every
if verify_every < 1:
print("[run] --verify-every 必须 >= 1;未执行任何写操作")
return 2
print("[run] 读取当前顺序 ...")
actual = [x["id"] for x in fetch_ids(cfg, media_id)]
@@ -428,7 +433,13 @@ def cmd_run(cfg, args):
long_after = random.randint(sc["long_every_min"], sc["long_every_max"])
for i, m in enumerate(moves, 1):
body = {"media_id": media_id, "sort": f"{m['pred']}:2:{m['moved']}:2", "csrf": cfg["csrf"]}
pred_type = types.get(m["pred"], 2)
moved_type = types.get(m["moved"], 2)
body = {
"media_id": media_id,
"sort": f"{m['pred']}:{pred_type}:{m['moved']}:{moved_type}",
"csrf": cfg["csrf"],
}
print(f"[run] {i}/{total} {titles[str(m['moved'])]['title'][:30]} "
f"-> after {m['pred']}", end=" ", flush=True)
resp = post_form(cfg, API["sort"].format(base=BASE), body)
@@ -541,6 +552,16 @@ COMMAND_HELP = {
}
def _positive_int(value):
try:
number = int(value)
except (TypeError, ValueError) as exc:
raise argparse.ArgumentTypeError("必须是整数") from exc
if number < 1:
raise argparse.ArgumentTypeError("必须 >= 1")
return number
def build_parser():
p = argparse.ArgumentParser(
prog="favsort",
@@ -565,7 +586,7 @@ def build_parser():
pr = sub.add_parser("run", help=COMMAND_HELP["run"])
pr.add_argument("media_id", type=int, help="收藏夹 media_id")
pr.add_argument("--verify-every", type=int, default=1, metavar="N",
pr.add_argument("--verify-every", type=_positive_int, default=1, metavar="N",
help="每 N 步回读校验一次(默认 1;前 3 步始终逐步校验)")
pv = sub.add_parser("verify", help=COMMAND_HELP["verify"])