Files
re-xxm/README.md
T

473 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 续期 | 可用 | 本地生成二维码;更新身份 Cookie 时保留设备指纹。 |
| 视频入库 | 可用 | 搜索候选经 Telegram 确认入库;私聊发送视频链接、BV 号、av 号或 `b23.tv` 短链手动入库。只写本地库。 |
| 视频日快照 | 可用(P1) | 逐条采集播放、点赞、投币、收藏、转发、评论、弹幕。 |
| 粉丝追踪 | 可用(P1) | 每日记录咻咻满等配置 UP 的粉丝数。 |
| 搜索发现与候选池 | 可用(P3) | 每小时搜索前 2 页,分类打分后写入候选池。 |
| 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 >= notify.min_score` 才发送 Telegram;
- 通知按钮:**入库**、**忽略**、**拉黑**;回调只接受 `telegram.chat_id` 对应的私聊用户;
- 「入库」把视频写入本地库并置为 `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.json
```
- 首页默认“最新动态”:顶部四个数据卡片(今日入库 / 播放增量 / 粉丝日增 / 待确认候选)可点击跳转(视频榜按入库或增长排序、粉丝页、候选页);下方为最近发布、增长动态、热度速览与采集状态;“最近发布”“增长动态”“热度速览”三处统一使用竖排卡片(封面 + 两行截断标题 + 作者/数据行 + 高亮数据行),并统一为响应式网格(移动端每行 2 个、PC 每行 3 个),每处各取前 6 条;
- 「视频」与「UP主」是两个独立入口;工具条顺序为「排序」下拉 → 时间下拉(无需 label)→ 右侧自适应搜索框(最大 320px,放大镜按钮在输入框内),三者在 PC 与移动端都保持一行;
- 视频排序:内容效率(默认)、综合热度、发布时间、入库时间、播放/投币/收藏/评论、互动率、播放增量/播放增速;UP 排序:内容效率、互动效率、内容量、总播放、视频数、平均播放、总互动、最近发布;
- UP 卡片显示 UP 头像(无头像时显示首字母占位)、视频数/总时长与最新粉丝数;「总播放」排序时副指标显示「平均播放」(相当于平均播放榜,主副指标对调);UP 详情页也显示头像;
- 时间筛选:全部 / 近 7 天 / 近 30 天 / 近 90 天 / 近 1 年 / 已入库的年份;不再提供按收藏夹过滤,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.json`,避免默认去找 `/app/config.json`。
## 访问统计与真实来源 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.json` 中的 `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.json`。Compose 文件是示例,需要显式指定:
```powershell
# 1. 准备服务器配置;至少填写 B 站 Cookie / Telegram,并保留 /data 绝对路径
Copy-Item config.docker.example.json config.json
# 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.json`,同时运行只读 Web、调度和 Telegram;
- `config.json` 挂载到 `/config/config.json`,`data/` 挂载到 `/data/`;
- 服务器配置需使用 `web.host = 0.0.0.0` 并设置 `web.auth_token`,否则服务拒绝启动;
- `storage.database_path` 使用 `/data/app.db`,`storage.backup_dir` 使用 `/data/backup`;
- 首次部署可先在本地执行 `uv run python app.py auth --account read` 扫码,再把生成好的 `config.json` 放到服务器;
- 也可在容器内扫码:`docker compose -f compose.example.yml run --rm board python app.py auth --account read --no-browser`,二维码会打印在临时文件路径,可用 `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.json:/config/config.json `
-v ${PWD}/data:/data `
reaction-board
```
## 配置
复制样例配置,并填写 B 站账号信息:
```powershell
Copy-Item config.example.json config.json
```
`config.json` 已被 `.gitignore` 忽略,**不得提交**。配置必须使用双账号格式:
```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`);
- `storage`:数据库与备份目录;
- `web`:绑定地址、端口、访问令牌与 `web.track`(访问统计开关、可信代理网段、上报头顺序、落库周期与保留期);
- `schedule` / `notify` / `classify` / `heat_weights`:任务节奏、通知阈值、分类规则和热度权重。
配置每 60 秒检查一次 mtime,凭据、分类规则等变更无需重启即可在下次任务读取;但 `schedule.discover_minutes` 与 `schedule.snapshot_at` 在启动时固化,修改后需重启 `run` 进程。
完整配置口径以 [project.md](project.md) 和 [DEVELOPMENT.md](DEVELOPMENT.md) 为准。
## 当前可用:收藏夹排序 CLI
所有命令建议通过 `uv run` 执行:
```powershell
# 查看全部命令;无需 config.json
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 <media_id> -v
# 查看或离线重算已有计划
uv run bili-fav show <media_id>
uv run bili-fav replan <media_id>
# 写操作:按计划逐步排序;完成后校验
uv run bili-fav run <media_id>
uv run bili-fav verify <media_id>
```
`plan` 会生成 `data/plan-<media_id>.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
```
授权流程在本地生成二维码图片并等待手机客户端扫码确认。成功后:
- 更新 `SESSDATA`、`bili_jct`、`DedeUserID`、`sid` 等身份字段;
- 保留 `buvid3`、`buvid4`、`_uuid`、`buvid_fp` 等设备指纹;
- 将选中账号的 `csrf` 同步为新的 `bili_jct`,并从 `nav` 回写该账号实际 mid;
- 原子写入 `config.json`,并保留 `config.json.bak`;
- 通过 `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
config.json.bak 最近一次 auth 更新前的配置备份
data/plan-<media_id>.json 排序计划、顺序快照和元数据缓存
data/journal-<media_id>.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.json 无凭据的预览配置(不提交)
```
## 文档索引
- [project.md](project.md):完整可行性分析、业务口径、接口验证、数据模型草案和风险清单。
- [DEVELOPMENT.md](DEVELOPMENT.md):开发架构、数据库迁移、API、调度、Telegram 与分阶段实施计划。