# bili-fav **咻咻满 Reaction 视频数据看板与收藏夹管理工具。** 项目以本地 SQLite 视频库为可信来源:搜索发现命中的候选经 Telegram 人工确认后入库,也可以在 Telegram 私聊直接发送视频链接或 BV 号手动入库。入库后持续采集七项数据与咻咻满粉丝数,并提供本地数据看板。B 站收藏夹由人工维护,项目只用写鉴权对收藏夹做低频重排序,不再自动加入收藏夹,也不再轮询收藏夹来标记入库/出库。 > 当前状态:**P0(数据底座)、P1(快照与粉丝追踪)、P2(只读本地看板)、P3(Telegram 发现与入库)已实施,并已完成「收藏夹解耦 + 手动入库」改造。** > > 当前已可用:扫码授权、收藏夹重排序(`favsort.py`);SQLite 建库迁移与黑名单种子;每日七项视频快照、粉丝数快照、失败占位、断点续跑与日备份;搜索发现、候选池、Telegram 入库/忽略/拉黑回调与私聊手动入库;只读本地看板与可重复生成的预览数据。 > > 详细业务口径、接口验证和风险记录见 [project.md](project.md);开发架构与实施计划见 [DEVELOPMENT.md](DEVELOPMENT.md)。 ## 功能概览 | 功能 | 状态 | 说明 | |---|---|---| | 收藏夹排序 | 可用 | 同一 UP 连续,组内按发布时间倒序,组间按该 UP 最早视频发布时间倒序。唯一使用写鉴权的功能。 | | 扫码授权与 Cookie 续期 | 可用 | 终端打印 ASCII 二维码并写出 PNG;更新身份 Cookie 时保留设备指纹。 | | 视频入库 | 可用 | 搜索候选经 Telegram 确认入库;私聊发送视频链接、BV 号、av 号或 `b23.tv` 短链手动入库。只写本地库。 | | 视频日快照 | 可用(P1) | 逐条采集播放、点赞、投币、收藏、转发、评论、弹幕。 | | 粉丝追踪 | 可用(P1) | 每日记录咻咻满等配置 UP 的粉丝数。 | | 搜索发现与候选池 | 可用(P3) | 每小时按 `discover` 配置搜索并分类打分后写入候选池。 | | Telegram 通知与入库 | 可用(P3) | 入库 / 忽略 / 拉黑按钮;私聊消息手动入库;候选查看 / 删除 / 清空命令。 | | 本地数据看板 | 可用(P2/P9) | 只读总览、视频榜、UP 主榜、内容效率、视频/UP 详情、粉丝曲线、数据说明、候选池和系统状态。 | | 访问统计(PV/UV) | 可用 | 前端埋点上报,按客户端 IP 与页面去重;先落内存、每分钟批量入库;系统页展示近 30 天。 | ## 核心工作流 ```text 关键词搜索(补漏) ──► 候选池 ──► Telegram「入库」──┐ ├──► 本地视频库(active)──► 每日指标与粉丝快照 ──► 本地看板 Telegram 私聊发链接/BV 号 ──► 解析 ──► 入库 ─────────┘ B 站收藏夹(人工维护)──► favsort.py 重排序(唯一使用写鉴权,频率极低) ``` - **收录范围**:只收录他人观看、试听或点评咻咻满内容的 reaction 视频;最终是否收录由人工确认。 - **本地库是真相源**:入库来自「搜索候选确认」与「Telegram 私聊手动入库」;收藏夹不再自动同步。 - **收藏夹解耦**:项目不再轮询收藏夹,也不会因取消收藏而改变视频状态;收藏夹由人工维护,重排序使用 `favsort.py`。 - **黑名单仅作用于发现**:已入库的黑名单 UP 视频不会被清理,也会继续追踪。 - **数据不回填**:B 站不提供任意日期的历史指标;新视频从首次入库日起开始积累快照。 ## 本地开发环境 项目默认使用 **Python 3.14** 和 [uv](https://docs.astral.sh/uv/)。本地开发、测试和现有排序工具的运行均不依赖 Docker、Node.js、数据库服务或宿主机 cron。 ```powershell # 在项目根目录初始化或同步环境 uv sync --all-groups # 确认锁定的解释器版本 uv run python --version # 查看现有 CLI 帮助 uv run bili-fav help ``` 完整的 `uv` 使用方式、依赖规范、架构与开发约定见 [DEVELOPMENT.md](DEVELOPMENT.md)。 ## P0:数据底座、采集与手动入库 P0 已实现 SQLite 迁移、配置热加载、B 站只读客户端、黑名单种子与每日快照调度。入库来源为搜索候选确认与 Telegram 私聊手动入库;**项目不再轮询收藏夹**,收藏夹由人工维护。 ```powershell # 建库、执行迁移、写入黑名单种子(离线) uv run python app.py init # 手动入库:视频链接、BV 号、av 号或 b23.tv 短链(只写本地库) uv run python app.py ingest BV1x8x166EzY uv run python app.py ingest https://www.bilibili.com/video/BV1x8x166EzY uv run python app.py ingest av12345 # 按配置周期常驻运行发现 + 每日快照;--once 只跑一次到期任务 uv run python app.py run uv run python app.py run --once # 查看库内状态(离线) uv run python app.py status ``` - 数据写入 `data/app.db`(SQLite WAL);升级已有数据库前会自动备份到 `data/backup/`; - 入库只写本地 `video(status='active')`,不写 B 站收藏夹;已失效/移出库的视频被再次入库时会重新激活; - 视频不可访问(`view` 返回 `-404` / `62002` / `62012`)由每日快照标记为 `removed`; - 黑名单只作为发现阶段的种子写入,不影响已入库视频; - `folders` 配置仅作为历史来源与重排序目标,系统不再自动同步或换夹。 ## P1:每日快照与粉丝追踪 P1 已实现每日七项视频指标快照、咻咻满粉丝数快照、失败占位、断点续跑、Cookie 失效告警和 SQLite 日备份。 ```powershell # 采集当日快照与粉丝数(只读联网,约 18~20 分钟 / 795 条) uv run python app.py snapshot # 忽略当日完成标记,重新采集 uv run python app.py snapshot --force # 回填历史视频的 UP 粉丝数快照(第 9 轮,只补空值) uv run python app.py backfill-followers uv run python app.py backfill-followers --dry-run # 分别为只读和写入账号扫码授权(默认 read;write 仅用于 favsort.py) uv run python app.py auth uv run python app.py auth --account write # 检查指定账号或两套账号的登录态 uv run python app.py auth-check --account read uv run python app.py auth-check --account all ``` - `snapshot` 对 `active` / `unfaved` 视频逐条请求 `view`,每条请求间隔 1.0~1.6 秒; - 成功写入 `snapshot` 的七项指标;同日 `ok=1` 的记录直接跳过,可断点续跑; - 当天已完成后新入库或从 `dropped` 恢复的视频会立即补采当天快照,不必等到次日; - 视频不可访问(`-404` / `62002` / `62012`)标记为 `removed`; - 其他错误重试 3 次,仍失败写入 `ok=0` 和错误信息,不写伪造的零值; - 粉丝数写入 `up_stat`,失败同样写 `ok=0`;续跑时已成功的 `(stat_date, mid)` 会跳过,不会被失败结果覆盖; - 当日全部视频与粉丝采集成功后写入完成标记;服务启动时若当天未完成会自动补跑; - 每次采集后生成 `data/backup/app-YYYYMMDD.db`,按 `storage.backup_keep_days` 清理旧日备份; - 备份失败时 `snapshot` 返回 1 便于任务告警;再次执行只重试备份,不重复请求 B 站; - Cookie 失效只告警,不阻塞匿名 `view` 采集;P0/P1 只使用 `bilibili.read`。 首次真实全量采集结果:795 条视频全部成功、0 失败、0 移除,耗时约 19 分钟;粉丝 `follower=2767270`;日备份为 `data/backup/app-20261031.db`。 ## P3:搜索发现、Telegram 入库与手动入库 P3 已实现 WBI 搜索发现、候选池、Telegram 通知与按钮回调(仅写本地库),以及 Telegram 私聊手动入库。 ```powershell # 执行一次发现;--no-notify 只写候选池,不发 Telegram uv run python app.py discover uv run python app.py discover --no-notify # 查看候选池(离线) uv run python app.py candidates uv run python app.py candidates --decision rejected # 常驻运行:快照 + 发现 + Telegram long polling uv run python app.py run ``` - 搜索使用 `x/web-interface/wbi/search/type`,WBI 密钥缓存 6 小时,失败时降级旧版 `search/type`; - 每轮按 `discover.keywords` 搜索前 `discover.pages` 页,过滤已入库/已见/超时间窗/黑名单/标题不含要求词的结果; - 分数规则与 `classify` 配置一致;`score >= discover.candidate_min_score` 才写入候选池,`score >= notify.min_score` 才发送 Telegram; - 通知按钮:**入库**、**忽略**、**拉黑**;回调只接受 `telegram.chat_id` 对应的私聊用户; - 私聊命令(仅本人可用): - `/candidate [页码]`:列出待决策候选标题(每页 10 条,用 `/candidate 2` 翻页); - `/candi_del`:每页 10 个标题按钮,点击即删除该候选;删后原地重建本页键盘,可连续删;带上一页/下一页; - `/candi_clear`:给出「全部 / 待决策 / 取消」按钮,清空整表或只清待决策; - 「入库」把视频写入本地库并置为 `active`,**不写 B 站收藏夹**;同时用取到的 `view` 数据补一条当天快照; - 私聊文本消息会自动解析其中的视频链接 / BV 号 / av 号 / `b23.tv` 短链并逐条入库,回执汇总结果; - 忽略写 `candidate.decision='rejected'`;拉黑写 `up_blacklist` 并拒绝该 UP 的其他待决策候选; - 候选超过 `schedule.candidate_ttl_days` 且未决策时自动删除,`seen_aid` 防止重复通知; - 手动入库不做「标题含咻咻满」等规则校验,用户明确指定即入库。 > 入库只依赖只读接口(`view`);写鉴权仅用于 `favsort.py` 收藏夹重排序。离线测试使用契约一致的 Mock 覆盖。 ## P2:只读本地看板 P2 提供移动端优先的只读看板,默认绑定 `127.0.0.1:8080`。Web 页面不提供收藏、忽略、拉黑、移出库等写操作;数据管理仍通过 Telegram、CLI 或数据库完成。 ```powershell # 只启动只读 Web(不运行调度、不连 B 站) uv run python app.py serve # 无子命令启动完整服务:只读 Web + 调度 + Telegram uv run python app.py # 生成 7 天预览库并启动预览 uv run python tools/seed_preview_db.py --days 7 uv run python app.py serve --preview --config data/preview/config.jsonc ``` - 首页默认“最新动态”:标题下显示「收录视频 N」,顶部四个数据卡片为「今日入库 / 播放增量 / 7 日粉丝变化 / 数据日期」(前三项可点击跳转);下方为最近发布、增长动态、热度速览三个区块,统一使用竖排卡片(封面 + 两行截断标题 + 作者/数据行 + 高亮数据行),并统一为响应式网格(移动端每行 2 个、PC 每行 3 个),每处各取前 6 条;采集状态等运行期信息只在登录后的「系统」页可见;有候选且已登录时,首页显示「N 条候选待确认」提示条(访客不显示); - 「视频」与「UP主」是两个独立入口;工具条顺序为「排序」下拉 → 时间下拉(无需 label)→ 右侧自适应搜索框(最大 320px,放大镜按钮在输入框内),三者在 PC 与移动端都保持一行; - 视频排序:发布时间(默认)、内容效率、综合热度、入库时间、播放/投币/收藏/评论、互动率、播放增量/播放增速;UP 排序:平均播放(默认)、内容效率、互动效率、内容量、总播放、视频数、总互动、最近发布; - UP 卡片显示 UP 头像(无头像时显示首字母占位)、视频数/总时长与最新粉丝数;「总播放」排序时副指标显示「平均播放」(相当于平均播放榜,主副指标对调);UP 详情页也显示头像; - 时间筛选:全部 / 7天内 / 30天内 / 90天内 / 1年内 / 已入库年份(如 2026年);不再提供按收藏夹过滤,UP 页也不再按视频数筛选; - 内容效率 = 播放 / 内容小时;单条视频用 60 分钟先验暴露量向全库均值收缩,只统计时长 ≥ 600 秒、发布满 2 天、播放 ≥ 2000 的视频(综合热度榜同样要求播放 ≥ 2000);UP 内容效率用经验贝叶斯收缩,避免极小样本冲榜; - 「数据说明」(`/about`)说明数据来源、发布时间/入库时间/快照日期、指标公式、筛选口径与已知局限,数值由 `/api/about` 动态读取; - 视频详情提供分 P 数、七项指标、内容/互动效率、入库时粉丝数、单指标趋势、快照表和同 UP 对比;UP 页提供聚合趋势、单视频对比与入库时/当前粉丝数; - 粉丝页只绘制粉丝曲线,不叠加视频数据;支持 7/30/90/全部与自选日期,范围超过一年时按每月峰值日期聚合;最近记录默认 14 天并显示较上一成功日的增量; - 前端使用 History API 的干净路径路由(`/videos`、`/ups`、`/video/{aid}`、`/up/{mid}`、`/about`),不使用 `#/` hash;旧 `/rank` 链接仍可用; - 候选池和系统状态只读展示,操作入口指向 Telegram/CLI; - 系统状态页展示访问统计(近 30 天每日 PV/UV 与 Top 页面):前端在整页加载与站内路由切换时通过 `navigator.sendBeacon("/api/visit")` 上报,服务端按客户端 IP 与归一化路由聚合,先写内存缓冲、每分钟批量落库(详见 [访问统计与真实来源 IP](#访问统计与真实来源-ip)); - 首页、视频榜、UP 主榜、视频/UP 详情、粉丝页和数据说明页对访客可见; - 配置 `web.auth_token` 后,“候选”和“系统”页面及其接口需要登录,启用登录页和 HttpOnly Cookie;这两个入口放在顶栏右侧:未登录显示「登录」,登录后显示「候选 | 系统」(从底部主导航移出,避免移动端底部拥挤); - 本地回环地址且未配置 token 时视为无鉴权,全部页面可见;非回环地址且未配置 token 时拒绝启动; - 所有业务接口只接受 GET,写方法返回 `405`;仅显式放行 `POST /api/visit`(访问统计埋点)与 `POST /login`(会话),其余 POST/PUT/PATCH/DELETE 仍统一 `405`; - 首页/榜单聚合结果缓存 1 小时,粉丝结果缓存 24 小时;快照、发现、入库/忽略/拉黑、候选清理和粉丝 CSV 导入会立即让对应缓存失效。 ### 预览数据 `tools/seed_preview_db.py` 通过 SQLite backup API 复制真实库到 `data/preview/app.db`,再回填 7 天合成快照: - 合成行标记 `src='preview'`,并写入 `kv preview:seeded=1`; - 真实库只读,预览库与真实库完全分离; - 默认包含少量缺失日、3 条失败快照、4 条预览候选和 1 条预览黑名单; - `--profile first-day` 可生成保持“首次导入”状态的冷启动库; - 预览配置不包含真实 Cookie,端口 8081,Telegram 关闭; - 重新生成前必须先停止预览服务,否则 Windows 会因数据库被占用而拒绝覆盖; - 第 9 轮起本地不再保留 `data/app.db` 真实副本,重新生成请加 `--source data/backup/<真实库备份>`;预览库会自动升级到当前 schema(含 `up_profile` / `pages`)。 ### 粉丝历史回填(CSV) B 站粉丝历史 CSV 形如: ```csv 时间,粉丝,7日变化,1日变化 2026-10-31 22:42,2767269,139,-33 2026-10-29 23:53,2767334,496,15 ``` 导入命令: ```powershell # 先预览将要写入的内容 uv run python tools/import_fans_csv.py --csv E:\Downloads\history.csv --dry-run # 实际写入;默认跳过已有成功记录,--force 才覆盖同日期记录 uv run python tools/import_fans_csv.py --csv E:\Downloads\history.csv # 用完整 CSV 替换该 mid 的全部粉丝历史(推荐首次导入完整历史时使用) uv run python tools/import_fans_csv.py --csv E:\Downloads\history.csv --replace ``` - 只写 `up_stat`,不修改视频快照、收藏夹或黑名单; - 同一天出现多行时保留时间最晚的一条; - 缺失日期不插值,粉丝曲线会断开; - 默认跳过已有 `ok=1` 记录;`ok=0` 的历史记录会被修复; - `--replace` 会先删除该 mid 的全部 `up_stat`,再按 CSV 重建,适合用完整历史覆盖旧数据; - 如果数据库里已有晚于 CSV 末尾的记录,`--replace` 会拒绝执行;确认要删除时才加 `--allow-lossy-replace`;只想补齐 CSV 日期并保留后续采集时用 `--force`; - 页面的“日增”只在存在前一天数据时计算,跨多天不会标成日增; - 写入前建议先备份 `data/app.db`,例如 `data/backup/app-pre-fans-YYYYMMDD.db`; - 容器内运行时用 `--db /data/app.db` 或 `--config /config/config.jsonc`,避免默认去找 `/app/config.jsonc`。 ## 访问统计与真实来源 IP 看板提供基础访问统计(PV/UV)。前端在整页加载与站内路由切换时通过 `navigator.sendBeacon("/api/visit")` 上报路径;服务端按 `(日期, 客户端 IP, 归一化路由)` 聚合,**先写入进程内缓冲、每分钟批量落库**到 `visit` 表,`/system` 系统状态页展示近 30 天每日 PV/UV 与 Top 页面。 统计按客户端 IP 去重,因此反代必须还原真实客户端 IP。链路为 `域名 → CDN → 公网 EIP → 云服务器内网 → 宿主机 nginx → 127.0.0.1:8080(容器)`: 容器内看到的 socket 对端是 Docker 网关,真实 IP 只能来自 nginx 注入的请求头。 - nginx 用 `realip` 模块按 CDN 网段把真实 IP 归一化到 `$remote_addr`,再写 `X-Real-IP` 传给容器;直连公网 EIP 时 `$remote_addr` 本身就是客户端; - 应用只读 `X-Real-IP`,且仅当 socket 对端属于可信代理网段时采信,否则回退对端, 因此直连请求伪造的 `CF-Connecting-IP` / `EO-Connecting-IP` / `X-Forwarded-For` 无效; - 三种来源(直连 EIP / Cloudflare / EdgeOne)的完整 nginx 配置、网段刷新脚本与 配置项说明见 [`deploy/nginx/README.md`](deploy/nginx/README.md)。 `config.jsonc` 中的 `web.track` 控制统计行为:`enabled` / `header_order` / `trusted_proxies` / `count_bots` / `flush_seconds` / `keep_days` / `max_hits_per_ip_path_per_day`;默认过滤常见爬虫 UA、每分钟落库、保留 180 天。 ## 快速部署(Docker) 仓库根目录已包含 `Dockerfile`、`compose.example.yml`、`.dockerignore`、`config.docker.example.jsonc` 与 `secrets.example.json`。Compose 文件是示例,需要显式指定: ```powershell # 1. 准备服务器配置:业务 JSONC + 无注释凭据文件;保留 /data 绝对路径 Copy-Item config.docker.example.jsonc config.jsonc Copy-Item secrets.example.json secrets.json # 编辑 secrets.json 填入 B 站 Cookie / Telegram / web.auth_token # 2. 构建并后台启动 docker compose -f compose.example.yml up -d --build # 3. 查看日志与健康状态 docker compose -f compose.example.yml logs -f curl http://127.0.0.1:8080/api/health ``` 如果希望省略 `-f`,可以复制为 `compose.yml`(该文件不提交): ```powershell Copy-Item compose.example.yml compose.yml docker compose up -d --build ``` - 容器执行 `python app.py --config /config/config.jsonc`,同时运行只读 Web、调度和 Telegram; - `config.jsonc` 与 `secrets.json` 挂到 `/config/`,`data/` 挂到 `/data/`; - 服务器配置需使用 `web.host = 0.0.0.0`,并在 `secrets.json` 设置 `web.auth_token`,否则服务拒绝启动; - `storage.database_path` 使用 `/data/app.db`,`storage.backup_dir` 使用 `/data/backup`; - 首次部署可先在本地执行 `uv run python app.py auth --account read` 扫码(只回写 `secrets.json`),再把 `config.jsonc` + `secrets.json` 放到服务器; - 也可在容器内扫码:`docker compose -f compose.example.yml run --rm board python app.py auth --account read --no-browser`,终端会打印 ASCII 二维码;PNG 仍写在临时文件路径,可用 `docker cp` 取出; - 镜像包含 `tools/`,可直接在容器内执行 `python tools/import_fans_csv.py ...` 回填粉丝历史; - 数据卷包含数据库、备份与预览库,建议定期备份 `/data`。 不使用 Compose 时: ```powershell docker build -t reaction-board . docker run -d --name reaction-board --restart unless-stopped ` -p 8080:8080 ` -v ${PWD}/config.jsonc:/config/config.jsonc ` -v ${PWD}/secrets.json:/config/secrets.json ` -v ${PWD}/data:/data ` reaction-board ``` ## 配置 复制样例:业务配置用 JSONC(支持 `//` / `/* */` 注释与尾逗号),凭据单独放无注释的 `secrets.json`: ```powershell Copy-Item config.example.jsonc config.jsonc Copy-Item secrets.example.json secrets.json ``` `config.jsonc` / `secrets.json`(以及旧名 `config.json`)已被 `.gitignore` 忽略,**不得提交**。 默认启动优先读 `config.jsonc`,若不存在则回落 `config.json`(旧单体文件仍可用)。 `python app.py auth` **只回写** `secrets.json`,不会冲掉 JSONC 注释。 凭据文件双账号格式: ```json { "bilibili": { "read": { "mid": 0, "cookie": "...", "csrf": "..." }, "write": { "mid": 0, "cookie": "...", "csrf": "..." } } } ``` - `bilibili.read`:视频数据快照、粉丝数、搜索发现与入库等所有只读查询使用; - `bilibili.write`:仅 `favsort.py` 收藏夹重排序使用; - 两个账号的 Cookie、CSRF 与设备指纹严格隔离;顶层 `mid` / `cookie` / `csrf` **不再支持**; - `overrides`:可选,为代发或联合投稿视频指定归属 UP; - `folders`:历史来源与重排序目标(`year` + `media_id` + `title`),系统不再自动同步或建夹; - `up_blacklist_seed`:首次启动写入数据库的 UP 黑名单种子; - `track_fans`:需要每日追踪粉丝数的 UP mid; - `discover`:搜索关键词、页数、时间窗与候选线(`keywords` / `pages` / `window_days` / `candidate_min_score`); - `storage`:数据库与备份目录; - `web`:绑定地址、端口、访问令牌与 `web.track`(访问统计开关、可信代理网段、上报头顺序、落库周期与保留期); - `schedule` / `notify` / `classify` / `heat_weights`:任务节奏、通知阈值、分类规则和热度权重。 配置每 60 秒检查一次 mtime,凭据、分类规则等变更无需重启即可在下次任务读取;但 `schedule.discover_minutes`、`schedule.snapshot_at` 与 `schedule.seed_at` 在启动时固化,修改后需重启 `run` 进程。 完整配置口径以 [project.md](project.md) 和 [DEVELOPMENT.md](DEVELOPMENT.md) 为准。 ## 当前可用:收藏夹排序 CLI 所有命令建议通过 `uv run` 执行: ```powershell # 查看全部命令;无需配置文件 uv run bili-fav help # 为写账号扫码登录与检查状态(认证入口统一在 app.py) uv run python app.py auth --account write uv run python app.py auth-check --account write # 列出本人创建的收藏夹 uv run bili-fav folders # 只读:拉取收藏夹,生成排序计划 uv run bili-fav plan -v # 查看或离线重算已有计划 uv run bili-fav show uv run bili-fav replan # 写操作:按计划逐步排序;完成后校验 uv run bili-fav run uv run bili-fav verify ``` `plan` 会生成 `data/plan-.json`,其中含原始顺序、目标顺序、移动脚本及视频元数据缓存。`run` 直接使用该计划,不会重复请求 `infos`;如排序算法变更,可用 `replan` 离线重算。 ### 排序规则 1. **同一 UP 必须连续**:按 `upper.mid` 分组,组内按发布时间倒序;发布时间相同则按 `avid` 升序。 2. **组间按最早投稿倒序**:取该 UP 在当前收藏夹中最早视频的发布时间作为组键,按组键倒序;并列时按组内最小 `avid` 升序。 3. **失效或异常资源置底**:保持它们原有的相对顺序。 4. **可配置归属修正**:`config.json` 的 `overrides` 可为指定视频指定实际归属 UP。 > 组间规则是有意设计:某个 UP 只要存在较老视频,即使近期继续投稿,整个分组也会靠后。 ## 扫码授权 当接口返回 `-101` 或 Cookie 即将过期时,使用统一认证入口: ```powershell uv run python app.py auth --account read uv run python app.py auth --account write uv run python app.py auth-check --account all ``` 授权流程会在终端打印 UTF-8 半块字符二维码(TTY 下白底黑字,方便深色终端扫码),同时写出 PNG,并等待手机客户端扫码确认。`--no-browser` 只跳过打开图片,仍会打印终端二维码。成功后: - 更新 `SESSDATA`、`bili_jct`、`DedeUserID`、`sid` 等身份字段; - 保留 `buvid3`、`buvid4`、`_uuid`、`buvid_fp` 等设备指纹; - 将选中账号的 `csrf` 同步为新的 `bili_jct`,并从 `nav` 回写该账号实际 mid; - 原子写入 `secrets.json`,并保留 `secrets.json.bak`(主配置 JSONC 不被改写); - 通过 `x/web-interface/nav` 验证新登录态,失败时自动回滚;不会修改另一个账号。 常用选项: ```powershell uv run python app.py auth --account write --no-browser uv run python app.py auth --account read --timeout 180 ``` ## B 站访问与安全约束 | 接口 | 当前用途 | 身份 Cookie | |---|---|---| | `view` / `relation/stat` / `nav` | 每日快照、入库详情、粉丝数、read 登录态 | `read`。 | | `wbi/search/type` | 关键词搜索发现 | `read`(匿名可用)。 | | `fav/resource/ids` / `infos` | 收藏夹重排序计划(`favsort.py`) | `write`。 | | `fav/folder/created/list-all` | 列出目标自建收藏夹(`favsort.py`) | `write`。 | | `fav/resource/sort` | 收藏夹重排序 | `write`,需要完整 Cookie 和 CSRF。 | 只读客户端默认剥离 `read` 身份 Cookie,仅保留该账号设备指纹;如果失败才使用完整 `read` Cookie 重试。写操作(`sort`)一律使用完整 `write` Cookie 与 CSRF,且仅由 `favsort.py` 触发。 B 站请求通过 `httpx` 连接池发送,并按 `data/raw-request.txt` 对齐 Chrome 155 的 UA、`sec-ch-ua`、`origin`、`referer` 和 `priority` 等请求头;收藏夹请求的 `referer` 会带上目标 `media_id`。私聊手动入库的 `b23.tv` 短链通过同一传输层跟随重定向还原为视频 URL。 `x/v3/fav/resource/sort` 的实测语义为: ```text sort=A:2:B:2 → 将 B 插到 A 之后 ``` 运行排序时会重新读取实际顺序、逐步回读校验,并在收藏夹内容于执行期间变动时中止,避免用过期计划强制排序。写操作存在风险,请确认计划后再执行;中断后可直接再次运行 `run` 继续收敛。 ## 测试 测试全部离线运行,外部接口只在 transport 边界用真实响应形状的 Mock 替换: ```powershell uv run python tests/test_auth.py # Cookie 合并、设备指纹、Set-Cookie 解析 uv run python tests/test_moves.py # 排序移动脚本:随机排列与头部用例 uv run python tests/test_favsort_http.py # favsort httpx 请求头与匿名回退 uv run python tests/test_config.py # 配置加载、校验、ENV 覆盖与热加载 uv run python tests/test_client_ip.py # 来源 IP 提取、路由归一化与机器人判定(纯函数) uv run python tests/test_visits.py # 访问统计内存缓冲、批量落库与保留期清理 uv run python tests/test_migrations.py # 迁移、备份、回滚与保留期 uv run python tests/test_database.py # 单写队列、事务与并发 uv run python tests/test_repository.py # 收藏夹/视频状态机与黑名单 uv run python tests/test_bilibili_client.py # ids/infos/view/view-detail/fans/nav/短链契约与重试 uv run python tests/test_httpx_transport.py # httpx B 站/Telegram 传输层 uv run python tests/test_classify.py # 标题归一化、时长解析与打分 uv run python tests/test_video_ref.py # BV/av/URL/b23.tv 引用解析(纯函数) uv run python tests/test_search.py # WBI 签名、搜索解析与降级 uv run python tests/test_candidates.py # 候选池、去重、通知与 fav_action uv run python tests/test_discovery.py # 发现过滤、候选池与通知批次 uv run python tests/test_library.py # 入库(候选/手动)、忽略、拉黑与幂等 uv run python tests/test_telegram.py # 消息、分片、长轮询、回调与文本入库 uv run python tests/test_snapshot.py # 七项快照、补采、断点续跑、粉丝、备份重试 uv run python tests/test_maintenance.py # 日备份与保留期 uv run python tests/test_timeutil.py # 上海时区与每日任务时间 uv run python tests/test_analytics.py # 热度、互动率、增长与封面 uv run python tests/test_preview_seed.py # 预览库回填、确定性与安全边界 uv run python tests/test_fans_csv.py # 粉丝 CSV 解析、替换与幂等回填 uv run python tests/test_web_queries.py # 看板查询层、冷启动与访问统计聚合 uv run python tests/test_web_server.py # 只读路由、鉴权、静态资源、访问统计与预览模式 uv run python tests/test_scheduler.py # 内置调度器到期/失败隔离 uv run python tests/test_provider.py # 凭据变化重建客户端与配置 revision uv run python tests/test_favsort_run.py # 排序参数校验与混合资源类型 uv run python tests/test_logging.py # JSON 日志脱敏 uv run python tests/test_cli.py # CLI 离线路径 ``` ## 运行期文件 ```text secrets.json.bak 最近一次 auth 更新前的凭据备份 data/plan-.json 排序计划、顺序快照和元数据缓存 data/journal-.jsonl 排序执行日志 data/app.db SQLite 数据库(视频、快照、粉丝、黑名单、历史收藏夹) data/backup/app-YYYYMMDD.db 每日快照后的 SQLite 备份 data/backup/app-pre-v*.db 数据库迁移前的自动备份 data/backup/app-pre-fans-*.db 粉丝历史回填前的手动备份 data/preview/app.db 合成预览数据库(不提交) data/preview/config.jsonc 无凭据的预览配置(不提交) ``` ## 文档索引 - [project.md](project.md):完整可行性分析、业务口径、接口验证、数据模型草案和风险清单。 - [DEVELOPMENT.md](DEVELOPMENT.md):开发架构、数据库迁移、API、调度、Telegram 与分阶段实施计划。