21 KiB
订阅重做 + 规则页(对标 MoviePilot v3)
需求来源:
docs/plan/00-first-version-plan.md§8 第 3、4 条(订阅重做、收录策略 + 偏好标签合一)。命名模板另见naming-template.md。 上游行为账本:docs/porting/moviepilot-subscribe.md;差距清单:docs/plan/05-subscription-gap.md;规则关键字核对:docs/porting/mp-rust-logic-parity.md§6。上游jxxghp/MoviePilotv3 @e6820a8,只记行为,不复制源码。
0. MP 要点与我们的现状
MP 订阅页(pages/subscribe.vue、views/subscribe/SubscribeListView.vue、components/cards/SubscribeCard.vue)
- 按类型分路由:电影 / 电视剧 / 音乐;每类 Tab「我的订阅 / 热门 / 分享」。
- 筛选(剧集):全部 / 洗版 / 未开始 / 订阅中 / 待定 / 暂停 / 已完成。归类顺序:洗版 → P 待定 → S 暂停 →
lack_episode==0已完成 →completed_episode>0订阅中 → 未开始。没有分组,只有筛选。 - 排序:自定义拖拽 / 上次更新 / 添加时间 / 缺集数(剧集),按类型记在本地。批量:全选、启用、暂停、删除。
- 卡片:背景图 + 海报、年份、名称 + 季(S0 显示"特别篇")、进度
total−lack / total、洗版进度条(已洗版/已下载双段)、洗版角标、状态角标(只显示 暂停 / 待定)、用户名、上次更新(相对时间)。不显示站点、下载器、规则组。 - 菜单:编辑 / 搜索 / 暂停·启用 / 重置 / 分享 / 媒体详情 / 文件统计 / 取消订阅。
- 编辑对话框两 Tab。基础:搜索关键词、总集数、开始集数、质量/分辨率/特效、订阅站点、下载器、保存路径、洗版、全集洗版、用 ImdbID 搜索。进阶:包含、排除、优先级规则组、剧集组、指定季、自定义类别、识别词。"默认订阅规则"用同一对话框,按类型存
Default{Movie|Tv}SubscribeConfig。 - 文件统计对话框:按集两 Tab「下载文件 / 媒体库文件」。选季对话框:每季显示集数、缺失/已订阅,可选 普通 / 分集洗版 / 全集洗版。日历:月视图,事件按
air_date聚合,入库状态 无 / 部分 / 完整。 - 进度口径:
lack_episode存库(普通 = 媒体库缺集;洗版 = 范围内 note 与 pri 都没有的集),completed_episode响应时派生(洗版按 pri==100 计)。
MP 规则(app/application/rules.py:510-656、modules/filter/__init__.py、views/setting/AccountSettingRule.vue)
- 内置规则 = 正则原子,默认匹配
标题+副标题+标签,可限定字段;可带tmdb条件(命中即放行,CNSUB/CNVOI 用original_language)。自定义规则字段:id、name、include、exclude、size_range(MB,剧集按每集)、seeders、publish_time,同 id 覆盖内置。 - 规则组 =
name + rule_string + media_type + category;>分层,层内! > & > |与括号;第 1 层 pri=100 逐层 −1,一层不中即过滤。多组按 media_type/category 取适用组后串联 AND,pri 取最后一组。 - 默认组:订阅默认、洗版默认、搜索默认,各为组名列表;订阅
filter_groups非空则覆盖默认。 - UI:三张卡片(自定义规则 / 优先级规则组 / 下载优先规则);规则组编辑 = 组名 + 媒体类型 + 分类 + 可拖拽"优先级 N"层卡片,每层多选 chips(内置 + 自定义 + "排除:X"),保存时层内
&、层间>;|与括号只能靠导入字符串。JSON 导入/分享。
我们
- 订阅 = 本地作品 + 季单元 +
season_scope+params;条件在版本目标(Slot);状态 active/paused/pending/completed/cancelled;已有/progress、/search、/episode-total、历史。订阅页是表格 + 详情抽屉,进度只算第一个版本目标(subscriptionProgress取slots[0])。 - 版本目标已有:硬条件(
SlotHardConditions,含 include/exclude、站点、做种、免费)、优先级阶梯soft.tiers[](层内字段 AND)、三种洗版模式、分集质量账本slot_unit。 - 收录策略(
policy_revision kind=acquisition,草案→发布→回滚,含 newTitle / defaultSlots)与偏好标签(preference_tag+ 抑制记录)是两页;偏好标签没接入评估(差距 #17)。 - 阶梯层内只能 AND 结构化字段;MP 的 SPECSUB/CNVOI/HKVOI/GZ/ATMOS/60FPS/3D/UHD、跨字段 OR 表达不了(parity §6:43 条样例 ✔22 ~6 ✗15)。
1. 订阅总览
路由 /console/subscriptions,页头 Tab:剧集 / 电影 / 综艺 / 日历(P2),选中项写 URL(?kind=)。视图切换 卡片 / 列表(记本地)。
1.1 分组(派生,不新增状态值)
| 分组 | 条件(按顺序判定) | 对应 MP |
|---|---|---|
| 暂停 | status=paused |
S |
| 待定 | status=pending(未建档 / 无 TMDB 身份),或任一范围内季 totalKnown=false |
P |
| 已完成 | status=completed,或 continuous 订阅所有版本目标在范围内全覆盖且 total 已知(卡片仍追下一季时显示"等待新季") |
lack=0 |
| 追更中 | active 且至少一个单元已入库或在途 | 订阅中 |
| 待搜索 | active 且没有任何已入库/在途单元 | 未开始 |
"洗版中"不单列分组,作为徽标(任一版本目标开升级且未达顶档)与筛选项。cancelled 不在总览,只在"订阅历史"。
默认按分组折叠展示(待定、追更中、待搜索、已完成、暂停),组内按上次更新倒序。也可切成平铺 + 排序:上次更新 / 下次搜索 / 缺集数 / 添加时间 / 名称。筛选:分组、洗版中、站点、规则组、来源(用户/Agent/策略)、关键词;全部进 URL query(use-query-state.ts)。
上次更新 = subscription.last_progress_at(新列):任一关联版本目标的单元转为 已入库 / 在途,或总集数变化时写入;为空取 created_at。与 MP last_update 同义(MP 在下载添加时更新)。
1.2 卡片字段
| 区 | 字段 | 数据 |
|---|---|---|
| 封面 | 占位块:类型图标 + 标题首字(暂无海报;有 TMDB poster_path 后再接) | — |
| 标题行 | 名称、年份、类型标签;剧集加季(S1,S0 显示"特别篇",following_seasons 显示"S2 起") |
实体、季单元 |
| 进度 | 已入库/总,总未知显示 ?;下方细条:已入库、在途两段;多版本时显示主版本数字,悬浮列出每个版本 |
overview.progress |
| 版本徽标 | 每个版本目标一个:名称 + 状态色(已齐 / 缺 N / 在途 N / 洗版中 pri a→目标 b) | overview.slots |
| 元信息 | 上次更新(相对时间)、下次搜索(或"搜索已关")、站点范围(≤2 个名 + "+N";空 = 全部站点)、规则组(同样折叠) | |
| 状态角标 | 仅 暂停 / 待定 / 洗版中(同 MP 只标异常态) | |
| 操作菜单 | 立即搜索、查看对比、编辑、暂停/启用、重置(P2)、删除;点卡片 = 查看对比 |
批量模式:勾选后 立即搜索 / 暂停 / 启用 / 删除(删除走确认,写审计)。空态:"还没有订阅。从媒体库或 Agent 添加。"
2. 对比视图(订阅详情)
路由 /console/subscriptions/:id(取代抽屉;抽屉保留为列表页快速预览)。页头:标题、状态、进度、操作按钮;下方三块:覆盖矩阵、版本目标、记录(搜索记录、候选评估、订阅历史)。
2.1 覆盖矩阵
- 列 = 集,按季分段(季标题显示
已入库/总 · 总集数来源 · 锁定,可点"设置总集数");行 = 版本目标,每个一行;首行"库内任一版本"汇总。 - 电影:每个版本目标一格。
| 格状态 | 显示 | 来源 |
|---|---|---|
| 已入库·达标 | 实心,文字 2160p REMUX |
slot_unit + asset |
| 已入库·可升级 | 实心 + 角点,悬浮 pri 97 → 目标 100 |
slot_unit.pri < targetPri |
| 在途 | 斜纹,计划状态(已预留/下载中/核验中) | 在途计划 unit_keys |
| 延后 | 空心 + 时钟 | slot_deferral |
| 缺失 | 空心红框 | computeUnitGaps |
| 疑似缺失 | 虚线红框 | total 未知时的集号空洞 |
| 未播出 | 灰,显示播出日期 | content_unit.air_date > 今天 |
| 范围外 | 淡灰 | episodeRange / season_scope |
点格打开侧栏:资产位置(链到文件管理)、质量与 pri、来源资源与下载任务、该单元的候选评估(接受/拒绝理由 trace)、操作"搜索此集""为此版本补全"。
2.2 多版本与自动补全
- 每个版本目标一行,行尾:缺失数、在途数、"补全此版本"按钮、自动补全状态。
- 自动补全其他版本:订阅级开关
params.autoFill: "inherit" | "on" | "off",缺省 inherit → 取引用规则组/订阅默认值的autoFill(系统缺省 on)。- on:非主版本目标像主版本一样独立评估、搜索、下载缺失单元。
- off:非主版本目标只登记缺失,不自动建计划;RSS 来的合格资源记为"延后(未开自动补全)";手动"补全此版本"或 Agent 调
download.plan仍可下。 - 主版本 = 版本目标排序第一个(可在版本目标区拖动)。
- 补全预算
params.fillBudget {bytesPerDay?, unitsPerDay?}:只约束非主版本的自动补全计划,计入全局每日预算;超出记延后,次日由slots.reevaluate重评。计划优先级用upgrade档(低于追更)。
3. 新建 / 编辑订阅表单
对话框两 Tab,同 MP 分"基础 / 进阶"。"订阅默认值"(按类型)用同一表单的默认模式,存规则页 §6.4。
| 区 | 字段(界面) | MP 字段 | 我们存放 | 默认 / 显示条件 |
|---|---|---|---|---|
| 基础 | 作品(搜索本地作品或 TMDB) | media_id / name | mediaEntityId / titleQuery |
必填 |
| 类型 | type | mediaKind |
由作品带出 | |
| 季 / 季范围 | season | seasonUnitId + seasonScope |
剧集;缺省最新季 + 接续后续季(Agent 来源缺省仅本季) | |
| 开始集 / 结束集 | start_episode | params.episodeRange {start,end} |
剧集 | |
| 总集数(可锁定) | total_episode / manual_total_episode | POST /episode-total |
剧集;显示来源 | |
| 方式 | — | mode once / continuous |
电影缺省 once | |
| 优先级 | — | priority |
tracking | |
| 站点范围 | sites | params.siteIds |
空 = 全部启用站点 | |
| 下载器 | downloader | params.downloaderInstanceId |
空 = 默认实例 | |
| 库根 | save_path(整理去向部分) | params.libraryRootId(新) |
空 = 按类型默认库根 | |
| 版本目标 | best_version / best_version_full | 子表:名称 + 规则组 + 升级(关 / 按集 / 整季 / 电影)+ 目标 pri | 缺省取订阅默认值 | |
| 进阶 | 规则组 | filter_groups | params.ruleGroupIds(作用于全部版本目标)+ 版本目标 rule_group_ids |
空 = 订阅默认组 |
| 包含 / 排除 | include / exclude | 订阅私有规则组(scope.subscriptionId,不在列表页显示) |
正则白名单校验 | |
| 分辨率 / 来源 / 特效 | resolution / quality / effect | 同上,写进私有规则组 hard | ||
| 搜索关键词 | keyword | params.keywords[](新,追加到标题/别名检索词) |
||
| 主动搜索间隔 | —(全局) | params.searchIntervalHours |
24;0 = 关 | |
| 自动补全 / 补全预算 | — | params.autoFill、params.fillBudget |
inherit | |
| 截止时间 | — | params.stopAfter |
||
| 下载保存路径 | save_path | params.downloadSavePath(新,P2) |
空 = 实例默认 | |
| 不做 | 剧集组 episode_group | P2(需 TMDB episode group 适配) | ||
| ImdbID 搜索 search_imdbid | P2 params.searchByImdb |
|||
| 自定义类别 media_category | 用命名模板 category |
|||
| 识别词 custom_words | 放身份层(实体别名/集偏移),表单给链接 |
保存:创建时同实体同季已有 active 订阅 → 返回 409 并给"打开已有订阅"(差距 #18)。
4. 日历(P2)
/console/subscriptions?kind=calendar:月视图,事件 = 追踪中订阅范围内集单元的 air_date(同日同剧聚合为 E03–E04),电影取上映日期;颜色 = 该日事件 已入库 / 部分 / 未入库 / 未播出。数据来自 refreshMetadata 写入的集单元 air_date(需补:目前只写季单元总集数,不建集单元)。接口 GET /subscriptions/calendar?from&to。
5. 后端与 contracts
| 端点 | 用途 | 新/改 |
|---|---|---|
GET /subscriptions/overview?kind&group&sort&siteId&ruleGroupId&q&cursor |
卡片数据:分组、进度、每版本摘要、上次更新、下次搜索、站点、规则组 | 新 |
GET /subscriptions/{id}/coverage |
覆盖矩阵:季 × 集 × 版本目标 | 新(取代 /progress,后者保留一版兼容) |
POST /subscriptions/batch |
{ids, action: search|pause|resume|delete} |
新 |
POST /subscriptions/{id}/slots/{slotId}/fill |
手动补全某版本(可带 unitKeys) | 新 |
POST /subscriptions/{id}/reset |
清在途预留、重评(保留资产) | 新(差距 #24) |
GET /subscriptions/calendar |
日历 | 新,P2 |
PATCH /subscriptions/{id} |
params 新键:ruleGroupIds keywords libraryRootId autoFill fillBudget downloadSavePath |
改 |
contracts(policy.ts):SubscriptionOverviewItem、SubscriptionCoverage {seasons[{seasonNumber,total,totalKnown,source,locked,episodes[{episode,airDate}]}], rows[{slotId|null,name,primary,autoFill,cells[{unitKey,state,quality,pri,planId,assetId}]}]}、SubscriptionBatch、SubscriptionParams 扩键;规则页见 §6.6。
覆盖矩阵查询:一次取 作品全部集单元 → 左连 asset_unit_coverage(库内任一版本)、slot_unit(按版本目标)、在途计划 unit_keys、slot_deferral;在服务里用 computeUnitGaps 逐版本目标算状态。总览用一条聚合 SQL 出每订阅 × 版本目标的 已入库/在途/总 计数,避免 N+1;超 200 条订阅走游标分页。
DB:subscription.last_progress_at;acquisition_slot.rule_group_ids uuid[]、acquisition_slot.position int(主版本排序);rule_group、rule_atom 两表(§6);偏好标签迁移。
定时任务(沿用注册表):subscriptions.search、subscriptions.metadata(补:建集单元与 air_date;完成前强制刷新 = 差距 #8)、slots.reevaluate(补:按 autoFill 与 fillBudget 过滤非主版本)。不新增任务。
6. 规则页(收录策略 + 偏好标签合一)
路由 /console/rules,页名"规则"。Tab:规则组 / 自定义规则 / 默认值 / 收录策略 / 抑制记录。/console/preferences 重定向到 /console/rules?tab=groups(带 origin 筛选时透传);术语表加"规则组",弃用"偏好标签"(对话与 Agent 产生的规则组以"来源"区分)。
6.1 规则组结构
| 字段 | 含义 |
|---|---|
| name、description | 名称必填唯一;说明一句 |
| hard | 硬条件(必须 / 排除),即 SlotHardConditions,再加 atoms {require[], exclude[]} 引用原子 |
| ladder | 优先级阶梯:有序层,每层一个布尔表达式 expr(原子 + `! & |
| ladderRequired | 一层不中即拒绝(MP 语义,缺省 true;从 MP 串导入时固定 true) |
| scope | mediaKinds[]、entityIds[]、siteIds[]、subscriptionId(私有组);空 = 全部 |
| origin | manual / conversation / agent_inferred(沿用偏好标签来源) |
| status、revision、fingerprint、evidence | 同偏好标签;每次修改 revision+1 并写审计,评估 trace 记 组id@revision |
| autoFill | 引用本组的订阅缺省是否自动补全其他版本 |
原子(rule_atom):内置(只读)+ 自定义。自定义原子字段对齐 MP 自定义规则:id(大写字母数字)、name、include[]、exclude[]、matchFields(title/subtitle/labels,缺省全部)、unitSizeMB {min,max}(剧集按每集)、minSeeders、publishDelayMinutes {min,max}、free。同 id 不允许覆盖内置(偏离 MP,避免静默改义)。
6.2 内置原子(对齐 MP 关键字)
| 原子 | 语义 | 我们的判定 |
|---|---|---|
| 4K / 1080P / 720P | 分辨率 | resolution 字段 |
| BLU | 蓝光原盘 | source = BluRay-Disc(先修 parity our_bug:Blu-ray+AVC/HEVC 无 x26x 判原盘) |
| BLURAY | 标题含 Blu-ray | 来源属蓝光族(原盘/REMUX/压制) |
| REMUX / WEBDL | Remux;WEB-DL 或 WEBRip | source 字段 |
| UHD | 标题含 UHD/UltraHD | parsed.uhd |
| H265 / H264 | 编码 | videoCodec |
| HDR / DOLBY / SDR | HDR 类;杜比视界;SDR | hdr 字段(HDR = 任一非 DV HDR,同 MP 正则口径做差分) |
| ATMOS | 全景声 | audio 含 Atmos |
| 60FPS / 3D | 帧率;3D | fps>=60;正则 3D |
| CNSUB | 中字 | subtitles 含 zh(补 CHS/CHT/GB/BIG5);作品原语言 zh 时直接满足(MP tmdb 放行) |
| SPECSUB | 特效字幕 | 正则 特效(标题+副标题+标签) |
| CNVOI / HKVOI | 国语配音 / 粤语 | 正则;CNVOI 作品原语言 zh 时满足 |
| GZ | 官种 | 只看站点标签 官方/官种/官组 |
| FREE | 免费 | downloadVolumeFactor = 0 |
| 音乐 13 条(HIRES/FLAC/…) | — | 不做 |
6.3 MP 规则串 ↔ 结构化
- 规则组编辑页有"规则串"输入框(高级):输入
SPECSUB & CNSUB > CNSUB & 4K > 1080P & !BLU实时解析,下方同步画出层卡片;反过来编辑层卡片时规则串随之更新(规范化:原子大写、层内按|优先级加最少括号)。 - 解析错误逐条给位置:未知原子(给相近名)、括号不配对、空层。音乐原子报"不支持"。
- 层卡片编辑:每层是 chips 行,chip = 原子或取反原子;层内 chips 之间可切换
且/或,支持"加括号组";拖动层即改优先级(同 MP),层标题显示优先级 1 · pri 100。 - 测试:"用最近资源测试"对 200 条近期资源跑本组,显示每条命中层/被拒原因,和 MP trace 同粒度;也可粘贴标题测试。
- JSON 导入/导出兼容 MP 格式(
name/rule_string/media_type,自定义规则 7 字段),media_type 映射到mediaKinds,category 丢弃并提示。
6.4 引用关系与默认值
- 版本目标
rule_group_ids(有序)+ 订阅params.ruleGroupIds;评估时编译为 SlotSpec:hard 取交集;各组按 scope 过滤后全部须命中某层,pri 取最后一个带阶梯的组(同 MP),界面在该组上标"决定优先级"。 - 版本目标自身的 hard/soft 字段保留,视为一个匿名组排在最前。
- 默认值 Tab:按类型设 订阅默认规则组、洗版默认规则组(版本目标开升级时额外叠加)、搜索默认规则组(手动搜索与 Agent
release.search)、订阅默认表单(§3 默认模式:站点、下载器、库根、版本目标模板、自动补全)。 - 收录策略 Tab:现有策略工作区原样搬入(草案→预览→发布→回滚、newTitle、defaultSlots);
SlotTemplate增加ruleGroupIds。 - Agent:能力
rules.list读规则组;新增/修改走rules.propose(origin=agent_inferred,生成待处理事项,批准后生效);命中有效抑制记录的指纹直接拒绝。对话中用户明确说的偏好 origin=conversation,直接生效并可撤销。
6.5 偏好标签迁移
| 旧 kind | 新规则组 |
|---|---|
| hard | hard 条件,ladder 空 |
| soft | ladder 单层(或按 rule 顺序多层),ladderRequired=false |
| version | 规则组 + 在"默认值"里挂到对应版本目标模板 |
| target | 转收录策略 newTitle scope,原标签留一条只读规则组作说明 |
指纹、来源、证据、修订历史、抑制记录原样迁移(抑制按新组指纹重算一次,旧指纹保留作别名)。迁移后偏好标签接入评估,关闭差距 #17。
6.6 规则接口
GET/POST /rule-groups、GET/PATCH/DELETE /rule-groups/{id}、GET /rule-groups/{id}/revisions、POST /rule-groups/parse {ruleString} → {ladder, errors[]}、POST /rule-groups/render {ladder} → {ruleString}、POST /rule-groups/{id}/test {sourceReleaseIds?|titles?}、GET/POST/PATCH/DELETE /rule-atoms、GET/PUT /rule-defaults、POST /rule-groups/import、GET /rule-groups/export。抑制记录接口沿用 /preference-suppressions(改名 alias /rule-suppressions)。
domain:slot/rule-expr.ts(词法/解析/求值/规范化输出)、slot/atoms.ts(内置原子表)、SlotTierRule.expr?;mapping/slot.ts 编译规则组 → SlotSpec。差分:filter-cases.json#rules 43 条全部重跑,目标 ✗ 从 15 降到 1(只剩音乐)。
7. 实施拆分与估算
| 包 | 内容 | 估 | 依赖 |
|---|---|---|---|
| R1 domain | rule-expr 解析/求值/串互转、内置原子(含 BLU/CHS 修正)、多组编译、差分样例 | M 2 天 | — |
| R2 server 规则 | 迁移(rule_group、rule_atom、slot.rule_group_ids)、CRUD/parse/render/test/import、偏好标签迁移、评估接入、Agent rules.propose |
L 3 天 | R1 |
| R3 web 规则页 | 5 个 Tab、层卡片编辑 + 规则串双向、测试面板、/preferences 重定向、术语表 |
L 3 天 | R2 |
| S1 server 订阅 | last_progress_at、overview 聚合、coverage 矩阵、batch、fill、reset、params 新键、autoFill/fillBudget 调度、查重 409、集单元与 air_date | L 3 天 | R2(ruleGroupIds) |
| S2 web 总览 | 类型 Tab、分组/排序/筛选、卡片、批量 | M 1.5 天 | S1 |
| S3 web 详情 | 覆盖矩阵 + 格侧栏、版本目标区(排序、补全、自动补全)、编辑表单(两 Tab、默认模式) | L 2.5 天 | S1 |
| S4 日历(P2) | 接口 + 月视图 | M 1 天 | S1 |
并行:R1 与 S1 的非规则部分可同时开工;R3 与 S2/S3 可并行。合计约 15 人日(不含 P2 约 14)。
验收:subscription.test.ts 加 覆盖矩阵、自动补全 off 不建计划、补全预算延后、多组 pri 取最后组;e2e 加"双版本订阅一版齐一版缺 → 矩阵正确 → 手动补全"。