Files
rootandClaude Opus 5.5 b343dddbf6 docs: 清理过程文档,CHANGELOG 补齐 0.3.8–0.3.14
- 删除 docs/plan 下 00–18 的计划、评审、验收、发布记录与升级证据,docs/spikes 与 spikes 验证代码
  (保留 spikes/movieclaw-src 本地克隆),以及 4 份一次性审查 / 调研;全部可在 ceb66f8 查到
- MP 对照移到 docs/porting/moviepilot-v3-comparison.md;经验教训改为 docs/plan/lessons.md(补第 25 条)
- docs/plan/README.md:文档放哪、当前状态、待办
- 引用改写:代码注释指向在维护的文档,其余注明已删除与 git 位置;清掉原有 6 个失效链接
- README 开头改为当前状态与入口;CHANGELOG 补齐 0.3.8–0.3.14 与工程变更

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 16:08:09 +08:00

21 KiB
Raw Permalink Blame History

站点中心与仪表盘调整(v0.2.1)

依据:00-first-version-plan(已删除,见 git ceb66f8) §8(用户 2026-09-28 上午)。状态:待审阅。只读调研 + 规格,不含代码改动。 参考:MoviePilot v3(/opt/moviepilot-plus/upstream)、MovieClaw v0.27.0(spikes/movieclaw-src/repo)。

1. 用户要求(原文要点)

  • 资源 → 站点页 = 添加站点 + 凭证管理(账密 / Cookie / API Key,馒头 API Key)+ 卡片/列表(上次登录时间、上传/下载、分享率、用户等级、VIP、账号状态,数据来自 sites/user-parsers 的 refreshSiteUserData)+ 手动"检测健康"与"用凭证刷新登录状态"。
  • 账密登录走 NexusPHP takelogin;验证码 / 二次验证失败 → 待处理,提示改用 Cookie。
  • 设置里只留全局默认(限流、快照周期)。
  • 仪表盘保留 REQ 六区块,补 MP 有价值的区块(媒体库统计、存储用量、下载器实时状态、最近入库、定时任务状态),砍重复。

2. 参考实现

2.1 MoviePilot v3

方面 做法 位置
卡片 名称、URL、左侧状态色条(sitestatistic.lst_state 成功/失败 + seconds 耗时,≥5s 黄)、徽标(限流 / 代理 / 浏览器仿真 / 过滤)、上传 / 下载量;按钮:测试连通、用户数据、更新 Cookie;菜单:编辑、删除;可拖拽排序(优先级) components/cards/SiteCard.vue、views/site/SiteCardListView.vue
添加 / 编辑 名称、URL、优先级 pri、RSS 地址、Cookie、API Key、Token、UA、代理开关、浏览器仿真、超时、限流(limit_interval 秒内 limit_count 次、limit_seconds 间隔)、下载器、启用 dialog/SiteAddEditDialog.vue、db/models/site.py
更新 Cookie 对话框填用户名 / 密码 / 二步验证码(或 TOTP 密钥)→ POST site/cookie/{id};后端 CookieHelper 用 Playwright 浏览器打开登录页、OCR 识别验证码、按 two_step_code/otp 输入框填 TOTP,拿到 Cookie + UA 写回站点。密码不保存 chain/site.py update_cookie、application/security/cookie.py
测试连通 GET 首页,SiteUtils.is_logged_in 判登录;Cloudflare 挑战单独提示;结果与耗时写 sitestatistic(success/fail/seconds/lst_state/lst_mod_date) chain/site.py test/__test
用户数据 siteuserdata 按天一行:username、userid、user_level、join_at、bonus、upload、download、ratio、seeding、leeching、seeding_size、leeching_size、seeding_info、message_unread(+内容)、err_msg、updated_day/time;对话框看历史曲线 db/models/siteuserdata.py、dialog/SiteUserDataDialog.vue
定时 sitedata_refresh「站点数据刷新」,间隔 sitedata_refresh_interval scheduler/catalog.py
Cookie 来源 手填、账密换取、CookieCloud 同步(设定 → 站点:地址 / 密钥 / 间隔 / 黑名单) chain/site.py sync_cookies

