feat(identify-words): 识别词——全局识别词(MP 语法)+ 作品识别词(季集换算),规则页「识别词」页签

- 新表 identify_words(0094):全局一块 + 每部作品一块原文,带版本与审计;episode_mapping_rule 转成文本行后删表
- 全局识别词移植 MP WordsMatcher(屏蔽 / 替换 / 集数偏移 / 替换+偏移 / {[tmdbid=…]}),所有按标题 / 文件名识别的地方解析前套用,命中的行记在解析结果上;保存后后台按改动行重新识别
- 作品识别词:S02 >> S01 EP+24 / S01E13 >> S00E01 / S01 @组 >> 忽略,解析器 + 打印器 + 编译;发布组繁简视为同一个
- 去掉规则自动停用:算出来对不上季表只待确认,要停用就注释掉那一行
- 集数待确认的采用 / 忽略与智能体 apply_rule 改为往作品识别词追加一行(带来历注释)
- 编辑器可视化 / 文本两种模式改同一份原文,保存前试算;作品页「集数映射」用同一个编辑器

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-08 17:03:00 +08:00
1 parent 12f8fc59c3
commit 63e94b308e
90 files changed
+6366 -2131

No files matched your search

+1 -1
View File
@@ -439,6 +439,6 @@ W10 已补(2026-09-28):
| 下载面板 | 行点开 `lg` 抽屉,`?open=` 是下载任务或计划 id;不在当前分段里的也能打开 |
| 日志 | 行点开抽屉(`?log=` / `?run=`),不再有「打开任务页」 |
| 文件管理 | 文件详情原本就是 `?sel=`;回收站并进右侧视图 |
| 规则 | 规则组编辑、「自动收新作品」的策略版本查看走 `?open=` |
| 规则 | 规则组编辑、「自动收新作品」的策略版本查看走 `?open=`;「识别词」页签里作品识别词的抽屉也走 `?open=<作品 id>`(同一参数,各页签认自己的 id),全局识别词就在页签里,不再分层 |
| 媒体库 | 「未识别」「外部身份」的行抽屉走 `?open=`;作品仍是独立页 |
| 设置 | 左栏只列分区;各分区里实例列表(下载器、媒体服务器、媒体库目录等)的编辑抽屉走 `?open=` |
+3
View File
@@ -40,6 +40,9 @@
| 未确认身份的文件/资源 | 未识别;列表里的一项叫「未识别资源」;数据保留里已识别出作品的那部分叫「未识别中已识别的条目」(与设置页一致) | 待识别、待识别池 |
| 识别给出的可能作品 | 候选作品;ambiguous 写「匹配到多部作品」 | 候选(单用)、有歧义、多个候选 |
| identity_ambiguous / 整理时认不出作品的事项 | 没识别出作品;主按钮"指定作品" | 身份歧义、识别有歧义、无法确定作品、待确认身份 |
| 识别前套到标题 / 文件名上的文本规则(identify_words) | 识别词;两级叫「全局识别词」(与 MoviePilot 自定义识别词同语法)与「作品识别词」(一部作品的季集换算);命中的行写「命中识别词:…」;编辑方式「可视化 / 文本」;保存前的预览叫「试算」 | 自定义识别词(界面,说明 MP 对照时可用)、映射规则(新文案)、规则文本 |
| 全局识别词的四种写法 | 屏蔽词、替换、集数偏移、替换 + 偏移;带 `{[tmdbid=…]}` 的替换叫「指定作品」;`前 <> 后` 两边叫「前定位词 / 后定位词」,`>>` 右边叫「偏移」 | 被替换词以外的叫法(替换源、匹配串) |
| 作品识别词的三种写法 | 季集换算(来源季、发布组、目标季、集号换算)、单集对照、特别篇 / 花絮 / 忽略;`*` 读作「任意季」;注释掉的规则行叫「已停用」 | 偏移表达式(界面上叫「集号换算」)、对照表、作用域(界面上叫「范围」) |
| 集号能解析但对不上作品季表 | 集数待确认(区别于文件名里"解析不出集号"/"认不出集号") | 集数异常 |
| 作品登记进本地库(不订阅) | 加入媒体库;状态"在媒体库 · 未下载"/"不在媒体库" | 建档、已建档、未收录 |
| 已完成下载任务的整理状态(下载面板) | 待整理 / 整理中 / 已整理 / 部分整理 / 整理失败;失败原因用原因码表的一句话 | 未整理(下载面板里用「待整理」)、导入失败、转移失败、已入库、入库失败 |
+96
View File
@@ -0,0 +1,96 @@
# 识别词
2026-10-08 定。识别词是给识别用的文本规则,分两级,都在规则页「识别词」页签里看:
- **全局识别词**:一块文本,写法与 MoviePilot 的「自定义识别词」一字不差,可以把 MP 的列表直接粘进来。识别前先套到标题与副标题上。
- **作品识别词**:每部作品一块文本,写这部作品的季集换算(取代原来散落在作品页的结构化映射规则)。作品页「集数映射」里改的是同一份。
规则照写的用:作品识别词算出来对不上季表的资源保持集数待确认,在预览里标「对不上」,不会自动停用那一行。要停用某一行就把它注释掉。
## 存储
表 `identify_words`(迁移 0094):`media_entity_id` 为空的一行是全局,其余每部作品一行;`text` 存原文(注释、空行原样保留),`version` 每次保存 +1,`updated_by_*` 记最后修改者,`reresolve` 是全局识别词保存后的后台重算进度。保存要带读到的版本(`expectedVersion`),别处先改过返回 409;每次保存写审计 `identify_words.updated`(增删的生效行,各最多 50 行)。
作品识别词在用的时候现场编译成规则(`compileEntityWords`,contracts),不另存结构化规则。0094 把旧表 `episode_mapping_rule` 逐作品转成文本行后删表:同季、同换算的多个发布组合成一行,单集对照一行一条,停用的变成注释行,智能体写的上方加一行注释;已有文本时只追加还没有的行。
## 全局识别词(MP 语法)
逐行处理,前一行的结果交给后一行。每行四种写法之一,`#` 开头是注释,空行跳过:
| 写法 | 含义 |
| --- | --- |
| `国语中字` | 屏蔽词:整行当正则,从标题里删掉 |
| `A => B` | 替换:`A` 是正则,`B` 可以用 `\1`、`\g<name>` 引用分组,留空就是删掉 |
| `前 <> 后 >> EP±N` | 集数偏移:前后定位词(正则)之间的数字按表达式换算;前导零保留宽度,中文数字仍写中文 |
| `A => B && 前 <> 后 >> 偏移` | 替换 + 偏移:替换成功才偏移 |
- 标题里偏移不了时偏移副标题:副标题里能直接定位就偏移副标题;定位词出现在「标题 + 副标题」里时只改副标题中会被识别成集数的数字(`第8集`、`Episode 8`、`E08`、`1-12 完结`),不动季号和年份。
- 替换词里带 `{[tmdbid=…;type=tv|movie;s=季;e=集]}` 时,解析器把它当显式身份标签:识别直接用这个 TMDB 作品,`s` / `e` 指定季集,`g=` 指定剧集组。
- 偏移表达式只允许数字、`EP`、括号和 `+ - * / // %`,结果取整;`2EP`、`EP(1)` 这类省略运算符的写法报错;`EP` 区分大小写;不含 `EP` 的表达式算出常数(与 MP 一致)。
- 正则按 Python 写法:`(?P<n>…)`、`(?P=n)`、开头的 `(?i)(?s)(?m)`、`\A` / `\Z`、`\p{Han}` 等脚本名都转成 JS 写法再编译。
- 写错的行(正则编译不了、偏移表达式不合法)编辑器里标红,但可以保存,套用时跳过这一行(MP 也是套用时才失败)。「替换 + 偏移」的偏移写错时替换照做、这一行不算命中,与 MP 一致。
- 命中的行记在解析结果上(`parsed.words.applied`,另记套用前的原标题),集数映射预览、全局试算里显示「命中识别词」。
实现:语法与 Python 正则 / 替换模板 / 算术在 `packages/contracts/src/identify-words-syntax.ts`,套用引擎移植 MP `WordsMatcher` 在 `packages/domain/src/identify-words/apply.ts`,`parseRelease(title, { words })` 解析前先套用(`parseReleasePath` 对文件、上级、上上级目录各自套用)。MP 用例移植在 `packages/domain/test/identify-words.test.ts`。
### 在哪里生效
服务端进程内缓存全局识别词(`identify-words-cache.ts`,5 秒按版本核对一次,本进程保存后立即失效),这些地方解析前都套用:
- 资源识别:站点列表抓取落库、RSS / 列表 / 搜索来的资源识别(`resolveRelease`)、手动指定作品、整理元数据补全、集数待确认的同组判断
- 搜索结果的快速筛选、规则组 / 收录版本的试算
- 下载:季包选文件时对种子里的文件名
- 整理:整理计划里每个文件(文件名改了季集时把覆盖值交给命名)、接管下载器里已有的任务
- 文件管理:识别、推荐文件名
- 刮削:未登记文件的季集
- 集数映射:按作品重算、预览、智能体的上下文包
### 保存后重算
保存时比较生效行(注释、空行之外的行)的增减。有增减才排后台任务 `reresolveIdentifyWords`:每批 500 条扫全部资源,标题 / 副标题被某个增减的行单独命中的,入队 `resolveObservation`(强制重新识别,身份与集数都重来,人工锁定的跳过)。进度写回 `identify_words.reresolve`,规则页每 3 秒刷一次;更新的保存会取代还在跑的那一版。
## 作品识别词
一行一条,`#` 开头是注释,空行跳过。三种写法:
```
S<来源季> [@发布组 …] >> [S<目标季>] <集号表达式> 季集换算
S<季>E<集>[-E<集>] [@发布组 …] >> S<季>E<集> 单集对照
S<来源季> [@发布组 …] >> 特别篇 | 花絮 | 忽略 整季另作处理
```
- 来源季写 `*`(或 `S*`)表示任意季;单集对照不写季(`E05 >> S00E02`)同样是任意季。
- 目标季可省,省略 = 来源季;只写目标季(`S02 >> S01`)= 集号不变。
- 集号表达式与全局识别词同一套算术,`EP` 不分大小写,必须出现 `EP`;`EP` 单独写 = 集号不变。打印时规范成大写、去空格(`ep - 12` → `EP-12`)。
- 发布组前面加 `@`,可以写多个;带空格的写成 `@"Some Group"`。比较时不分大小写,繁简写法算同一个(`綠茶字幕組` = `绿茶字幕组`)。
- 单集对照可以多行,同一范围合成一张对照表,区间不能重叠;区间里每一集都要在表里,目标要在同一季且连续。
- 同一范围(季 + 发布组)只能有一行换算或整季处理;一行多个发布组拆成多条规则。冲突、写错都按行号报出来,有错不能保存。
药屋少女的呢喃(TMDB 只有一季,后面的季接着编号):
```
# 药屋少女的呢喃:TMDB 只有一季,第二、三季接着第一季编号
S02 >> S01 EP+24
S03 >> S01 EP+48
S03 @ADWeb @MWeb @CHDWEB @黒ネズミたち @Judas @绿茶字幕组 @綠茶字幕組 >> S01 EP
```
### 优先级
资源的(来源季, 发布组)找最具体的一条:季 + 发布组 > 只写季 > 只写发布组 > 任意。命中规则就只按它算,算出来对不上季表 → 待确认(不再回退到绝对集号 / 播出日期换算,也不停用这一行);没命中规则才走原号校验与确定性换算。解析结果记命中的行号与原文(`episode_resolution.ruleLine / ruleText`)。
### 保存与试算
编辑后自动试算(纯读,`POST /media-entities/{id}/identify-words/dry-run`):逐行错误、近期 100 条资源换算前后、仍对不上季表的条数。保存(`PUT /media-entities/{id}/identify-words`)在同一事务里按作品重算已绑定资源,返回重算 / 变化 / 待确认转已解决的条数。
集数待确认事项的「采用建议」「忽略并写忽略规则」、智能体的 `episode_mapping.apply_rule` 都是往作品识别词末尾追加一行,上方加一行注释写来历(日期、智能体会话、理由)。识别词里有同范围被注释掉的行(用户停用过)时智能体不再自动写。
## 编辑器
`components/identify-words/words-editor.tsx`,规则页「识别词」的全局区块、作品抽屉(`?open=<作品 id>`)和作品页「集数映射」用的是同一个组件:
- 「可视化 / 文本」两种模式改同一份原文,随时切换不丢内容,上次用的模式记在本机。
- 文本模式:等宽、行号、按语法着色,写错的行行号标红、整行浅红底;粘贴 MP 列表就是普通粘贴。
- 可视化模式:一行规则一张卡。先选类型(全局:屏蔽词 / 替换 / 集数偏移 / 替换 + 偏移 / 指定作品;作品:季集换算 / 单集对照 / 特别篇·花絮·忽略),字段按类型展开;集号换算有「不变 / +N / −N / 自定义表达式」;发布组是标签输入,提示这部作品资源里出现过的发布组。开关 = 注释掉与否,拖动(或焦点在手柄上按上下方向键)排序,备注写成上方的注释行,卡片下面拿近期资源(或自己给的标题)试这一行。
- 可视化表达不了的行(写错的)显示成只读的「原文」行,带错误和「在文本里改」,不会丢。
- 行模型 `wordsToRows` / `rowsToText`(contracts):紧贴规则上方、没有空行隔开的注释是它的备注;注释内容本身是一条规则的(全局里只认带运算符的)是停用的规则行;没改过的行原样拼回,往返逐字不变。