Files
UHD_Media_Tool/docs/design/site-center.md
T

223 lines
21 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.
# 站点中心与仪表盘调整(v0.2.1)
> 依据:`docs/plan/00-first-version-plan.md` §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` 外键的处理需实现时确认,不能级联删资源。