2.2 MovieClaw v0.27.0

  • 认证方式三种:cookie / apikey / credential(用户名 + 密码,程序模拟登录并保存)。CredentialAuthProvider:GET login.php → 正则找 imagecode.php?... 验证码(无识别器 → NEEDS_CAPTCHA 返回图片地址)→ 带隐藏字段、imagehash、extra_form_data POST 表单 → success_css(a[href*='logout'])判成功,error_css(td.text)取失败原因,验证码错最多重试。
  • 账号状态 pending / verifying / active / failed,附 last_verified_at、last_checked_at、last_error(归类后的中文);启动时把卡在 verifying 的复位。保存凭证即"保存并验证",卡片有"重新验证"。
  • 验证成功时顺手抓 profile:用户名、等级、vip_group(定义里 vip_css 命中即 VIP)、上传 / 下载字节、分享率(null 与 0 区分)、魔力(无则不显)、做种 / 下载数、fetched_at。
  • 卡片另有同步统计(资源数、上次同步 / 成功、下次同步、连续失败、上次错误)和保护 / 刷流开关(不在本期范围)。

3. 现状与缺口

项 现状 缺口
入口 站点工作台卡片(启停、健康、账号摘要、快照);详情"账号"页签;设置 #sites 另有站点列表 + 登记抽屉 + 凭证表单(components/settings/sites.tsx,625 行) 仍是两处可增删改,违背 IA"一个对象一个家"
凭证 PUT /sites/{id}/credentials(cookie/passkey/api_key/password + username)与 /site-accounts CRUD 并存;密码只存不用 无账密登录实现;无 UA 字段;无 TOTP
用户数据 refreshSiteUserData 与 20 个解析器已移植,服务端没有调用点;site_account.user_info 只存 username 无数据、无历史、无 VIP 字段
健康 adapter 有 healthCheck()(首页 + loginSuccess),无路由;健康只在快照 / Feed 出错时被动更新 无手动检测、无检测时间与耗时
登录态 site_account.status = unknown/active/invalid/expired,last_verified_at 只在失败时写 没有"需验证"态、没有上次登录时间
删除 无删除站点 / 账号接口 需要
全局默认 settings 无站点默认项;snapshotIntervalSec=null 时不做定时快照 需要 sites.* 默认

站点定义:23 个里 22 个有 umt.login(path/usernameField/passwordField/errorCss/extraFormData/selectDefaults),馒头只支持 apikey;chdbits、ssd 的 umt.profile.vip_css 已有。

4. 站点页规格(/console/sites,唯一入口)

4.1 页面

  • 页头:标题"站点"、视图切换(卡片 / 列表,写 URL ?view=)、刷新、"添加站点"。筛选:全部 / 已启用 / 需处理(账号非正常或健康 failing)。
  • 卡片与列表同字段,列表可按分享率、上传、上次登录排序。点名称进详情(整理视图 / 原站视图 / 快照与 Feed / 账号 4 页签不变,"账号"页签改为本页抽屉的完整版,含用户数据历史)。

4.2 卡片字段

字段 来源 显示规则
名称、域名 site.name、site.base_url 主机名 域名可点,新窗口开原站
启用 site.enabled 开关
账号状态 site_account.status 正常(active)/ 已失效(expired、invalid)/ 需验证(challenge)/ 未验证(unknown)/ 未配置(无账号);多账号取最差
凭证类型 auth_kind Cookie / API Key / 账号密码;另有 passkey 时加"Passkey"小标
上次登录 last_login_at 相对时间;从未成功显示"—"
上传 / 下载 / 分享率 user_data.upload/download/ratio 字节格式化;分享率 null 显示"—",download=0 显示"∞"
等级、VIP user_data.userLevel、user_data.vip VIP 为 true 才显徽标
做种数(体积) seeding、seedingSize
魔力 bonus 0 且站点不提供时不显
未读消息 messageUnread >0 才显
健康检测 site.last_health + last_health_at "正常 · 320 ms · 5 分钟前" / 失败原因一句
快照 last_snapshot_at、周期 过期标黄(沿用 snapshotStale)
限流 site.rate_limit,空则"默认" 例:"20 次/分"

