refactor(sync): drop favorite-folder tracking and deal
This commit is contained in:
1 parent
62abbd7ea1
commit
6c4287b43a
21 files changed
+1390
-2766
No files matched your search
+40
-96
@@ -2,7 +2,7 @@
|
||||
"""看板命令行入口。
|
||||
|
||||
python app.py init # 建库 / 迁移 / 写入黑名单种子(离线)
|
||||
python app.py sync # 收藏夹只读同步(首次导入或增量)
|
||||
python app.py ingest # 手动入库 B 站视频链接 / BV 号 / av 号(只写本地库)
|
||||
python app.py snapshot # 采集当日七项快照与粉丝数
|
||||
python app.py discover # 执行一次搜索发现并写入候选池
|
||||
python app.py candidates # 查看候选池(离线)
|
||||
@@ -10,6 +10,7 @@
|
||||
python app.py run # 按配置周期常驻运行(--once 只跑一次)
|
||||
python app.py status # 查看库内状态(离线)
|
||||
|
||||
收藏夹重排序使用独立的 ``favsort.py``(唯一使用 ``bilibili.write`` 的功能)。
|
||||
P2 起 ``python app.py`` 将在此基础上启动本地看板服务。
|
||||
"""
|
||||
|
||||
@@ -27,7 +28,6 @@ from .bilibili.auth_flow import AuthService
|
||||
from .bilibili.client import BilibiliClient, Pacer
|
||||
from .bilibili.provider import ClientProvider
|
||||
from .bilibili.search import SearchClient
|
||||
from .bilibili.write_client import BilibiliWriteClient
|
||||
from .config import ConfigError, ConfigStore
|
||||
from .database import Database
|
||||
from .logging_json import JsonLogger
|
||||
@@ -35,8 +35,7 @@ from .notify.telegram import TelegramClient
|
||||
from .repository import Repository
|
||||
from .scheduler import ScheduledTask, Scheduler
|
||||
from .services.discovery import DiscoveryService
|
||||
from .services.favorites import FavoriteActionService
|
||||
from .services.folder_sync import FolderSyncService
|
||||
from .services.library import LibraryService
|
||||
from .services.snapshot import SnapshotService
|
||||
from .services.telegram_poller import TelegramPoller
|
||||
from .services.up_profile import UpProfileService
|
||||
@@ -66,20 +65,6 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
sub = parser.add_subparsers(dest="command", required=False)
|
||||
|
||||
sub.add_parser("init", parents=[common], help="建库、迁移并写入黑名单种子")
|
||||
sync = sub.add_parser("sync", parents=[common], help="同步已配置的年度收藏夹")
|
||||
sync.add_argument(
|
||||
"--full", action="store_true", help="强制全量刷新元数据(忽略 6 小时周期)"
|
||||
)
|
||||
sync.add_argument(
|
||||
"--allow-empty-folder",
|
||||
action="store_true",
|
||||
help="允许把远端 0 条视为真实清空(默认按异常跳过差集)",
|
||||
)
|
||||
sync.add_argument(
|
||||
"--no-capture-followers",
|
||||
action="store_true",
|
||||
help="跳过同步末尾的 UP 粉丝采集(改用 backfill-followers 单独回填)",
|
||||
)
|
||||
snapshot = sub.add_parser(
|
||||
"snapshot", parents=[common], help="采集当日七项快照与粉丝数"
|
||||
)
|
||||
@@ -103,6 +88,14 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
default=None,
|
||||
help="只查看指定决策状态的候选",
|
||||
)
|
||||
ingest = sub.add_parser(
|
||||
"ingest", parents=[common],
|
||||
help="手动入库:B 站视频链接、BV 号或 av 号(只写本地库)",
|
||||
)
|
||||
ingest.add_argument(
|
||||
"refs", nargs="+", metavar="REF",
|
||||
help="视频链接、BV 号(BV…)、av 号(av12345)或含它们的字符串",
|
||||
)
|
||||
auth = sub.add_parser("auth", parents=[common], help="扫码授权并更新指定账号 Cookie")
|
||||
auth.add_argument("--account", choices=("read", "write"), default="read",
|
||||
help="要授权的账号角色(默认 read)")
|
||||
@@ -112,15 +105,10 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
auth_check = sub.add_parser("auth-check", parents=[common], help="检查账号登录态")
|
||||
auth_check.add_argument("--account", choices=("read", "write", "all"), default="read",
|
||||
help="要检查的账号角色(默认 read)")
|
||||
run = sub.add_parser("run", parents=[common], help="按配置周期常驻运行同步与快照")
|
||||
run = sub.add_parser("run", parents=[common], help="按配置周期常驻运行发现与快照")
|
||||
run.add_argument(
|
||||
"--once", action="store_true", help="只执行一次到期任务后退出"
|
||||
)
|
||||
run.add_argument(
|
||||
"--allow-empty-folder",
|
||||
action="store_true",
|
||||
help="允许把远端 0 条视为真实清空(默认按异常跳过差集)",
|
||||
)
|
||||
serve = sub.add_parser("serve", parents=[common], help="启动只读本地看板(不运行调度)")
|
||||
serve.add_argument(
|
||||
"--preview", action="store_true",
|
||||
@@ -209,32 +197,30 @@ def cmd_init(args, logger: JsonLogger) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_sync(args, logger: JsonLogger) -> int:
|
||||
store = ConfigStore(args.config, require_auth=True)
|
||||
def cmd_ingest(args, logger: JsonLogger) -> int:
|
||||
store = ConfigStore(args.config, require_auth=False)
|
||||
cfg = store.get()
|
||||
db = _open_db(cfg, logger)
|
||||
try:
|
||||
repo = Repository(db)
|
||||
client = _client(cfg.bilibili.read, logger)
|
||||
service = FolderSyncService(
|
||||
store,
|
||||
repo,
|
||||
client,
|
||||
logger=logger,
|
||||
allow_empty_folders=args.allow_empty_folder,
|
||||
capture_followers=not getattr(args, "no_capture_followers", False),
|
||||
service = LibraryService(
|
||||
store, repo, lambda: client, logger=logger
|
||||
)
|
||||
started = int(time.time())
|
||||
summary = service.run(full_refresh=args.full)
|
||||
payload = summary.as_dict()
|
||||
text = " ".join(args.refs)
|
||||
result = service.ingest_text(text)
|
||||
payload = result.as_dict()
|
||||
payload["database"] = str(cfg.storage.database_path)
|
||||
payload["counts"] = repo.counts()
|
||||
_record_task(
|
||||
repo, "folder_sync", ok=summary.ok, started_at=started,
|
||||
finished_at=int(time.time()), payload=payload,
|
||||
repo, "ingest", ok=bool(result.results) and not any(
|
||||
item.state == "error" for item in result.results
|
||||
),
|
||||
payload=payload,
|
||||
)
|
||||
_print(payload)
|
||||
return 0 if summary.ok else 1
|
||||
return 0 if result.results and not any(
|
||||
item.state == "error" for item in result.results
|
||||
) else 1
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -403,7 +389,9 @@ def cmd_run(args, logger: JsonLogger, *, serve_web: bool = False) -> int:
|
||||
web_server = None
|
||||
try:
|
||||
repo = Repository(db)
|
||||
folder_provider = ClientProvider(
|
||||
if cfg.up_blacklist_seed:
|
||||
repo.seed_blacklist(cfg.up_blacklist_seed)
|
||||
read_provider = ClientProvider(
|
||||
store, "read", lambda credentials: _client(credentials, logger)
|
||||
)
|
||||
snapshot_provider = ClientProvider(
|
||||
@@ -413,34 +401,22 @@ def cmd_run(args, logger: JsonLogger, *, serve_web: bool = False) -> int:
|
||||
credentials, logger, pacer=Pacer(1.0, 1.6), retries=1
|
||||
),
|
||||
)
|
||||
folder_service = FolderSyncService(
|
||||
store,
|
||||
repo,
|
||||
client_provider=folder_provider,
|
||||
logger=logger,
|
||||
allow_empty_folders=args.allow_empty_folder,
|
||||
)
|
||||
snapshot_service = SnapshotService(
|
||||
store, repo, client_provider=snapshot_provider, logger=logger
|
||||
)
|
||||
write_provider = ClientProvider(
|
||||
up_profile_service = UpProfileService(repo, read_provider.get, logger=logger)
|
||||
library_service = LibraryService(
|
||||
store,
|
||||
"write",
|
||||
lambda credentials: BilibiliWriteClient(
|
||||
cookie=credentials.cookie,
|
||||
csrf=credentials.csrf,
|
||||
mid=credentials.mid,
|
||||
logger=logger,
|
||||
),
|
||||
)
|
||||
favorite_service = FavoriteActionService(
|
||||
store, repo, write_provider, logger=logger
|
||||
repo,
|
||||
read_provider,
|
||||
up_profile_service=up_profile_service,
|
||||
logger=logger,
|
||||
)
|
||||
notifier = _telegram(cfg, logger)
|
||||
discovery_service = DiscoveryService(
|
||||
store,
|
||||
repo,
|
||||
SearchClient(folder_provider.get),
|
||||
SearchClient(read_provider.get),
|
||||
notifier=notifier,
|
||||
logger=logger,
|
||||
)
|
||||
@@ -513,29 +489,6 @@ def cmd_run(args, logger: JsonLogger, *, serve_web: bool = False) -> int:
|
||||
logger.info("snapshot.summary", **payload)
|
||||
return summary
|
||||
|
||||
def run_sync():
|
||||
started = int(time.time())
|
||||
try:
|
||||
summary = folder_service.run()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
_record_task(
|
||||
repo, "folder_sync", ok=False, started_at=started,
|
||||
finished_at=int(time.time()), error=str(exc),
|
||||
)
|
||||
raise
|
||||
payload = summary.as_dict()
|
||||
_record_task(
|
||||
repo, "folder_sync", ok=summary.ok, started_at=started,
|
||||
finished_at=int(time.time()), payload=payload,
|
||||
error=None if summary.ok else ";".join(summary.errors),
|
||||
)
|
||||
last["folder"] = summary
|
||||
logger.info("folder_sync.summary", **payload)
|
||||
# 新入库/恢复的视频立即补当天快照,而不是等到次日。
|
||||
if summary.added > 0:
|
||||
run_snapshot()
|
||||
return summary
|
||||
|
||||
def run_config_watch():
|
||||
before = store.revision
|
||||
store.get()
|
||||
@@ -547,11 +500,6 @@ def cmd_run(args, logger: JsonLogger, *, serve_web: bool = False) -> int:
|
||||
snapshot_at = f"{hh:02d}:{mm:02d}"
|
||||
scheduler = Scheduler(
|
||||
[
|
||||
ScheduledTask(
|
||||
"folder_sync",
|
||||
float(cfg.schedule.folder_sync_minutes * 60),
|
||||
run_sync,
|
||||
),
|
||||
ScheduledTask(
|
||||
"snapshot",
|
||||
86400.0,
|
||||
@@ -579,7 +527,7 @@ def cmd_run(args, logger: JsonLogger, *, serve_web: bool = False) -> int:
|
||||
daemon=True,
|
||||
).start()
|
||||
if notifier is not None and not args.once:
|
||||
poller = TelegramPoller(notifier, favorite_service, logger=logger)
|
||||
poller = TelegramPoller(notifier, library_service, logger=logger)
|
||||
threading.Thread(
|
||||
target=poller.run_forever,
|
||||
name="telegram-poller",
|
||||
@@ -592,18 +540,15 @@ def cmd_run(args, logger: JsonLogger, *, serve_web: bool = False) -> int:
|
||||
else:
|
||||
logger.info(
|
||||
"scheduler.start",
|
||||
folder_sync_seconds=cfg.schedule.folder_sync_minutes * 60,
|
||||
snapshot_at=snapshot_at,
|
||||
)
|
||||
scheduler.run_forever()
|
||||
|
||||
folder_summary = last.get("folder")
|
||||
snapshot_summary = last.get("snapshot")
|
||||
discover_summary = last.get("discover")
|
||||
folder_ok = folder_summary is None or getattr(folder_summary, "ok", True)
|
||||
snapshot_ok = snapshot_summary is None or getattr(snapshot_summary, "completed", True)
|
||||
discover_ok = discover_summary is None or not getattr(discover_summary, "errors", [])
|
||||
return 0 if folder_ok and snapshot_ok and discover_ok else 1
|
||||
return 0 if snapshot_ok and discover_ok else 1
|
||||
finally:
|
||||
if web_server is not None:
|
||||
try:
|
||||
@@ -703,16 +648,15 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
# 无子命令时启动完整服务:只读 Web + 调度 + Telegram。
|
||||
args.command = "run"
|
||||
args.once = False
|
||||
args.allow_empty_folder = False
|
||||
return cmd_run(args, logger, serve_web=True)
|
||||
if args.command == "serve":
|
||||
return cmd_serve(args, logger)
|
||||
if args.command == "init":
|
||||
return cmd_init(args, logger)
|
||||
if args.command == "sync":
|
||||
return cmd_sync(args, logger)
|
||||
if args.command == "snapshot":
|
||||
return cmd_snapshot(args, logger)
|
||||
if args.command == "ingest":
|
||||
return cmd_ingest(args, logger)
|
||||
if args.command == "backfill-followers":
|
||||
return cmd_backfill_followers(args, logger)
|
||||
if args.command == "discover":
|
||||
|
||||
Reference in new issue
Block a user