用户数据时间戳(user_data_at)显示在数据区角落:"数据 2 小时前"。

4.3 操作

卡片主按钮:检测健康、刷新登录;"更多"菜单:更新 Cookie / 更新 API Key / 更新密码(按凭证类型显示一项)、编辑、快照、停用 / 启用、删除。

操作 行为 端点
添加站点 三步对话框:① 选定义(搜索框 + 列表,已登记的置灰;"未适配站点"只能 generic_rss)② 域名覆盖(默认定义域名)+ 凭证(按定义 authSupported 列出可选类型)+ 可选 UA ③ "测试"=保存后立即刷新登录,显示结果(成功带用户名 / 等级),失败可返回修改;"保存"关闭 POST /sites → PUT /sites/{id}/credentials → POST /sites/{id}/refresh-login
编辑 抽屉:名称、域名、限流(空 = 全局默认)、定时快照开关与周期、UA、使用代理 PATCH /sites/{id}
检测健康 首页连通 + 登录态,同步返回(≤20 s,走站点限流) POST /sites/{id}/health-check
刷新登录 账密:重新登录拿 Cookie;Cookie / API Key:校验;成功后拉用户数据。异步任务,返回 taskId,卡片转圈直到任务结束 POST /sites/{id}/refresh-login
更新凭证 小对话框,只写,不回显;保存即触发刷新登录 PUT /sites/{id}/credentials
删除 确认框写明后果:"删除站点、账号和凭证;已抓到的资源和下载任务保留。不可恢复" DELETE /sites/{id}

4.4 账密登录(NexusPHP takelogin)

适配器新增 packages/adapters/src/sites/login/nexusphp.ts,输入定义的 umt.login + 用户名 / 密码 / 可选 TOTP 密钥,输出 { ok, cookie, userAgent } 或 SiteLoginError{ reason }。流程对齐 MovieClaw CredentialAuthProvider:

  1. 用固定 UA 清空 Cookie GET login.php(umt.login.path),记下 Set-Cookie。
  2. 页面匹配 imagecode.php?… → 验证码。本期不做 OCR:直接失败,reason=captcha。
  3. 取登录表单(form[action*=takelogin],缺省 takelogin.php)的隐藏字段,填 usernameField / passwordField、selectDefaults、extraFormData;有 TOTP 密钥且表单有 two_step_code/otp 字段时填当前 6 位码。
  4. POST(不跟随 3xx),再 GET index.php;loginSuccess 命中 → 成功,拼 Cookie。
  5. 失败分支:errorCss 文本归类 —— 账号或密码错(bad_credentials)、需要二次验证而无 TOTP(two_factor)、验证码(captcha)、账号封禁 / 未确认(account_blocked)、Cloudflare 挑战(challenge)、其它(unknown,原文截 200 字)。

服务端处理:

  • 成功:派生 Cookie 存入 session_secret_ref(purpose site_cookie,label site_account:<label>:session),status=active,last_login_at=now;之后请求优先用会话 Cookie,遇 auth_expired 自动重登一次(同账号 10 分钟内最多一次),仍失败再走现有 onAuthError。
  • captcha / two_factor / challenge:status=challenge,开待处理(kind 复用 source_unreachable,dedupeKey=site-login:<accountId>),标题"站点「X」登录需要人工验证",摘要"登录页要求验证码 / 二次验证,无法自动登录。改用 Cookie 登录,或填写 TOTP 密钥后重试",选项:改用 Cookie(深链站点页并打开凭证对话框)、重试登录、忽略。
  • bad_credentials / account_blocked:status=invalid,同一待处理,选项去掉"重试登录"。
  • 表单里提供"只换 Cookie,不保存密码"(MP 做法):登录成功后凭证类型直接落为 Cookie,密码丢弃。
  • 馒头及 authSupported 不含 password 的站点,对话框不显示账号密码。

4.5 用户数据

  • 调用 refreshSiteUserData({ name, url, schema, cookie|apikey, limiter, fetchMessages:false }),做种分页上限由全局 sites.userDataMaxSeedingPages(缺省 10;MP 为 50)控制,请求全部过站点令牌桶。
  • VIP:SiteUserInfo 增加 vip: boolean | null;定义有 umt.profile.vip_css 时在首页 / 详情页判定,否则按新可选字段 umt.vipLevels(等级名列表)匹配,都没有为 null(不显示)。
  • errMsg 非空或 userId 为空:视为登录态失效,走 4.4 失败处理;数据不覆盖上次成功值。
  • 首页已显示登录态时顺手更新 last_login_at(Cookie / API Key 校验成功也算"登录")。

4.6 状态机(site_account.status)

unknown →(刷新登录成功)active →(请求遇 auth_expired 且重登失败)expired;任意态 →(验证码 / 二次验证 / 挑战)challenge;→(密码错 / 封禁)invalid;更新凭证 → unknown 并关闭 site-auth:* 与 site-login:* 待处理(现有逻辑扩展)。

5. 数据模型(迁移 0031_site_center.ts)

site_account(沿用 auth_kind 作为凭证类型,不改名;status 即登录状态):

列 类型 说明
status 检查约束加 challenge contracts SiteAccountStatus 同步
login_name text null 账密登录名;从 user_info.username 迁出
totp_secret_ref uuid null 可选 TOTP 密钥(purpose 新增 site_totp)
session_secret_ref uuid null 账密登录得到的会话 Cookie
user_agent text null 空 = 全局默认 UA;Cookie 与 UA 需成对时填
last_login_at timestamptz null 最近一次登录 / 校验成功
last_error jsonb null {reason, message, at}
user_data jsonb not null default {} 最近一次成功的 SiteUserInfo 去掉 raw、messageUnreadContents、seedingInfo
user_data_at timestamptz null

user_info 保留只读兼容一版,迁移把 username 写入 login_name。

新表 site_user_data(按天一行,MP siteuserdata 对应):id, site_account_id → site_account on delete cascade, day date, upload bigint, download bigint, ratio numeric null, bonus numeric null, seeding int, seeding_size bigint, leeching int, user_level text null, vip boolean null, captured_at timestamptz,唯一 (site_account_id, day),同日 upsert 取最新;保留 400 天(并入现有日志保留清理)。

site:加 last_health jsonb null({ok, status, latencyMs, message})、last_health_at timestamptz null、proxy boolean not null default false。

contracts:SiteAccount 增加上表字段(secret ref 不外露,只给 hasTotp、hasSession 布尔);Site 增加 lastHealth、lastHealthAt、proxy;新增 SiteUserData、SiteHealthResult、SiteCredentialsPut 改为按 kind 的判别联合。

6. 后端端点

方法 路径 scope 说明
GET /sites?include=accounts read 列表附带账号摘要(状态、凭证类型、last_login_at、user_data),站点页一次请求
POST /sites / PATCH /sites/{id} admin 现有;PATCH 加 proxy
DELETE /sites/{id} admin 删站点、账号、secret;source_release 保留(外键改 set null 或保留 site_id 只读,实现时确认)
POST /sites/{id}/health-check admin 同步,返回 SiteHealthResult 并写 site.last_health*、health_status
POST /sites/{id}/refresh-login admin body {accountId?};入队引擎任务 refreshSiteAccount,返回 {taskId};幂等键按 10 s 取整
GET /sites/{id}/user-data read ?accountId&days=30,返回 site_user_data 序列
PUT /sites/{id}/credentials admin body 判别联合:{kind:"password", label, username, password, totpSecret?, saveCookieOnly?} / {kind:"cookie", label, cookie, userAgent?} / {kind:"api_key", label, apiKey};passkey 仍用 {kind:"passkey", passkey} 附加到同账号(RSS / 下载用,不参与登录)
DELETE /site-accounts/{id} admin 删账号与其 secret

/site-accounts 的 POST / PATCH(凭证部分)标弃用,前端全部改走 PUT /sites/{id}/credentials;Agent 能力表(capabilities.ts)登记 sites.healthCheck、sites.refreshLogin 两个只读风险低的能力。

7. 定时任务

在 sources-identity/index.ts 用现有 job() 登记:

key 名称 分组 默认 行为
sites.userData 站点数据刷新 collect 每 6 小时,firstRun: after_interval 对启用站点的启用账号依次入队 refreshSiteAccount(不重登,只有遇失效才重登);跳过 challenge/invalid 账号;只读对账模式跳过

另:sites.snapshot 对 snapshotIntervalSec=null 的站点改用全局默认周期。

8. 设置页"站点与账号"改为"站点默认"

  • SETTINGS_SECTIONS.sites:label 改"站点默认",description"新站点的限流、快照周期、UA 和代理;站点和凭证在站点页管理",resources: [],fields 如下(新存储 key sites,contracts SitesSettings):
字段 缺省 说明
sites.defaultRateLimit {requestsPerMinute: 20, concurrency: 1} 站点 rate_limit 为空时使用
sites.defaultSnapshotIntervalSec 3600 站点未设周期时使用
sites.userAgent 固定桌面 Chrome UA 账号未设 UA 时使用
sites.proxyUrl 空 站点开"使用代理"时生效
sites.userDataMaxSeedingPages 10 用户数据做种分页上限
  • 删除 components/settings/sites.tsx 里的站点列表、登记抽屉、凭证表单、账号列表;节顶部放一行"站点和凭证在「站点」页管理"+ 跳转链接。
  • 迁移:legacyTabs 保留 site-credentials,但落点改为 /console/sites;settingsPath("sites", "accounts") 的调用点全部改为 consolePath("sites");冒烟 REDIRECT_ROUTES 补一条。
  • 数据迁移:无(旧设置里没有站点默认值;现有站点的 rate_limit 保留)。
  • console-ia.md §6 设置分区表与 §6.3"站点三处合一"行在实施后更新。

9. 文案(按 glossary)

  • 页名"站点"(侧栏"站点工作台"改名);凭证统称"凭证";不写"PT 源""认证信息"。
  • 按钮:添加站点、检测健康、刷新登录、更新 Cookie、更新 API Key、更新密码、删除站点。
  • 空态:"还没有站点。添加站点并填写凭证后,这里显示账号数据和健康状态"。
  • 错误示例:"登录失败:站点要求验证码。改用 Cookie,或稍后在站点页重试"、"Cookie 已失效。更新 Cookie 后重新刷新登录"。
  • 账密说明只陈述事实:"密码加密保存,用于 Cookie 失效后自动重新登录。选'只换 Cookie'则不保存密码"。

10. 仪表盘调整

MP 仪表盘组件与数据源:媒体统计(dashboard/statistic:电影 / 剧集 / 集 / 音乐数)、存储(dashboard/storage:总 / 已用)、下载速度(dashboard/downloader:上下行速度、累计、剩余空间)、后台服务(dashboard/schedule + 整理队列)、最近入库(history/transfer)、周整理量(dashboard/transfer)、媒体服务器最新 / 在播 / 媒体库、CPU / 内存 / 网络 / 进程 / 系统信息、快捷操作。

区块 处理 数据来源 位置
自动化概览 保留;加"媒体库"一行:电影 / 剧集 / 集数(MP 媒体统计并入) GET /dashboard 新增 library{movies, series, episodes, assets}(按 media_entity、content_unit、media_asset 计数) 第一行左
当前工作 保留;页头加下载器实时速度 ↓/↑ 与活动数(MP 下载速度并入) /dashboard 新增 downloaders[]{id, name, dlSpeed, upSpeed, freeBytes},来自 qB transferInfo(),服务端 5 s 缓存 第一行中
待处理 保留 现有 第一行右
缓存水位 → 存储 合并:下载缓存卷 + 库根所在卷(MP 存储) /dashboard.cache 保留;新增 storage[]{rootId, label, totalBytes, usedBytes}(库根 statfs,按设备去重) 第二行左
来源健康 保留;站点行加账号状态点与分享率,需处理的排前 /dashboard.sources.sites 增加 accountStatus、ratio、lastLoginAt 第二行中
最近产出(= MP 最近入库) 保留,加海报缩略图 现有 recentOutput 加 posterPath 第二行右
定时任务 新增小块:失败的任务在前,其余显示下次运行 前端直接用 GET /schedules(已有) 第三行,整宽窄条

不做:CPU / 内存 / 网络 / 进程 / 系统信息(与媒体流程无关)、媒体服务器在播与最新(v0.3 再评估)、周整理量图(自动化概览已有今日计数)、快捷操作(侧栏与 Agent 已覆盖)、站点统计单独区块(并入来源健康)。

11. 实施拆分

路 范围 主要文件 估算
后端 B 迁移 0031;contracts(sources.ts、instances.ts 凭证联合、settings.ts 的 SitesSettings 与分区、organization.ts Dashboard 扩展);NexusPHP 登录模块 + fixture 测试(成功 / 密码错 / 验证码 / 二次验证 / TOTP);SiteUserInfo.vip 与 umt.vipLevels schema;refreshSiteAccount 引擎任务、会话 Cookie 优先与自动重登;4.3 / 6 节端点;sites.userData 定时任务;默认值接入快照与限流;dashboard 聚合(库计数、qB 速度、库根 statfs);Agent 能力登记;vertical 测试补一条"添加站点 → 刷新登录 → 用户数据入库" packages/db/migrations/0031_site_center.ts、packages/db/src/types.ts、packages/contracts/src/{sources,instances,settings,organization,api}.ts、packages/adapters/src/sites/{login/*,user-parsers/types.ts,definitions/schema.ts}、apps/server/src/modules/sources-identity/{routes/sources.ts,service/account.ts(新),tasks.ts,index.ts,capabilities.ts}、auth-settings/instances-routes.ts(凭证 PUT)、acquisition-organize/routes/dashboard.ts 3–3.5 天
前端 F 站点页重写(卡片 / 列表切换、筛选、字段、操作菜单);添加站点三步对话框(替换 site-register-dialog.tsx);凭证对话框(三种 kind + 只换 Cookie + TOTP);编辑抽屉;详情"账号"页签加用户数据折线(近 30 天上传 / 分享率);设置分区改"站点默认"并删旧表单;settingsPath("sites") 调用点与重定向;首页区块调整;侧栏改名;冒烟与 nav 测试 routes/console/sites.tsx、sites.$siteId.tsx、components/workbench/{site-card.tsx(新),site-list.tsx(新),site-register-dialog.tsx,site-credential-dialog.tsx(新),site-accounts.tsx}、components/settings/sites.tsx、api/sites.ts、api/dashboard.ts、routes/console/home.tsx、components/console/nav.ts、scripts/smoke.mjs 2.5–3 天

顺序:B 先交 contracts 与迁移(半天),F 基于 contracts 用 mock 并行;testkit mock 站点补 login.php/takelogin.php 与用户详情页,两路共用。

12. 待核验 / 风险

  1. takelogin 表单字段、二次验证字段名、错误文本全部未经真实站点核验(同 README"待真实站点核验")。
  2. 保存密码是与 MP 不同的取舍(MovieClaw 保存);默认选"保存",可选"只换 Cookie"。
  3. 用户数据做种分页请求量大,限流默认值需在真实账号上调。
  4. Cloudflare 站点需要浏览器服务(camofox)登录,本期只识别并转待处理。
  5. DELETE /sites/{id} 对 source_release.site_id 外键的处理需实现时确认,不能级联删资源。