From e994a7bcfae7483bdf1e8a4d446f092039105bcf Mon Sep 17 00:00:00 2001 From: root Date: Mon, 28 Sep 2026 14:50:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(domain):=20rule=20groups=20=E2=80=94=20MP?= =?UTF-8?q?=20rule-string=20parser/serializer,=20builtin=20keywords,=20cha?= =?UTF-8?q?in=20evaluation=20wired=20into=20slot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rules/: RuleExpr tree, '>' layers with ! & | and parens, position-aware errors (unknown atom with suggestions, unsupported music atoms, parens, empty layer) - builtin keyword table aligned with MP BUILTIN_RULE_SET; field-based judgement where parsed fields exist, MP regex otherwise; custom atoms (MP 7 fields) - chain evaluation: hard excludes, ladder pri 100-k, groups AND-ed, pri from last laddered group, readable per-atom trace - slot: SlotSpec.rules, rule.hard_exclude / rule.no_layer_matched violations, pri basis switches to priority when a rule ladder is present - differential: 66 rule chains x 32 torrents vs MP python path (parse 64/64, pri 2090/2112, 22 registered mp_bug) Co-Authored-By: Claude Opus 5.5 --- packages/domain/src/index.ts | 1 + packages/domain/src/rules/atoms.ts | 470 + packages/domain/src/rules/evaluate.ts | 127 + packages/domain/src/rules/expr.ts | 278 + packages/domain/src/rules/index.ts | 32 + packages/domain/src/rules/types.ts | 100 + packages/domain/src/slot/evaluate.ts | 87 +- packages/domain/src/slot/index.ts | 2 + packages/domain/src/slot/types.ts | 19 +- .../domain/test/differential/rules-cases.json | 22361 ++++++++++++++++ .../domain/test/differential/rules.test.ts | 184 + packages/domain/test/rules-slot.test.ts | 94 + scripts/mp-sync/gen-rules-differential.py | 174 + 13 files changed, 23919 insertions(+), 10 deletions(-) create mode 100644 packages/domain/src/rules/atoms.ts create mode 100644 packages/domain/src/rules/evaluate.ts create mode 100644 packages/domain/src/rules/expr.ts create mode 100644 packages/domain/src/rules/index.ts create mode 100644 packages/domain/src/rules/types.ts create mode 100644 packages/domain/test/differential/rules-cases.json create mode 100644 packages/domain/test/differential/rules.test.ts create mode 100644 packages/domain/test/rules-slot.test.ts create mode 100644 scripts/mp-sync/gen-rules-differential.py diff --git a/packages/domain/src/index.ts b/packages/domain/src/index.ts index 349c7733..be8912fe 100644 --- a/packages/domain/src/index.ts +++ b/packages/domain/src/index.ts @@ -7,4 +7,5 @@ export * from "./identity/index.ts"; export * from "./organize/index.ts"; export * from "./path/index.ts"; export * from "./release-parser/index.ts"; +export * from "./rules/index.ts"; export * from "./slot/index.ts"; diff --git a/packages/domain/src/rules/atoms.ts b/packages/domain/src/rules/atoms.ts new file mode 100644 index 00000000..78c1b12f --- /dev/null +++ b/packages/domain/src/rules/atoms.ts @@ -0,0 +1,470 @@ +import type { ParsedRelease } from "../release-parser/types.ts"; +import type { CustomAtom, RuleContext } from "./types.ts"; + +// 内置关键字表:名称与 MP BUILTIN_RULE_SET(app/application/rules.py)一一对应。 +// mp 字段原样记录上游的判定口径(正则/条件),供关键字说明与差分核对; +// test 是我们的判定:能用解析字段的用字段(分辨率/来源/HDR/编码/字幕),其余沿用 MP 正则。 +// 音乐 13 条不支持(解析时报 unsupported_atom)。 + +export interface AtomInput { + r: ParsedRelease; + ctx: RuleContext; + /** 标题 + 副标题 + 标签(对照 MP `title description labels`) */ + text: string; +} + +export interface AtomHit { + hit: boolean; + /** 可读依据,如 "分辨率 1080p"、"原语言 zh 放行" */ + why: string; +} + +export interface BuiltinAtom { + id: string; + name: string; + /** 一句话语义 */ + description: string; + /** 我们的判定依据:field = 解析字段;regex = 沿用 MP 正则;site = 站点事实 */ + basis: "field" | "regex" | "site"; + supported: boolean; + /** MP 上游定义(只读,说明用) */ + mp: { + include?: string[]; + exclude?: string[]; + match?: string[]; + tmdb?: Record; + downloadvolumefactor?: number; + }; + test?: (i: AtomInput) => AtomHit; +} + +/** MP 正则是 Python 语法:去掉内联 (?i),统一按不区分大小写编译;非法返回 null */ +export function compileMpPattern(p: string): RegExp | null { + try { + return new RegExp(p.replace(/\(\?i\)/g, ""), "i"); + } catch { + return null; + } +} + +const reCache = new Map(); +function re(p: string): RegExp | null { + if (!reCache.has(p)) { + if (reCache.size > 500) reCache.clear(); + reCache.set(p, compileMpPattern(p)); + } + return reCache.get(p)!; +} + +const anyRe = (ps: readonly string[], text: string) => ps.some((p) => re(p)?.test(text) ?? false); + +function regexAtom(ps: string[], label: string) { + return ({ text }: AtomInput): AtomHit => + anyRe(ps, text) ? { hit: true, why: `含「${label}」` } : { hit: false, why: `不含「${label}」` }; +} + +const zhLang = (lang: string | undefined, accept: readonly string[]) => + !!lang && accept.includes(lang.toLowerCase().split("-")[0]!); + +const MP_CNSUB = + "[中国國繁简](/|\\s|\\\\|\\|)?[繁简英粤]|[英简繁](/|\\s|\\\\|\\|)?[中繁简]" + + "|繁體|简体|[中国國][字配]|国语|國語|中文|中字|简日|繁日|简繁|繁体" + + "|([\\s,.-\\[])(chs|cht)(|[\\s,.-\\]])" + + "|(? + (i: AtomInput): AtomHit => + i.r.resolution && rs.includes(i.r.resolution) + ? { hit: true, why: `分辨率 ${i.r.resolution}` } + : { hit: false, why: `分辨率 ${i.r.resolution ?? "未知"}` }; +const src = + (...ss: string[]) => + (i: AtomInput): AtomHit => + i.r.source && ss.includes(i.r.source) + ? { hit: true, why: `来源 ${i.r.source}` } + : { hit: false, why: `来源 ${i.r.source ?? "未知"}` }; +const codec = + (c: string) => + (i: AtomInput): AtomHit => + i.r.videoCodec === c ? { hit: true, why: `编码 ${c}` } : { hit: false, why: `编码 ${i.r.videoCodec ?? "未知"}` }; +const hdrList = (i: AtomInput) => (i.r.hdr.length ? i.r.hdr.join("/") : "无"); + +const MUSIC: [string, string, string][] = [ + [ + "HIRES", + "Hi-Res 无损", + "(?i)\\b(?:Hi[ ._-]?Res(?:olution)?|DSD(?:64|128|256|512)?)\\b|高解析|(?:24|32)\\s*(?:-?bit|位)", + ], + ["LOSSLESS", "无损音频", "(?i)\\b(?:Lossless|FLAC|ALAC|APE|WAV|WAVE|AIFF?|PCM|DSD|DSF|DFF)\\b|无损"], + ["FLAC", "FLAC", "(?i)(? { + if (zhLang(i.ctx.originalLanguage, ["zh", "cn"])) + return { hit: true, why: `原语言 ${i.ctx.originalLanguage} 放行` }; + const zh = (i.r.subtitles ?? []).filter((s) => s.startsWith("zh")); + if (zh.length) return { hit: true, why: `字幕 ${zh.join("/")}` }; + return { hit: false, why: i.r.subtitles === undefined ? "字幕未知" : `字幕 ${i.r.subtitles.join("/") || "无"}` }; + }, + }, + { + id: "GZ", + name: "官种", + description: "站点标签含 官方/官种/官组(只看标签)", + basis: "site", + supported: true, + mp: { include: ["官方", "官种", "官组"], match: ["labels"] }, + test: (i) => { + const labels = (i.ctx.labels ?? []).join(" "); + return anyRe(["官方", "官种", "官组"], labels) + ? { hit: true, why: "官方标签" } + : { hit: false, why: labels ? `标签 ${labels}` : "无站点标签" }; + }, + }, + { + id: "SPECSUB", + name: "特效字幕", + description: "标题/副标题/标签含「特效」", + basis: "regex", + supported: true, + mp: { include: ["特效"] }, + test: regexAtom(["特效"], "特效"), + }, + { + id: "BLURAY", + name: "蓝光", + description: "蓝光来源(原盘、蓝光 Remux、蓝光压制)", + basis: "field", + supported: true, + mp: { include: ["Blu-?Ray"] }, + test: (i) => { + const s = i.r.source; + if (s === "BluRay" || s === "BluRay-Disc") return { hit: true, why: `来源 ${s}` }; + if (s === "REMUX" && /blu-?ray/i.test(i.r.raw)) return { hit: true, why: "蓝光 REMUX" }; + return { hit: false, why: `来源 ${s ?? "未知"}` }; + }, + }, + { + id: "UHD", + name: "UHD", + description: "标题含 UHD / UltraHD", + basis: "field", + supported: true, + mp: { include: ["UHD|UltraHD"] }, + test: (i) => (i.r.uhd || /UltraHD/i.test(i.text) ? { hit: true, why: "UHD" } : { hit: false, why: "无 UHD 标记" }), + }, + { + id: "H265", + name: "H265", + description: "视频编码 H.265/HEVC", + basis: "field", + supported: true, + mp: { include: ["[Hx].?265|HEVC"] }, + test: codec("H.265"), + }, + { + id: "H264", + name: "H264", + description: "视频编码 H.264/AVC", + basis: "field", + supported: true, + mp: { include: ["[Hx].?264|AVC"] }, + test: codec("H.264"), + }, + { + id: "DOLBY", + name: "杜比视界", + description: "HDR 含杜比视界(DV/DoVi)", + basis: "field", + supported: true, + mp: { include: ["Dolby[\\s.]+Vision|DOVI|[\\s.]+DV[\\s.]+|杜比视界"] }, + test: (i) => + i.r.hdr.includes("DV") || /杜比视界/.test(i.text) + ? { hit: true, why: "杜比视界" } + : { hit: false, why: `HDR ${hdrList(i)}` }, + }, + { + id: "ATMOS", + name: "杜比全景声", + description: "音轨含 Atmos(杜比全景声)", + basis: "field", + supported: true, + mp: { include: ["Dolby[\\s.+]+Atmos|Atmos|杜比全景[声聲]"] }, + test: (i) => + i.r.audio.some((a) => /atmos/i.test(a)) || anyRe(["Atmos|杜比全景[声聲]"], i.text) + ? { hit: true, why: "Atmos" } + : { hit: false, why: "无 Atmos" }, + }, + { + id: "HDR", + name: "HDR", + description: "HDR10 / HDR10+ / HDR Vivid(不含只有杜比视界的)", + basis: "field", + supported: true, + mp: { include: ["[\\s.]+HDR[\\s.]+|HDR10|HDR10\\+|HDRVivid"] }, + test: (i) => + i.r.hdr.some((h) => h === "HDR10" || h === "HDR10+" || h === "HDR Vivid") + ? { hit: true, why: `HDR ${hdrList(i)}` } + : { hit: false, why: `HDR ${hdrList(i)}` }, + }, + { + id: "SDR", + name: "SDR", + description: "标题明确标 SDR", + basis: "regex", + supported: true, + mp: { include: ["[\\s.]+SDR[\\s.]+"] }, + test: regexAtom(["[\\s.]+SDR[\\s.]+"], "SDR"), + }, + { + id: "REMUX", + name: "Remux", + description: "来源为 Remux", + basis: "field", + supported: true, + mp: { include: ["REMUX"] }, + test: src("REMUX"), + }, + { + id: "WEBDL", + name: "WEB-DL", + description: "来源为 WEB-DL 或 WEBRip", + basis: "field", + supported: true, + mp: { include: ["WEB-?DL|WEB-?RIP"] }, + test: src("WEB-DL", "WEBRip"), + }, + { + id: "FREE", + name: "免费", + description: "站点促销为免费(下载量不计)", + basis: "site", + supported: true, + mp: { downloadvolumefactor: 0 }, + test: (i) => + i.ctx.downloadVolumeFactor === 0 + ? { hit: true, why: "免费" } + : { + hit: false, + why: i.ctx.downloadVolumeFactor === undefined ? "促销未知" : `下载系数 ${i.ctx.downloadVolumeFactor}`, + }, + }, + { + id: "CNVOI", + name: "国语配音", + description: "含国语配音;作品原语言为中文时直接满足", + basis: "regex", + supported: true, + mp: { include: [MP_CNVOI], tmdb: { original_language: "zh" } }, + test: (i) => + zhLang(i.ctx.originalLanguage, ["zh"]) + ? { hit: true, why: `原语言 ${i.ctx.originalLanguage} 放行` } + : regexAtom([MP_CNVOI], "国语")(i), + }, + { + id: "HKVOI", + name: "粤语配音", + description: "含粤语配音", + basis: "regex", + supported: true, + mp: { include: ["粤语配音|粤语"] }, + test: regexAtom(["粤语配音|粤语"], "粤语"), + }, + { + id: "60FPS", + name: "60帧", + description: "帧率 ≥ 60", + basis: "field", + supported: true, + mp: { include: ["60fps|60帧"] }, + test: (i) => + (i.r.fps ?? 0) >= 60 || anyRe(["60fps|60帧"], i.text) + ? { hit: true, why: `帧率 ${i.r.fps ?? 60}` } + : { hit: false, why: `帧率 ${i.r.fps ?? "未知"}` }, + }, + { + id: "3D", + name: "3D", + description: "标题含 3D", + basis: "regex", + supported: true, + mp: { include: ["3D"] }, + test: regexAtom(["3D"], "3D"), + }, + ...MUSIC.map( + ([id, name, p]): BuiltinAtom => ({ + id, + name, + description: "音乐资源条件,暂不支持", + basis: "regex", + supported: false, + mp: { include: [p] }, + }), + ), +]; + +export const BUILTIN_ATOM_MAP: ReadonlyMap = new Map(BUILTIN_ATOMS.map((a) => [a.id, a])); + +/** 自定义条件 id:大写字母开头或数字+字母(对照 MP pyparsing 原子),规范化为大写 */ +export const ATOM_ID_RE = /^(?:[A-Z][A-Z0-9]*|[0-9]+[A-Z][A-Z0-9]*)$/; + +const MB = 1024 * 1024; + +function unitCount(i: AtomInput): number { + const { r, ctx } = i; + if (ctx.unitCount !== undefined && ctx.unitCount > 0) return ctx.unitCount; + if (r.episodes) return Math.max(1, r.episodes.end - r.episodes.start + 1); + if (r.totalEpisodes && r.totalEpisodes > 0) return r.totalEpisodes; + return 1; +} + +/** 自定义条件判定(对照 MP __match_rule:包含 → 排除 → 体积(每集)→ 做种 → 促销 → 发布时间) */ +export function testCustomAtom(a: CustomAtom, i: AtomInput): AtomHit { + let text = i.text; + if (a.matchFields?.length) { + const parts: string[] = []; + if (a.matchFields.includes("title")) parts.push(i.r.raw); + if (a.matchFields.includes("subtitle") && i.ctx.subtitle) parts.push(i.ctx.subtitle); + if (a.matchFields.includes("labels")) parts.push(...(i.ctx.labels ?? [])); + text = parts.join(" "); + } + if (a.include?.length && !anyRe(a.include, text)) return { hit: false, why: `不含 ${a.include.join(" / ")}` }; + const ex = (a.exclude ?? []).find((p) => re(p)?.test(text)); + if (ex) return { hit: false, why: `含排除词 ${ex}` }; + if (a.unitSizeMB && (a.unitSizeMB.min !== undefined || a.unitSizeMB.max !== undefined)) { + if (i.r.sizeBytes === undefined) return { hit: false, why: "体积未知" }; + const n = unitCount(i); + const per = i.r.sizeBytes / n; + const mb = Math.round(per / MB); + const tag = n > 1 ? `每集 ${mb}MB` : `${mb}MB`; + if (a.unitSizeMB.min !== undefined && per < a.unitSizeMB.min * MB) + return { hit: false, why: `${tag} 小于 ${a.unitSizeMB.min}MB` }; + if (a.unitSizeMB.max !== undefined && per > a.unitSizeMB.max * MB) + return { hit: false, why: `${tag} 大于 ${a.unitSizeMB.max}MB` }; + } + if (a.minSeeders !== undefined && a.minSeeders > 0) { + if (i.ctx.seeders === undefined) return { hit: false, why: "做种数未知" }; + if (i.ctx.seeders < a.minSeeders) return { hit: false, why: `做种 ${i.ctx.seeders} 小于 ${a.minSeeders}` }; + } + if (a.free && i.ctx.downloadVolumeFactor !== 0) + return { hit: false, why: i.ctx.downloadVolumeFactor === undefined ? "促销未知" : "非免费" }; + const pd = a.publishDelayMinutes; + if (pd && (pd.min !== undefined || pd.max !== undefined)) { + const m = i.ctx.publishedMinutesAgo; + if (m === undefined) return { hit: false, why: "发布时间未知" }; + if (pd.min !== undefined && m < pd.min) return { hit: false, why: `发布 ${Math.round(m)} 分钟,不足 ${pd.min}` }; + if (pd.max !== undefined && m > pd.max) return { hit: false, why: `发布 ${Math.round(m)} 分钟,超过 ${pd.max}` }; + } + return { hit: true, why: "满足" }; +} + +/** MP 自定义规则(导入格式)→ CustomAtom;size_range 形如 "a-b" / ">a" / " (v === undefined || v === "" ? [] : Array.isArray(v) ? v : [v]); + const out: CustomAtom = { id: id.toUpperCase() }; + if (mp.name) out.name = mp.name; + const inc = list(mp.include); + const exc = list(mp.exclude); + if (inc.length) out.include = inc; + if (exc.length) out.exclude = exc; + const sr = mp.size_range?.trim(); + if (sr) { + if (sr.includes("-")) { + const [a, b] = sr.split("-").map((x) => Number(x.trim())); + out.unitSizeMB = { min: a!, max: b! }; + } else if (sr.startsWith(">")) out.unitSizeMB = { min: Number(sr.slice(1).trim()) }; + else if (sr.startsWith("<")) out.unitSizeMB = { max: Number(sr.slice(1).trim()) }; + } + if (mp.seeders !== undefined && mp.seeders !== "") out.minSeeders = Number(mp.seeders); + if (mp.publish_time) { + const ts = mp.publish_time.split("-").map((x) => Number(x.trim())); + out.publishDelayMinutes = ts.length === 1 ? { min: ts[0]! } : { min: ts[0]!, max: ts[1]! }; + } + if (mp.downloadvolumefactor === 0) out.free = true; + const fields = (mp.match ?? []).flatMap((m) => + m === "title" + ? ["title" as const] + : m === "description" + ? ["subtitle" as const] + : m === "labels" + ? ["labels" as const] + : [], + ); + if (fields.length) out.matchFields = fields; + return out; +} diff --git a/packages/domain/src/rules/evaluate.ts b/packages/domain/src/rules/evaluate.ts new file mode 100644 index 00000000..7fa5340c --- /dev/null +++ b/packages/domain/src/rules/evaluate.ts @@ -0,0 +1,127 @@ +import type { ParsedRelease } from "../release-parser/types.ts"; +import { type AtomHit, type AtomInput, BUILTIN_ATOM_MAP, testCustomAtom } from "./atoms.ts"; +import { serializeExpr } from "./expr.ts"; +import type { CustomAtom, RuleChainResult, RuleContext, RuleExpr, RuleGroupResult, RuleGroupSpec } from "./types.ts"; + +// 规则组求值(对照 MP FilterModule.__get_order / __match_group / __match_rule): +// - 组内:硬排除任一命中 → 拒绝;层按序匹配,第 k 层(0 起)命中 pri = 100 - k;一层不中且 ladderRequired → 拒绝。 +// - 多组串联取交集(任一组拒绝即拒绝);pri 取最后一个带层的组。 +// - 未知原子视为不匹配(MP 语义),trace 里写明。 + +export type AtomCatalog = ReadonlyMap; + +export function catalogOf(atoms: readonly CustomAtom[] = []): AtomCatalog { + return new Map(atoms.map((a) => [a.id.toUpperCase(), a])); +} + +function inputOf(r: ParsedRelease, ctx: RuleContext): AtomInput { + return { r, ctx, text: [r.raw ?? "", ctx.subtitle ?? "", ...(ctx.labels ?? [])].join(" ") }; +} + +/** 单个原子判定 */ +export function testAtom(id: string, input: AtomInput, catalog: AtomCatalog = new Map()): AtomHit { + const key = id.toUpperCase(); + const b = BUILTIN_ATOM_MAP.get(key); + if (b) { + if (!b.supported || !b.test) return { hit: false, why: "不支持" }; + return b.test(input); + } + const c = catalog.get(key); + if (c) return testCustomAtom(c, input); + return { hit: false, why: "条件不存在" }; +} + +/** 表达式求值(短路,同 MP);notes 收集已求值原子的依据(按字面:`!X✗` 表示取反后不满足),供 trace */ +function evalExpr(e: RuleExpr, input: AtomInput, catalog: AtomCatalog, notes: string[], neg = false): boolean { + switch (e.op) { + case "atom": { + const h = testAtom(e.id, input, catalog); + const lit = neg ? !h.hit : h.hit; + notes.push(`${neg ? "!" : ""}${e.id.toUpperCase()}${lit ? "✓" : "✗"} ${h.why}`); + return h.hit; + } + case "not": + return !evalExpr(e.arg, input, catalog, notes, !neg); + case "and": + return e.args.every((a) => evalExpr(a, input, catalog, notes, neg)); + case "or": + return e.args.some((a) => evalExpr(a, input, catalog, notes, neg)); + } +} + +/** 表达式是否命中(不要 trace 时用) */ +export function matchesExpr(e: RuleExpr, r: ParsedRelease, ctx: RuleContext = {}, catalog?: AtomCatalog): boolean { + return evalExpr(e, inputOf(r, ctx), catalog ?? new Map(), []); +} + +export function evaluateRuleGroup( + g: RuleGroupSpec, + r: ParsedRelease, + ctx: RuleContext = {}, + catalog: AtomCatalog = new Map(), +): RuleGroupResult { + const input = inputOf(r, ctx); + const label = `规则组「${g.name}」${g.revision !== undefined ? `@r${g.revision}` : ""}`; + const trace: string[] = []; + const excludedBy: string[] = []; + for (const x of g.hardExcludes ?? []) { + const notes: string[] = []; + if (evalExpr(x, input, catalog, notes)) { + const s = serializeExpr(x); + excludedBy.push(s); + trace.push(`${label} 硬排除 ${s} 命中(${notes.join(";")})`); + } + } + const base = { + ...(g.id !== undefined ? { groupId: g.id } : {}), + name: g.name, + ...(g.revision !== undefined ? { revision: g.revision } : {}), + }; + if (g.layers.length === 0) { + return { ...base, excludedBy, level: null, pri: null, passed: excludedBy.length === 0, trace }; + } + let level: number | null = null; + for (let k = 0; k < g.layers.length; k++) { + const layer = g.layers[k]!; + const notes: string[] = []; + const hit = evalExpr(layer.expr, input, catalog, notes); + const name = layer.name ? `第${k + 1}层「${layer.name}」` : `第${k + 1}层`; + const expr = serializeExpr(layer.expr); + if (hit) { + trace.push(`${label} ${name} ${expr}:命中,pri=${100 - k}(${notes.join(";")})`); + level = k; + break; + } + trace.push(`${label} ${name} ${expr}:未命中(${notes.join(";")})`); + } + const required = g.ladderRequired !== false; + if (level === null && required) trace.push(`${label} 不命中任何一层,过滤`); + return { + ...base, + excludedBy, + level, + pri: level === null ? 0 : 100 - level, + passed: excludedBy.length === 0 && (level !== null || !required), + trace, + }; +} + +/** 多组串联:任一组不通过即不通过;pri 取最后一个带层的组 */ +export function evaluateRuleChain( + groups: readonly RuleGroupSpec[], + r: ParsedRelease, + ctx: RuleContext = {}, + catalog: AtomCatalog = new Map(), +): RuleChainResult { + const results = groups.map((g) => evaluateRuleGroup(g, r, ctx, catalog)); + let deciding: number | null = null; + results.forEach((x, i) => { + if (x.pri !== null) deciding = i; + }); + const passed = results.every((x) => x.passed); + const pri = deciding === null ? null : results[deciding]!.pri; + const trace = results.flatMap((x) => x.trace); + if (deciding !== null && results.filter((x) => x.pri !== null).length > 1) + trace.push(`pri 取最后一个带层的规则组「${results[deciding]!.name}」:${pri}`); + return { passed, pri, decidingGroup: deciding, groups: results, trace }; +} diff --git a/packages/domain/src/rules/expr.ts b/packages/domain/src/rules/expr.ts new file mode 100644 index 00000000..f03a435d --- /dev/null +++ b/packages/domain/src/rules/expr.ts @@ -0,0 +1,278 @@ +import { BUILTIN_ATOM_MAP } from "./atoms.ts"; +import type { RuleExpr, RuleLayer, RuleParseError } from "./types.ts"; + +// MP 规则串 ↔ 结构化层。语法对齐 MP RuleParser(pyparsing infix_notation): +// 规则串 = 层 ('>' 层)*;层内优先级 ! > & > |,可加括号;原子 = 字母开头的字母数字串,或数字+字母(4K、60FPS)。 +// 偏离:MP 对未知原子视为不匹配、对多余 token 静默截断;我们在解析时报错并给位置(allowUnknown 时保留未知原子)。 + +export interface RuleParseOptions { + /** 自定义条件 id(大写);不在内置表也不在这里的原子报 unknown_atom */ + customAtoms?: Iterable; + /** 保留未知原子(求值时视为不匹配,对齐 MP);缺省 false 报错 */ + allowUnknown?: boolean; +} + +export interface RuleParseResult { + layers: RuleLayer[]; + errors: RuleParseError[]; +} + +type Tok = { t: "atom" | "(" | ")" | "!" | "&" | "|" | "bad"; v: string; pos: number }; + +function lex(s: string, base: number): Tok[] { + const out: Tok[] = []; + let i = 0; + while (i < s.length) { + const ch = s[i]!; + if (/\s/.test(ch)) { + i++; + continue; + } + if ("()!&|".includes(ch)) { + out.push({ t: ch as Tok["t"], v: ch, pos: base + i }); + i++; + continue; + } + const m = /^[A-Za-z0-9]+/.exec(s.slice(i)); + if (m) { + out.push({ t: "atom", v: m[0], pos: base + i }); + i += m[0].length; + continue; + } + out.push({ t: "bad", v: ch, pos: base + i }); + i++; + } + return out; +} + +function levenshtein(a: string, b: string): number { + const dp = Array.from({ length: b.length + 1 }, (_, j) => j); + for (let i = 1; i <= a.length; i++) { + let prev = dp[0]!; + dp[0] = i; + for (let j = 1; j <= b.length; j++) { + const tmp = dp[j]!; + dp[j] = Math.min(dp[j]! + 1, dp[j - 1]! + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1)); + prev = tmp; + } + } + return dp[b.length]!; +} + +/** 相近原子名(给 unknown_atom 提示) */ +export function suggestAtoms(id: string, custom: Iterable = []): string[] { + const all = [...[...BUILTIN_ATOM_MAP.values()].filter((a) => a.supported).map((a) => a.id), ...custom]; + return all + .map((x) => ({ x, d: levenshtein(id, x) })) + .filter((e) => e.d <= Math.max(1, Math.floor(id.length / 2)) || e.x.startsWith(id) || id.startsWith(e.x)) + .sort((a, b) => a.d - b.d) + .slice(0, 3) + .map((e) => e.x); +} + +const ATOM_SHAPE = /^(?:[A-Za-z][A-Za-z0-9]*|[0-9]+[A-Za-z][A-Za-z0-9]*)$/; + +class LayerParser { + i = 0; + constructor( + readonly toks: Tok[], + readonly layer: number, + readonly errors: RuleParseError[], + readonly custom: Set, + readonly opts: RuleParseOptions, + ) {} + peek() { + return this.toks[this.i]; + } + err(code: RuleParseError["code"], message: string, pos: number, extra: Partial = {}) { + this.errors.push({ code, message, position: pos, layer: this.layer, ...extra }); + } + or(): RuleExpr | null { + const first = this.and(); + if (!first) return null; + const args = [first]; + while (this.peek()?.t === "|") { + this.i++; + const n = this.and(); + if (!n) return null; + args.push(n); + } + return args.length === 1 ? first : flatten({ op: "or", args }); + } + and(): RuleExpr | null { + const first = this.unary(); + if (!first) return null; + const args = [first]; + while (this.peek()?.t === "&") { + this.i++; + const n = this.unary(); + if (!n) return null; + args.push(n); + } + return args.length === 1 ? first : flatten({ op: "and", args }); + } + unary(): RuleExpr | null { + const t = this.peek(); + if (t?.t === "!") { + this.i++; + const a = this.unary(); + return a ? { op: "not", arg: a } : null; + } + return this.primary(); + } + primary(): RuleExpr | null { + const t = this.peek(); + if (!t) { + const last = this.toks[this.toks.length - 1]; + this.err("unexpected_token", "表达式不完整,缺少条件", last ? last.pos + last.v.length : 0); + return null; + } + if (t.t === "(") { + this.i++; + const e = this.or(); + if (!e) return null; + if (this.peek()?.t !== ")") { + this.err("unbalanced_paren", "括号不配对:缺少 )", t.pos); + return null; + } + this.i++; + return e; + } + if (t.t === "atom") { + this.i++; + if (!ATOM_SHAPE.test(t.v)) { + this.err("unexpected_token", `「${t.v}」不是合法条件名(字母开头,或数字加字母如 4K)`, t.pos); + return null; + } + const id = t.v.toUpperCase(); + const b = BUILTIN_ATOM_MAP.get(id); + if (b && !b.supported) + this.err("unsupported_atom", `${id}(${b.name})不支持:音乐资源条件没有迁移`, t.pos, { atom: id }); + else if (!b && !this.custom.has(id) && !this.opts.allowUnknown) { + const suggestions = suggestAtoms(id, this.custom); + this.err( + "unknown_atom", + `未知条件 ${id}${suggestions.length ? `,是不是 ${suggestions.join(" / ")}` : ""}`, + t.pos, + { atom: id, suggestions }, + ); + } + return { op: "atom", id }; + } + if (t.t === ")") this.err("unbalanced_paren", "括号不配对:多余的 )", t.pos); + else if (t.t === "bad") this.err("invalid_char", `不认识的字符「${t.v}」`, t.pos); + else this.err("unexpected_token", `「${t.v}」前缺少条件`, t.pos); + return null; + } +} + +function flatten(e: RuleExpr): RuleExpr { + if (e.op !== "and" && e.op !== "or") return e; + const args: RuleExpr[] = []; + for (const a of e.args) { + const f = flatten(a); + if (f.op === e.op) args.push(...f.args); + else args.push(f); + } + return { op: e.op, args }; +} + +/** 规范化:同类 AND/OR 展平、原子大写(结构等价比较与指纹用) */ +export function normalizeExpr(e: RuleExpr): RuleExpr { + switch (e.op) { + case "atom": + return { op: "atom", id: e.id.toUpperCase() }; + case "not": + return { op: "not", arg: normalizeExpr(e.arg) }; + default: + return flatten({ op: e.op, args: e.args.map(normalizeExpr) }); + } +} + +/** 解析一层表达式(不含 `>`);offset 用于报错位置 */ +export function parseExpr( + s: string, + opts: RuleParseOptions = {}, + layer = 0, + offset = 0, +): { expr: RuleExpr | null; errors: RuleParseError[] } { + const errors: RuleParseError[] = []; + const toks = lex(s, offset); + if (toks.length === 0) { + errors.push({ code: "empty_layer", message: `第 ${layer + 1} 层是空的`, position: offset, layer }); + return { expr: null, errors }; + } + const p = new LayerParser( + toks, + layer, + errors, + new Set([...(opts.customAtoms ?? [])].map((x) => x.toUpperCase())), + opts, + ); + const expr = p.or(); + if (expr && p.i < toks.length && errors.length === 0) { + const t = toks[p.i]!; + if (t.t === ")") p.err("unbalanced_paren", "括号不配对:多余的 )", t.pos); + else if (t.t === "bad") p.err("invalid_char", `不认识的字符「${t.v}」`, t.pos); + else p.err("unexpected_token", `「${t.v}」前缺少 & 或 |`, t.pos); + } + return { expr: errors.length ? null : expr, errors }; +} + +/** 规则串 → 结构化层(`>` 分层,第 1 层 pri=100) */ +export function parseRuleString(s: string, opts: RuleParseOptions = {}): RuleParseResult { + const errors: RuleParseError[] = []; + const layers: RuleLayer[] = []; + if (!s.trim()) return { layers, errors: [{ code: "empty", message: "规则串是空的", position: 0, layer: 0 }] }; + let offset = 0; + s.split(">").forEach((part, k) => { + const r = parseExpr(part, opts, k, offset); + errors.push(...r.errors); + if (r.expr) layers.push({ expr: r.expr }); + offset += part.length + 1; + }); + return { layers: errors.length ? [] : layers, errors }; +} + +const PREC = { or: 1, and: 2, not: 3, atom: 4 } as const; + +/** 表达式 → 规则串(原子大写,只加必要的括号) */ +export function serializeExpr(e: RuleExpr, parent = 0): string { + switch (e.op) { + case "atom": + return e.id.toUpperCase(); + case "not": + return `!${serializeExpr(e.arg, PREC.not)}`; + case "and": { + const s = e.args.map((a) => serializeExpr(a, PREC.and)).join(" & "); + return parent > PREC.and ? `(${s})` : s; + } + case "or": { + const s = e.args.map((a) => serializeExpr(a, PREC.or)).join(" | "); + return parent > PREC.or ? `(${s})` : s; + } + } +} + +/** 结构化层 → 规则串 */ +export function serializeLayers(layers: readonly RuleLayer[]): string { + return layers.map((l) => serializeExpr(l.expr)).join(" > "); +} + +/** 表达式引用到的原子 id(去重) */ +export function atomsOf(e: RuleExpr, out = new Set()): Set { + if (e.op === "atom") out.add(e.id.toUpperCase()); + else if (e.op === "not") atomsOf(e.arg, out); + else for (const a of e.args) atomsOf(a, out); + return out; +} + +/** MP pyparsing as_list() 形状 → RuleExpr(差分测试用):"A" | ["not", X] | [X, "and", Y, "and", Z] */ +export function fromMpParseList(v: unknown): RuleExpr { + if (typeof v === "string") return { op: "atom", id: v.toUpperCase() }; + const a = v as unknown[]; + if (a.length === 1) return fromMpParseList(a[0]); + if (a[0] === "not") return { op: "not", arg: fromMpParseList(a.length === 2 ? a[1] : a.slice(1)) }; + const op = a[1] as "and" | "or"; + return flatten({ op, args: a.filter((_, i) => i % 2 === 0).map(fromMpParseList) }); +} diff --git a/packages/domain/src/rules/index.ts b/packages/domain/src/rules/index.ts new file mode 100644 index 00000000..807b15b4 --- /dev/null +++ b/packages/domain/src/rules/index.ts @@ -0,0 +1,32 @@ +export { + ATOM_ID_RE, + type AtomHit, + type AtomInput, + BUILTIN_ATOM_MAP, + BUILTIN_ATOMS, + type BuiltinAtom, + compileMpPattern, + customAtomFromMp, + testCustomAtom, +} from "./atoms.ts"; +export { + type AtomCatalog, + catalogOf, + evaluateRuleChain, + evaluateRuleGroup, + matchesExpr, + testAtom, +} from "./evaluate.ts"; +export { + atomsOf, + fromMpParseList, + normalizeExpr, + parseExpr, + parseRuleString, + type RuleParseOptions, + type RuleParseResult, + serializeExpr, + serializeLayers, + suggestAtoms, +} from "./expr.ts"; +export * from "./types.ts"; diff --git a/packages/domain/src/rules/types.ts b/packages/domain/src/rules/types.ts new file mode 100644 index 00000000..7ad04ccb --- /dev/null +++ b/packages/domain/src/rules/types.ts @@ -0,0 +1,100 @@ +import type { EvaluationContext } from "../slot/types.ts"; + +// 规则组(对照 MP 规则组 `name + rule_string`):结构化层 + 硬排除。规则串只是它的一种文本表示。 + +/** 条件表达式:内置关键字 / 自定义条件的 AND / OR / NOT 树 */ +export type RuleExpr = + | { op: "atom"; id: string } + | { op: "not"; arg: RuleExpr } + | { op: "and"; args: RuleExpr[] } + | { op: "or"; args: RuleExpr[] }; + +/** 优先级阶梯的一层;第 k 层(0 起)命中 → pri = 100 - k */ +export interface RuleLayer { + name?: string; + expr: RuleExpr; +} + +export type RuleGroupKind = "acquisition" | "upgrade" | "search"; + +/** 参与求值的规则组(server 从 rule_group 行编译) */ +export interface RuleGroupSpec { + id?: string; + name: string; + revision?: number; + layers: RuleLayer[]; + /** 任一表达式命中即拒绝 */ + hardExcludes?: RuleExpr[]; + /** 一层都不命中即拒绝(MP 语义);缺省 true。软偏好组为 false:不中只是 pri 低 */ + ladderRequired?: boolean; +} + +/** 自定义条件(对照 MP 自定义规则 7 字段;同 id 不允许覆盖内置) */ +export interface CustomAtom { + id: string; + name?: string; + /** 正则(不区分大小写),至少命中一条 */ + include?: string[]; + /** 正则,一条都不能命中 */ + exclude?: string[]; + /** 匹配范围,缺省 标题+副标题+标签 */ + matchFields?: ("title" | "subtitle" | "labels")[]; + /** 体积区间(MB,1 MB = 1024²),剧集按每集折算 */ + unitSizeMB?: { min?: number; max?: number }; + minSeeders?: number; + /** 发布后经过的分钟数区间(只给 min = 至少发布这么久) */ + publishDelayMinutes?: { min?: number; max?: number }; + /** 只要免费 */ + free?: boolean; +} + +/** 规则求值上下文:站点事实 + 作品事实(tmdb 放行条件) */ +export interface RuleContext extends EvaluationContext { + /** 作品原语言(TMDB original_language),CNSUB/CNVOI 据此放行 */ + originalLanguage?: string; + /** 发布后经过的分钟数 */ + publishedMinutesAgo?: number; +} + +export interface RuleParseError { + code: + | "empty" + | "empty_layer" + | "unknown_atom" + | "unsupported_atom" + | "unbalanced_paren" + | "unexpected_token" + | "invalid_char"; + message: string; + /** 在整个规则串里的字符偏移(0 起) */ + position: number; + /** 第几层(0 起) */ + layer: number; + atom?: string; + suggestions?: string[]; +} + +export interface RuleGroupResult { + groupId?: string; + name: string; + revision?: number; + /** 命中的硬排除表达式(规则串形式) */ + excludedBy: string[]; + /** 命中的层(0 起);null = 未命中或没有层 */ + level: number | null; + /** 本组 pri;没有层时为 null(不参与"pri 取最后一组") */ + pri: number | null; + /** 本组放行(未被硬排除,且有层时命中某层或 ladderRequired=false) */ + passed: boolean; + trace: string[]; +} + +export interface RuleChainResult { + passed: boolean; + /** 最后一个带层的组的 pri;整条链都没有层时为 null */ + pri: number | null; + /** 决定 pri 的组(下标) */ + decidingGroup: number | null; + groups: RuleGroupResult[]; + trace: string[]; +} diff --git a/packages/domain/src/slot/evaluate.ts b/packages/domain/src/slot/evaluate.ts index 2451c96f..1a7fe432 100644 --- a/packages/domain/src/slot/evaluate.ts +++ b/packages/domain/src/slot/evaluate.ts @@ -1,4 +1,6 @@ import type { ParsedRelease, ReleaseSource, Resolution } from "../release-parser/types.ts"; +import { catalogOf, evaluateRuleChain } from "../rules/evaluate.ts"; +import type { RuleChainResult } from "../rules/types.ts"; import type { EvaluationContext, HardField, @@ -288,6 +290,10 @@ export function describeViolation(v: Violation): string { return `非免费(下载系数 ${a})`; case "tier.no_tier_matched": return "不命中优先级阶梯的任何一层"; + case "rule.hard_exclude": + return `命中规则组「${e}」的硬排除 ${fmtList(a)}`; + case "rule.no_layer_matched": + return `不命中规则组「${e}」的任何一层`; case "upgrade.upgrade_disabled": return "已有版本且未开启升级"; case "upgrade.at_ceiling": @@ -366,8 +372,47 @@ export function priorityLadder(spec: SlotSpec, r: ParsedRelease, ctx: Evaluation /** 当前资产的阶梯 pri:入库时记下的优先;否则按交付特征重算(关键词条件对已入库资产视为不命中) */ export function currentPri(spec: SlotSpec, current: QualityFacts): number { if (typeof current.pri === "number") return current.pri; - const pseudo = { raw: "", hdr: [], isEncode: false, ...current } as unknown as ParsedRelease; - return priorityLadder(spec, pseudo).pri; + const pseudo = { raw: "", hdr: [], audio: [], isEncode: false, ...current } as unknown as ParsedRelease; + return effectiveLadder(spec, pseudo, {}, ruleChainOf(spec, pseudo, {})).pri; +} + +/** 版本目标引用的规则组链求值;没有规则组时为 null */ +export function ruleChainOf(spec: SlotSpec, r: ParsedRelease, ctx: EvaluationContext = {}): RuleChainResult | null { + if (!spec.rules?.groups.length) return null; + return evaluateRuleChain(spec.rules.groups, r, ctx, catalogOf(spec.rules.atoms)); +} + +/** + * 实际生效的阶梯:版本目标自身 soft.tiers 视为排在最前的匿名组,规则组链里有带层的组时 pri 取最后一个带层的组。 + */ +export function effectiveLadder( + spec: SlotSpec, + r: ParsedRelease, + ctx: EvaluationContext, + chain: RuleChainResult | null, +): LadderResult { + const own = priorityLadder(spec, r, ctx); + if (!chain) return own; + if (chain.pri === null || chain.decidingGroup === null) return { ...own, trace: [...own.trace, ...chain.trace] }; + const g = chain.groups[chain.decidingGroup]!; + return { + pri: chain.pri, + level: g.level, + levelName: g.level === null ? null : `${g.name}·第${g.level + 1}层`, + trace: [...own.trace, ...chain.trace], + }; +} + +function chainViolations(chain: RuleChainResult | null, spec: SlotSpec): Violation[] { + if (!chain) return []; + const out: Violation[] = []; + chain.groups.forEach((g, i) => { + if (g.excludedBy.length) out.push({ field: "rule", code: "hard_exclude", expected: g.name, actual: g.excludedBy }); + const required = spec.rules!.groups[i]!.ladderRequired !== false; + if (g.pri !== null && g.level === null && required) + out.push({ field: "rule", code: "no_layer_matched", expected: g.name }); + }); + return out; } function prefIndexScore(pref: readonly T[] | undefined, have: readonly T[]): number { @@ -420,21 +465,43 @@ export function evaluateCandidate( current?: QualityFacts | null, ctx: EvaluationContext = {}, ): SlotEvaluation { - const { violations, missing, accepted } = checkHardConditions(spec, r, ctx); - const ladder = priorityLadder(spec, r, ctx); + const hard = checkHardConditions(spec, r, ctx); + const { missing, accepted } = hard; + const chain = ruleChainOf(spec, r, ctx); + const withRules = chain ? { rules: chain } : {}; + const violations = [...hard.violations, ...chainViolations(chain, spec)]; + const ladder = effectiveLadder(spec, r, ctx, chain); if (violations.length > 0) - return { decision: "reject", violations, trace: [...violations.map(describeViolation), ...ladder.trace], ladder }; + return { + decision: "reject", + violations, + trace: [...violations.map(describeViolation), ...ladder.trace], + ladder, + ...withRules, + }; if (missing.length > 0) return { decision: "unknown_field", missing, trace: missing.map((f) => `字段未知,需要补充:${f}`) }; if (spec.soft?.tiers?.length && spec.soft.tiersRequired && ladder.level === null) { const v: Violation = { field: "tier", code: "no_tier_matched" }; - return { decision: "reject", violations: [v], trace: [describeViolation(v), ...ladder.trace], ladder }; + return { + decision: "reject", + violations: [v], + trace: [describeViolation(v), ...ladder.trace], + ladder, + ...withRules, + }; } const tier = qualityTier(r, spec); const rank = softRank(spec, r); const reasons = accepted.map((f) => `unknown_${f}_accepted_by_policy`); - const tiered = !!spec.soft?.tiers?.length; - const priReasons = tiered ? [`pri:${ladder.pri}`, ...(ladder.levelName ? [`tier:${ladder.levelName}`] : [])] : []; + const tiered = !!spec.soft?.tiers?.length || (chain?.pri ?? null) !== null; + const ruleReasons = (chain?.groups ?? []).flatMap((g) => + g.groupId ? [`rule_group:${g.groupId}${g.revision !== undefined ? `@${g.revision}` : ""}`] : [], + ); + const priReasons = [ + ...(tiered ? [`pri:${ladder.pri}`, ...(ladder.levelName ? [`tier:${ladder.levelName}`] : [])] : []), + ...ruleReasons, + ]; if (!current) return { decision: "accept", @@ -444,6 +511,7 @@ export function evaluateCandidate( pri: ladder.pri, ladder, trace: ["硬条件全部满足", ...ladder.trace], + ...withRules, }; const reject = (v: Violation): SlotEvaluation => ({ @@ -451,6 +519,7 @@ export function evaluateCandidate( violations: [v], trace: [describeViolation(v), ...ladder.trace], ladder, + ...withRules, }); const up = spec.upgrade; if (!up?.enabled) return reject({ field: "upgrade", code: "upgrade_disabled" }); @@ -473,6 +542,7 @@ export function evaluateCandidate( fromPri, ladder, trace: [`阶梯 pri ${fromPri} → ${ladder.pri}`, ...ladder.trace], + ...withRules, }; } if (!current.source) @@ -509,6 +579,7 @@ export function evaluateCandidate( pri: ladder.pri, ladder, trace: [bySource ? "来源等级提升" : "分辨率提升", ...ladder.trace], + ...withRules, }; } diff --git a/packages/domain/src/slot/index.ts b/packages/domain/src/slot/index.ts index aa086c69..901bbd13 100644 --- a/packages/domain/src/slot/index.ts +++ b/packages/domain/src/slot/index.ts @@ -18,6 +18,7 @@ export { currentPri, DEFAULT_SOURCE_TIERS, describeViolation, + effectiveLadder, evaluateCandidate, improvableUnits, priorityLadder, @@ -25,6 +26,7 @@ export { type RankedEvaluation, RESOLUTION_RANK, rankForSlot, + ruleChainOf, softRank, unitCountOf, validateKeywordPattern, diff --git a/packages/domain/src/slot/types.ts b/packages/domain/src/slot/types.ts index bbc9c333..c37c2e05 100644 --- a/packages/domain/src/slot/types.ts +++ b/packages/domain/src/slot/types.ts @@ -7,6 +7,7 @@ import type { Resolution, VideoCodec, } from "../release-parser/types.ts"; +import type { CustomAtom, RuleChainResult, RuleGroupSpec } from "../rules/types.ts"; /** 可判定的硬条件字段 */ export type HardField = @@ -115,10 +116,18 @@ export interface SlotUpgradePolicy { export type SlotCompletionSpec = { kind: "movie"; cuts?: (Cut | "default")[] } | { kind: "units" }; +/** 版本目标引用的规则组链(已按 scope 过滤、按引用顺序排列)与自定义条件 */ +export interface SlotRuleChain { + groups: RuleGroupSpec[]; + atoms?: CustomAtom[]; +} + export interface SlotSpec { id?: string; name: string; hard: SlotHardConditions; + /** 规则组:硬排除 → 拒绝;各组须命中某层(ladderRequired);pri 取最后一个带层的组(覆盖 soft.tiers 的 pri) */ + rules?: SlotRuleChain; unknownPolicy?: UnknownPolicy | Partial>; soft?: SlotSoftPreferences; upgrade?: SlotUpgradePolicy; @@ -151,6 +160,10 @@ export interface EvaluationContext { downloadVolumeFactor?: number; /** 候选覆盖的内容单元数(每集体积折算);未给时按解析出的集范围/总集数推断 */ unitCount?: number; + /** 作品原语言(TMDB original_language),规则组 CNSUB/CNVOI 据此放行 */ + originalLanguage?: string; + /** 发布后经过的分钟数(自定义条件 publishDelayMinutes) */ + publishedMinutesAgo?: number; } /** 阶梯判定结果:pri 0–100 与逐层说明 */ @@ -169,7 +182,7 @@ export interface QualityTier { } export interface Violation { - field: HardField | "upgrade" | "tier"; + field: HardField | "upgrade" | "tier" | "rule"; code: string; expected?: unknown; actual?: unknown; @@ -184,6 +197,7 @@ export type SlotEvaluation = pri?: number; ladder?: LadderResult; trace?: string[]; + rules?: RuleChainResult; } | { decision: "upgrade"; @@ -195,6 +209,7 @@ export type SlotEvaluation = fromPri?: number; ladder?: LadderResult; trace?: string[]; + rules?: RuleChainResult; } - | { decision: "reject"; violations: Violation[]; trace?: string[]; ladder?: LadderResult } + | { decision: "reject"; violations: Violation[]; trace?: string[]; ladder?: LadderResult; rules?: RuleChainResult } | { decision: "unknown_field"; missing: HardField[] | ["current_asset"]; trace?: string[] }; diff --git a/packages/domain/test/differential/rules-cases.json b/packages/domain/test/differential/rules-cases.json new file mode 100644 index 00000000..0616c4a9 --- /dev/null +++ b/packages/domain/test/differential/rules-cases.json @@ -0,0 +1,22361 @@ +{ + "generatedFrom": { + "repo": "MoviePilot", + "commit": "e6820a8", + "engine": "python-fallback (FilterModule, rust_accel disabled); parse = pyparsing RuleParser", + "generator": "scripts/mp-sync/gen-rules-differential.py", + "semantics": "groups 串联取交集,pri = 最后一组命中层的 pri_order(100 起逐层 -1);null = 被过滤" + }, + "customRules": { + "SIZEMOVIE": { + "size_range": "2000-30000" + }, + "SIZESMALL": { + "size_range": "<8000" + }, + "SIZEBIG": { + "size_range": ">20000" + }, + "SEED5": { + "seeders": "5" + } + }, + "torrents": [ + { + "id": "t01", + "title": "Dune.Part.Two.2024.2160p.UHD.BluRay.Remux.DV.HDR.HEVC.TrueHD.Atmos.7.1-FraMeSToR", + "description": "", + "size": 75161927680, + "seeders": 20 + }, + { + "id": "t02", + "title": "Dune.Part.Two.2024.2160p.BluRay.x265.10bit.DV.HDR-CHD", + "description": "沙丘2 中字 特效", + "size": 26843545600, + "seeders": 12 + }, + { + "id": "t03", + "title": "Dune Part Two 2024 2160p UHD Blu-ray HEVC DV TrueHD 7.1-BeyondHD", + "description": "", + "size": 96636764160, + "seeders": 4 + }, + { + "id": "t04", + "title": "Dune.Part.Two.2024.1080p.WEB-DL.H264.DDP5.1-HHWEB", + "description": "沙丘2 | 国语 中字", + "size": 8589934592, + "seeders": 50 + }, + { + "id": "t05", + "title": "Dune.Part.Two.2024.1080p.BluRay.x264.DTS-HD.MA.5.1-WiKi", + "description": "简繁英字幕", + "size": 16106127360, + "seeders": 9 + }, + { + "id": "t06", + "title": "Dune.Part.Two.2024.720p.WEBRip.x264-GRP", + "description": "", + "size": 2147483648, + "seeders": 1 + }, + { + "id": "t07", + "title": "Dune.Part.Two.2024.2160p.WEB-DL.DDP5.1.Atmos.DV.HDR.H.265-FLUX", + "description": "", + "size": 21474836480, + "seeders": 30 + }, + { + "id": "t08", + "title": "Movie.2022.2160p.HDR.HEVC-NOSRC", + "description": "", + "size": 19327352832, + "seeders": 2 + }, + { + "id": "t09", + "title": "The.Bear.S03.2160p.DSNP.WEB-DL.DDP5.1.HDR.H.265-NTb", + "description": "熊家餐馆 第三季 全10集 中字", + "size": 64424509440, + "seeders": 15 + }, + { + "id": "t10", + "title": "The.Bear.S03E01.1080p.WEB-DL.H264-NTb", + "description": "", + "size": 2684354560, + "seeders": 8 + }, + { + "id": "t11", + "title": "Movie.2023.1080p.BluRay.REMUX.AVC.DTS-HD.MA.5.1-EPSiLON", + "description": "", + "size": 32212254720, + "seeders": 6 + }, + { + "id": "t12", + "title": "Movie 2023 1080p Blu-ray AVC DTS-HD MA 5.1-AAA", + "description": "", + "size": 42949672960, + "seeders": 3 + }, + { + "id": "t13", + "title": "Movie.2023.COMPLETE.BLURAY-GRP", + "description": "", + "size": 48318382080, + "seeders": 2 + }, + { + "id": "t14", + "title": "Movie.2023.1080i.HDTV.H264-GRP", + "description": "", + "size": 10737418240, + "seeders": 1 + }, + { + "id": "t15", + "title": "[Nekomoe kissaten] Frieren - 01 [WebRip 1080p HEVC-10bit AAC ASSx2].mkv", + "description": "简繁日内封", + "size": 1073741824, + "seeders": 40 + }, + { + "id": "t16", + "title": "The.Wandering.Earth.II.2023.2160p.WEB-DL.H265.HDR.DDP5.1-HDSWEB", + "description": "流浪地球2 国语配音 中文字幕", + "size": 12884901888, + "seeders": 25 + }, + { + "id": "t17", + "title": "Oppenheimer.2023.IMAX.2160p.BluRay.x265.10bit.HDR.DTS-HD.MA.5.1-SWTYBLZ", + "description": "奥本海默 CHS/ENG", + "size": 32212254720, + "seeders": 7 + }, + { + "id": "t18", + "title": "Movie.2022.3840x2160.WEB-DL.HDR10.HEVC-GRP", + "description": "", + "size": 16106127360, + "seeders": 5 + }, + { + "id": "t19", + "title": "Movie.2022.1920x1080.WEB-DL.H264-GRP", + "description": "", + "size": 6442450944, + "seeders": 5 + }, + { + "id": "t20", + "title": "Movie.2022.4K.WEB-DL.H265-GRP", + "description": "", + "size": 15032385536, + "seeders": 5 + }, + { + "id": "t21", + "title": "Movie.2022.1080p.WEB-DL.x264 [GB]", + "description": "", + "size": 4294967296, + "seeders": 5 + }, + { + "id": "t22", + "title": "Movie.2022.1080p.WEB-DL.H264.AAC 39.23 GB", + "description": "", + "size": 4294967296, + "seeders": 5 + }, + { + "id": "t23", + "title": "Some.Show.S01.1080p.BluRay.x264-GRP", + "description": "全12集 简体中字", + "size": 38654705664, + "seeders": 3 + }, + { + "id": "t24", + "title": "Movie.2021.DVDRip.x264-GRP", + "description": "", + "size": 1503238553, + "seeders": 2 + }, + { + "id": "t25", + "title": "Movie 2021 1080p 3D BluRay Half-SBS x264-GRP", + "description": "", + "size": 8589934592, + "seeders": 2 + }, + { + "id": "t26", + "title": "Movie.2021.2160p.WEB-DL.60fps.HEVC-GRP", + "description": "60帧 国语", + "size": 10737418240, + "seeders": 4 + }, + { + "id": "t27", + "title": "Movie.2020.2160p.Remux.HEVC.DoVi-GRP", + "description": "", + "size": 53687091200, + "seeders": 3 + }, + { + "id": "t28", + "title": "Movie.2020.1080p.NF.WEB-DL.DDP5.1.H.264-CHDWEB", + "description": "官方 中字", + "labels": ["官方"], + "size": 5368709120, + "seeders": 10, + "downloadvolumefactor": 0 + }, + { + "id": "t29", + "title": "Infernal.Affairs.2002.1080p.BluRay.x264.DTS-HK-GRP", + "description": "无间道 粤语 中字", + "size": 12884901888, + "seeders": 6 + }, + { + "id": "t30", + "title": "Movie.2019.1080p.WEB-DL.SDR.H264-GRP", + "description": "", + "size": 5368709120, + "seeders": 5 + }, + { + "id": "t31", + "title": "Show.S01E05.2160p.WEB-DL.HDR10+.H265-GRP", + "description": "官种 特效中字", + "labels": ["官种"], + "size": 4294967296, + "seeders": 30, + "downloadvolumefactor": 0.5 + }, + { + "id": "t32", + "title": "Movie.2018.1080p.BluRay.Remux.AVC.Atmos-GRP", + "description": "国配 杜比全景声", + "size": 30064771072, + "seeders": 2 + } + ], + "rules": [ + { + "id": "R01", + "groups": ["4K"] + }, + { + "id": "R02", + "groups": ["1080P"] + }, + { + "id": "R03", + "groups": ["720P"] + }, + { + "id": "R04", + "groups": ["4K | 1080P"] + }, + { + "id": "R05", + "groups": ["4K > 1080P > 720P"] + }, + { + "id": "R06", + "groups": ["BLU"] + }, + { + "id": "R07", + "groups": ["!BLU"] + }, + { + "id": "R08", + "groups": ["REMUX"] + }, + { + "id": "R09", + "groups": ["!REMUX"] + }, + { + "id": "R10", + "groups": ["WEBDL"] + }, + { + "id": "R11", + "groups": ["!BLU & !REMUX"] + }, + { + "id": "R12", + "groups": ["4K & !REMUX > 1080P & WEBDL"] + }, + { + "id": "R13", + "groups": ["CNSUB"] + }, + { + "id": "R14", + "groups": ["CNSUB & 4K & !BLU"] + }, + { + "id": "R15", + "groups": ["CNSUB & 1080P > 1080P"] + }, + { + "id": "R16", + "groups": ["SIZEMOVIE"] + }, + { + "id": "R17", + "groups": ["SIZESMALL"] + }, + { + "id": "R18", + "groups": ["4K & SIZEMOVIE"] + }, + { + "id": "R19", + "groups": ["!BLU & !REMUX & !WEBDL"] + }, + { + "id": "R20", + "groups": ["BLURAY & !REMUX & !BLU"] + }, + { + "id": "R21", + "groups": ["4K & !BLU & !REMUX > 1080P & !BLU & !REMUX"] + }, + { + "id": "R22", + "groups": ["(4K | 1080P) & WEBDL"] + }, + { + "id": "R23", + "groups": ["CNSUB"], + "mediainfo": { + "original_language": "zh" + } + }, + { + "id": "R24", + "groups": ["4K & !DOLBY"] + }, + { + "id": "R25", + "groups": ["SPECSUB & CNSUB & !BLU"] + }, + { + "id": "R26", + "groups": ["SIZEBIG"] + }, + { + "id": "N01", + "groups": ["H265"] + }, + { + "id": "N02", + "groups": ["!DOLBY & !REMUX"] + }, + { + "id": "N03", + "groups": ["HDR"] + }, + { + "id": "N04", + "groups": ["SPECSUB"] + }, + { + "id": "N05", + "groups": ["CNVOI"] + }, + { + "id": "N06", + "groups": ["60FPS"] + }, + { + "id": "N07", + "groups": ["!3D"] + }, + { + "id": "N08", + "groups": ["ATMOS"] + }, + { + "id": "N09", + "groups": ["GZ"] + }, + { + "id": "N10", + "groups": ["FREE"] + }, + { + "id": "N11", + "groups": ["SEED5"] + }, + { + "id": "N12", + "groups": ["CNSUB | 4K"] + }, + { + "id": "N13", + "groups": ["!(4K & REMUX)"] + }, + { + "id": "N14", + "groups": ["UHD"] + }, + { + "id": "N15", + "groups": ["4K & H265 > 4K"] + }, + { + "id": "N16", + "groups": ["NOSUCHRULE | 4K"] + }, + { + "id": "X01", + "groups": ["SPECSUB & CNSUB > CNSUB & 4K > 1080P & !BLU"] + }, + { + "id": "X02", + "groups": ["HKVOI"] + }, + { + "id": "X03", + "groups": ["SDR"] + }, + { + "id": "X04", + "groups": ["HDR & !DOLBY"] + }, + { + "id": "X05", + "groups": ["DOLBY | HDR"] + }, + { + "id": "X06", + "groups": ["H264 > H265"] + }, + { + "id": "X07", + "groups": ["!(BLU | REMUX) & 4K"] + }, + { + "id": "X08", + "groups": ["BLURAY"] + }, + { + "id": "X09", + "groups": ["FREE | SEED5"] + }, + { + "id": "X10", + "groups": ["UHD & REMUX > 4K"] + }, + { + "id": "X11", + "groups": ["CNVOI | HKVOI > !CNVOI"] + }, + { + "id": "X12", + "groups": ["!!4K"] + }, + { + "id": "X13", + "groups": ["4K & (CNSUB | SPECSUB) & !3D"] + }, + { + "id": "X14", + "groups": ["ATMOS & (REMUX | BLU)"] + }, + { + "id": "X15", + "groups": ["GZ > FREE > 4K"] + }, + { + "id": "X16", + "groups": ["CNVOI"], + "mediainfo": { + "original_language": "zh" + } + }, + { + "id": "X17", + "groups": ["SIZESMALL > SIZEBIG"] + }, + { + "id": "X18", + "groups": ["1080P & CNSUB | 4K & !CNSUB"] + }, + { + "id": "X19", + "groups": ["60FPS | 3D > ATMOS"] + }, + { + "id": "M01", + "groups": ["4K | 1080P", "CNSUB > !CNSUB"] + }, + { + "id": "M02", + "groups": ["!BLU", "4K > 1080P > 720P"] + }, + { + "id": "M03", + "groups": ["4K > 1080P", "WEBDL"] + }, + { + "id": "M04", + "groups": ["CNSUB", "SIZEMOVIE", "H265 > H264"] + }, + { + "id": "M05", + "groups": ["REMUX > BLU > WEBDL", "!DOLBY"] + } + ], + "parse": { + "4K": ["4K"], + "1080P": ["1080P"], + "720P": ["720P"], + "4K | 1080P": [["4K", "or", "1080P"]], + "4K > 1080P > 720P": ["4K", "1080P", "720P"], + "BLU": ["BLU"], + "!BLU": [["not", "BLU"]], + "REMUX": ["REMUX"], + "!REMUX": [["not", "REMUX"]], + "WEBDL": ["WEBDL"], + "!BLU & !REMUX": [[["not", "BLU"], "and", ["not", "REMUX"]]], + "4K & !REMUX > 1080P & WEBDL": [["4K", "and", ["not", "REMUX"]], ["1080P", "and", "WEBDL"]], + "CNSUB": ["CNSUB"], + "CNSUB & 4K & !BLU": [["CNSUB", "and", "4K", "and", ["not", "BLU"]]], + "CNSUB & 1080P > 1080P": [["CNSUB", "and", "1080P"], "1080P"], + "SIZEMOVIE": ["SIZEMOVIE"], + "SIZESMALL": ["SIZESMALL"], + "4K & SIZEMOVIE": [["4K", "and", "SIZEMOVIE"]], + "!BLU & !REMUX & !WEBDL": [[["not", "BLU"], "and", ["not", "REMUX"], "and", ["not", "WEBDL"]]], + "BLURAY & !REMUX & !BLU": [["BLURAY", "and", ["not", "REMUX"], "and", ["not", "BLU"]]], + "4K & !BLU & !REMUX > 1080P & !BLU & !REMUX": [ + ["4K", "and", ["not", "BLU"], "and", ["not", "REMUX"]], + ["1080P", "and", ["not", "BLU"], "and", ["not", "REMUX"]] + ], + "(4K | 1080P) & WEBDL": [[["4K", "or", "1080P"], "and", "WEBDL"]], + "4K & !DOLBY": [["4K", "and", ["not", "DOLBY"]]], + "SPECSUB & CNSUB & !BLU": [["SPECSUB", "and", "CNSUB", "and", ["not", "BLU"]]], + "SIZEBIG": ["SIZEBIG"], + "H265": ["H265"], + "!DOLBY & !REMUX": [[["not", "DOLBY"], "and", ["not", "REMUX"]]], + "HDR": ["HDR"], + "SPECSUB": ["SPECSUB"], + "CNVOI": ["CNVOI"], + "60FPS": ["60FPS"], + "!3D": [["not", "3D"]], + "ATMOS": ["ATMOS"], + "GZ": ["GZ"], + "FREE": ["FREE"], + "SEED5": ["SEED5"], + "CNSUB | 4K": [["CNSUB", "or", "4K"]], + "!(4K & REMUX)": [["not", ["4K", "and", "REMUX"]]], + "UHD": ["UHD"], + "4K & H265 > 4K": [["4K", "and", "H265"], "4K"], + "NOSUCHRULE | 4K": [["NOSUCHRULE", "or", "4K"]], + "SPECSUB & CNSUB > CNSUB & 4K > 1080P & !BLU": [ + ["SPECSUB", "and", "CNSUB"], + ["CNSUB", "and", "4K"], + ["1080P", "and", ["not", "BLU"]] + ], + "HKVOI": ["HKVOI"], + "SDR": ["SDR"], + "HDR & !DOLBY": [["HDR", "and", ["not", "DOLBY"]]], + "DOLBY | HDR": [["DOLBY", "or", "HDR"]], + "H264 > H265": ["H264", "H265"], + "!(BLU | REMUX) & 4K": [[["not", ["BLU", "or", "REMUX"]], "and", "4K"]], + "BLURAY": ["BLURAY"], + "FREE | SEED5": [["FREE", "or", "SEED5"]], + "UHD & REMUX > 4K": [["UHD", "and", "REMUX"], "4K"], + "CNVOI | HKVOI > !CNVOI": [["CNVOI", "or", "HKVOI"], ["not", "CNVOI"]], + "!!4K": [["not", ["not", "4K"]]], + "4K & (CNSUB | SPECSUB) & !3D": [["4K", "and", ["CNSUB", "or", "SPECSUB"], "and", ["not", "3D"]]], + "ATMOS & (REMUX | BLU)": [["ATMOS", "and", ["REMUX", "or", "BLU"]]], + "GZ > FREE > 4K": ["GZ", "FREE", "4K"], + "SIZESMALL > SIZEBIG": ["SIZESMALL", "SIZEBIG"], + "1080P & CNSUB | 4K & !CNSUB": [[["1080P", "and", "CNSUB"], "or", ["4K", "and", ["not", "CNSUB"]]]], + "60FPS | 3D > ATMOS": [["60FPS", "or", "3D"], "ATMOS"], + "CNSUB > !CNSUB": ["CNSUB", ["not", "CNSUB"]], + "4K > 1080P": ["4K", "1080P"], + "H265 > H264": ["H265", "H264"], + "REMUX > BLU > WEBDL": ["REMUX", "BLU", "WEBDL"], + "!DOLBY": [["not", "DOLBY"]] + }, + "cases": [ + { + "caseId": "R01:t01", + "ruleId": "R01", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t02", + "ruleId": "R01", + "torrentId": "t02", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t03", + "ruleId": "R01", + "torrentId": "t03", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t04", + "ruleId": "R01", + "torrentId": "t04", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "沙丘2 | 国语 中字 不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t05", + "ruleId": "R01", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "简繁英字幕 不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t06", + "ruleId": "R01", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t07", + "ruleId": "R01", + "torrentId": "t07", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t08", + "ruleId": "R01", + "torrentId": "t08", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t09", + "ruleId": "R01", + "torrentId": "t09", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t10", + "ruleId": "R01", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t11", + "ruleId": "R01", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t12", + "ruleId": "R01", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t13", + "ruleId": "R01", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t14", + "ruleId": "R01", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t15", + "ruleId": "R01", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "简繁日内封 不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t16", + "ruleId": "R01", + "torrentId": "t16", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t17", + "ruleId": "R01", + "torrentId": "t17", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t18", + "ruleId": "R01", + "torrentId": "t18", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t19", + "ruleId": "R01", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t20", + "ruleId": "R01", + "torrentId": "t20", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t21", + "ruleId": "R01", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t22", + "ruleId": "R01", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t23", + "ruleId": "R01", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "全12集 简体中字 不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t24", + "ruleId": "R01", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t25", + "ruleId": "R01", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t26", + "ruleId": "R01", + "torrentId": "t26", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t27", + "ruleId": "R01", + "torrentId": "t27", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t28", + "ruleId": "R01", + "torrentId": "t28", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "官方 中字 不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t29", + "ruleId": "R01", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "无间道 粤语 中字 不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t30", + "ruleId": "R01", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R01:t31", + "ruleId": "R01", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R01:t32", + "ruleId": "R01", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "国配 杜比全景声 不匹配 R01#0 过滤规则"] + } + }, + { + "caseId": "R02:t01", + "ruleId": "R02", + "torrentId": "t01", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t02", + "ruleId": "R02", + "torrentId": "t02", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "沙丘2 中字 特效 不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t03", + "ruleId": "R02", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t04", + "ruleId": "R02", + "torrentId": "t04", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t05", + "ruleId": "R02", + "torrentId": "t05", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t06", + "ruleId": "R02", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t07", + "ruleId": "R02", + "torrentId": "t07", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t08", + "ruleId": "R02", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t09", + "ruleId": "R02", + "torrentId": "t09", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "熊家餐馆 第三季 全10集 中字 不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t10", + "ruleId": "R02", + "torrentId": "t10", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t11", + "ruleId": "R02", + "torrentId": "t11", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t12", + "ruleId": "R02", + "torrentId": "t12", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t13", + "ruleId": "R02", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t14", + "ruleId": "R02", + "torrentId": "t14", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t15", + "ruleId": "R02", + "torrentId": "t15", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t16", + "ruleId": "R02", + "torrentId": "t16", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "流浪地球2 国语配音 中文字幕 不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t17", + "ruleId": "R02", + "torrentId": "t17", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "奥本海默 CHS/ENG 不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t18", + "ruleId": "R02", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t19", + "ruleId": "R02", + "torrentId": "t19", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t20", + "ruleId": "R02", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t21", + "ruleId": "R02", + "torrentId": "t21", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t22", + "ruleId": "R02", + "torrentId": "t22", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t23", + "ruleId": "R02", + "torrentId": "t23", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t24", + "ruleId": "R02", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t25", + "ruleId": "R02", + "torrentId": "t25", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t26", + "ruleId": "R02", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "60帧 国语 不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t27", + "ruleId": "R02", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t28", + "ruleId": "R02", + "torrentId": "t28", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t29", + "ruleId": "R02", + "torrentId": "t29", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t30", + "ruleId": "R02", + "torrentId": "t30", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R02:t31", + "ruleId": "R02", + "torrentId": "t31", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['1080[pi]|x1080']", "官种 特效中字 不匹配 R02#0 过滤规则"] + } + }, + { + "caseId": "R02:t32", + "ruleId": "R02", + "torrentId": "t32", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R03:t01", + "ruleId": "R03", + "torrentId": "t01", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t02", + "ruleId": "R03", + "torrentId": "t02", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "沙丘2 中字 特效 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t03", + "ruleId": "R03", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t04", + "ruleId": "R03", + "torrentId": "t04", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "沙丘2 | 国语 中字 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t05", + "ruleId": "R03", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "简繁英字幕 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t06", + "ruleId": "R03", + "torrentId": "t06", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R03:t07", + "ruleId": "R03", + "torrentId": "t07", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t08", + "ruleId": "R03", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t09", + "ruleId": "R03", + "torrentId": "t09", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "熊家餐馆 第三季 全10集 中字 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t10", + "ruleId": "R03", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t11", + "ruleId": "R03", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t12", + "ruleId": "R03", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t13", + "ruleId": "R03", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t14", + "ruleId": "R03", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t15", + "ruleId": "R03", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "简繁日内封 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t16", + "ruleId": "R03", + "torrentId": "t16", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "流浪地球2 国语配音 中文字幕 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t17", + "ruleId": "R03", + "torrentId": "t17", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "奥本海默 CHS/ENG 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t18", + "ruleId": "R03", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t19", + "ruleId": "R03", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t20", + "ruleId": "R03", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t21", + "ruleId": "R03", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t22", + "ruleId": "R03", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t23", + "ruleId": "R03", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "全12集 简体中字 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t24", + "ruleId": "R03", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t25", + "ruleId": "R03", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t26", + "ruleId": "R03", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "60帧 国语 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t27", + "ruleId": "R03", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t28", + "ruleId": "R03", + "torrentId": "t28", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "官方 中字 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t29", + "ruleId": "R03", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "无间道 粤语 中字 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t30", + "ruleId": "R03", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t31", + "ruleId": "R03", + "torrentId": "t31", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "官种 特效中字 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R03:t32", + "ruleId": "R03", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['720[pi]|x720']", "国配 杜比全景声 不匹配 R03#0 过滤规则"] + } + }, + { + "caseId": "R04:t01", + "ruleId": "R04", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t02", + "ruleId": "R04", + "torrentId": "t02", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t03", + "ruleId": "R04", + "torrentId": "t03", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t04", + "ruleId": "R04", + "torrentId": "t04", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t05", + "ruleId": "R04", + "torrentId": "t05", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t06", + "ruleId": "R04", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不包含任何项 ['1080[pi]|x1080']", "不匹配 R04#0 过滤规则"] + } + }, + { + "caseId": "R04:t07", + "ruleId": "R04", + "torrentId": "t07", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t08", + "ruleId": "R04", + "torrentId": "t08", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t09", + "ruleId": "R04", + "torrentId": "t09", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t10", + "ruleId": "R04", + "torrentId": "t10", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t11", + "ruleId": "R04", + "torrentId": "t11", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t12", + "ruleId": "R04", + "torrentId": "t12", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t13", + "ruleId": "R04", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不包含任何项 ['1080[pi]|x1080']", "不匹配 R04#0 过滤规则"] + } + }, + { + "caseId": "R04:t14", + "ruleId": "R04", + "torrentId": "t14", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t15", + "ruleId": "R04", + "torrentId": "t15", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t16", + "ruleId": "R04", + "torrentId": "t16", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t17", + "ruleId": "R04", + "torrentId": "t17", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t18", + "ruleId": "R04", + "torrentId": "t18", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t19", + "ruleId": "R04", + "torrentId": "t19", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t20", + "ruleId": "R04", + "torrentId": "t20", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t21", + "ruleId": "R04", + "torrentId": "t21", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t22", + "ruleId": "R04", + "torrentId": "t22", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t23", + "ruleId": "R04", + "torrentId": "t23", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t24", + "ruleId": "R04", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不包含任何项 ['1080[pi]|x1080']", "不匹配 R04#0 过滤规则"] + } + }, + { + "caseId": "R04:t25", + "ruleId": "R04", + "torrentId": "t25", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t26", + "ruleId": "R04", + "torrentId": "t26", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t27", + "ruleId": "R04", + "torrentId": "t27", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t28", + "ruleId": "R04", + "torrentId": "t28", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t29", + "ruleId": "R04", + "torrentId": "t29", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t30", + "ruleId": "R04", + "torrentId": "t30", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R04:t31", + "ruleId": "R04", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R04:t32", + "ruleId": "R04", + "torrentId": "t32", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 1"] + } + }, + { + "caseId": "R05:t01", + "ruleId": "R05", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t02", + "ruleId": "R05", + "torrentId": "t02", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t03", + "ruleId": "R05", + "torrentId": "t03", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t04", + "ruleId": "R05", + "torrentId": "t04", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t05", + "ruleId": "R05", + "torrentId": "t05", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t06", + "ruleId": "R05", + "torrentId": "t06", + "mp": { + "pri": 98, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "不包含任何项 ['1080[pi]|x1080']", "优先级为 3"] + } + }, + { + "caseId": "R05:t07", + "ruleId": "R05", + "torrentId": "t07", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t08", + "ruleId": "R05", + "torrentId": "t08", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t09", + "ruleId": "R05", + "torrentId": "t09", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t10", + "ruleId": "R05", + "torrentId": "t10", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t11", + "ruleId": "R05", + "torrentId": "t11", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t12", + "ruleId": "R05", + "torrentId": "t12", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t13", + "ruleId": "R05", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['4k|2160p|x2160']", + "不包含任何项 ['1080[pi]|x1080']", + "不包含任何项 ['720[pi]|x720']", + "不匹配 R05#0 过滤规则" + ] + } + }, + { + "caseId": "R05:t14", + "ruleId": "R05", + "torrentId": "t14", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t15", + "ruleId": "R05", + "torrentId": "t15", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t16", + "ruleId": "R05", + "torrentId": "t16", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t17", + "ruleId": "R05", + "torrentId": "t17", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t18", + "ruleId": "R05", + "torrentId": "t18", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t19", + "ruleId": "R05", + "torrentId": "t19", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t20", + "ruleId": "R05", + "torrentId": "t20", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t21", + "ruleId": "R05", + "torrentId": "t21", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t22", + "ruleId": "R05", + "torrentId": "t22", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t23", + "ruleId": "R05", + "torrentId": "t23", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t24", + "ruleId": "R05", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['4k|2160p|x2160']", + "不包含任何项 ['1080[pi]|x1080']", + "不包含任何项 ['720[pi]|x720']", + "不匹配 R05#0 过滤规则" + ] + } + }, + { + "caseId": "R05:t25", + "ruleId": "R05", + "torrentId": "t25", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t26", + "ruleId": "R05", + "torrentId": "t26", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t27", + "ruleId": "R05", + "torrentId": "t27", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t28", + "ruleId": "R05", + "torrentId": "t28", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t29", + "ruleId": "R05", + "torrentId": "t29", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t30", + "ruleId": "R05", + "torrentId": "t30", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R05:t31", + "ruleId": "R05", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R05:t32", + "ruleId": "R05", + "torrentId": "t32", + "mp": { + "pri": 99, + "trace": ["不包含任何项 ['4k|2160p|x2160']", "优先级为 2"] + } + }, + { + "caseId": "R06:t01", + "ruleId": "R06", + "torrentId": "t01", + "mp": { + "pri": null, + "trace": [ + "包含 (?i)(\\b[XH]\\.?264\\b|\\b[XH]\\.?265\\b|\\bWEB-?DL\\b|\\bWEB-?RIP\\b|\\bHDTV(?:RIP)?\\b|\\bREMUX\\b|\\bBDRip\\b|\\bBRRip\\b|\\bHDRip\\b|\\bENCODE\\b|\\b(?20000MB", "沙丘2 | 国语 中字 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 8G 不在范围 >20000MB", "沙丘2 | 国语 中字 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t05", + "ruleId": "R26", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["大小 15.0G 不在范围 >20000MB", "简繁英字幕 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 15G 不在范围 >20000MB", "简繁英字幕 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t06", + "ruleId": "R26", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["大小 2.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 2G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t07", + "ruleId": "R26", + "torrentId": "t07", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R26:t08", + "ruleId": "R26", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["大小 18.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 18G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t09", + "ruleId": "R26", + "torrentId": "t09", + "mp": { + "pri": null, + "trace": ["大小 60.0G 不在范围 >20000MB", "熊家餐馆 第三季 全10集 中字 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 60G 不在范围 >20000MB", "熊家餐馆 第三季 全10集 中字 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t10", + "ruleId": "R26", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["大小 2.5G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t11", + "ruleId": "R26", + "torrentId": "t11", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R26:t12", + "ruleId": "R26", + "torrentId": "t12", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R26:t13", + "ruleId": "R26", + "torrentId": "t13", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R26:t14", + "ruleId": "R26", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["大小 10.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 10G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t15", + "ruleId": "R26", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": ["大小 1.0G 不在范围 >20000MB", "简繁日内封 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 1G 不在范围 >20000MB", "简繁日内封 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t16", + "ruleId": "R26", + "torrentId": "t16", + "mp": { + "pri": null, + "trace": ["大小 12.0G 不在范围 >20000MB", "流浪地球2 国语配音 中文字幕 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 12G 不在范围 >20000MB", "流浪地球2 国语配音 中文字幕 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t17", + "ruleId": "R26", + "torrentId": "t17", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R26:t18", + "ruleId": "R26", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["大小 15.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 15G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t19", + "ruleId": "R26", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["大小 6.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 6G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t20", + "ruleId": "R26", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["大小 14.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 14G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t21", + "ruleId": "R26", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["大小 4.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 4G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t22", + "ruleId": "R26", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["大小 4.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 4G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t23", + "ruleId": "R26", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["大小 36.0G 不在范围 >20000MB", "全12集 简体中字 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 36G 不在范围 >20000MB", "全12集 简体中字 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t24", + "ruleId": "R26", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["大小 1.4G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t25", + "ruleId": "R26", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["大小 8.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 8G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t26", + "ruleId": "R26", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["大小 10.0G 不在范围 >20000MB", "60帧 国语 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 10G 不在范围 >20000MB", "60帧 国语 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t27", + "ruleId": "R26", + "torrentId": "t27", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "R26:t28", + "ruleId": "R26", + "torrentId": "t28", + "mp": { + "pri": null, + "trace": ["大小 5.0G 不在范围 >20000MB", "官方 中字 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 5G 不在范围 >20000MB", "官方 中字 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t29", + "ruleId": "R26", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": ["大小 12.0G 不在范围 >20000MB", "无间道 粤语 中字 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 12G 不在范围 >20000MB", "无间道 粤语 中字 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t30", + "ruleId": "R26", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["大小 5.0G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 5G 不在范围 >20000MB", "不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t31", + "ruleId": "R26", + "torrentId": "t31", + "mp": { + "pri": null, + "trace": ["大小 4.0G 不在范围 >20000MB", "官种 特效中字 不匹配 R26#0 过滤规则"], + "rustTrace": ["大小 4G 不在范围 >20000MB", "官种 特效中字 不匹配 R26#0 过滤规则"] + } + }, + { + "caseId": "R26:t32", + "ruleId": "R26", + "torrentId": "t32", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t01", + "ruleId": "N01", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t02", + "ruleId": "N01", + "torrentId": "t02", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t03", + "ruleId": "N01", + "torrentId": "t03", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t04", + "ruleId": "N01", + "torrentId": "t04", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "沙丘2 | 国语 中字 不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t05", + "ruleId": "N01", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "简繁英字幕 不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t06", + "ruleId": "N01", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t07", + "ruleId": "N01", + "torrentId": "t07", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t08", + "ruleId": "N01", + "torrentId": "t08", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t09", + "ruleId": "N01", + "torrentId": "t09", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t10", + "ruleId": "N01", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t11", + "ruleId": "N01", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t12", + "ruleId": "N01", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t13", + "ruleId": "N01", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t14", + "ruleId": "N01", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t15", + "ruleId": "N01", + "torrentId": "t15", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t16", + "ruleId": "N01", + "torrentId": "t16", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t17", + "ruleId": "N01", + "torrentId": "t17", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t18", + "ruleId": "N01", + "torrentId": "t18", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t19", + "ruleId": "N01", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t20", + "ruleId": "N01", + "torrentId": "t20", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t21", + "ruleId": "N01", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t22", + "ruleId": "N01", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t23", + "ruleId": "N01", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "全12集 简体中字 不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t24", + "ruleId": "N01", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t25", + "ruleId": "N01", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t26", + "ruleId": "N01", + "torrentId": "t26", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t27", + "ruleId": "N01", + "torrentId": "t27", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t28", + "ruleId": "N01", + "torrentId": "t28", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "官方 中字 不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t29", + "ruleId": "N01", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "无间道 粤语 中字 不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t30", + "ruleId": "N01", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N01:t31", + "ruleId": "N01", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N01:t32", + "ruleId": "N01", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[Hx].?265|HEVC']", "国配 杜比全景声 不匹配 N01#0 过滤规则"] + } + }, + { + "caseId": "N02:t01", + "ruleId": "N02", + "torrentId": "t01", + "mp": { + "pri": null, + "trace": ["不匹配 N02#0 过滤规则"] + } + }, + { + "caseId": "N02:t02", + "ruleId": "N02", + "torrentId": "t02", + "mp": { + "pri": null, + "trace": ["沙丘2 中字 特效 不匹配 N02#0 过滤规则"] + } + }, + { + "caseId": "N02:t03", + "ruleId": "N02", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["不匹配 N02#0 过滤规则"] + } + }, + { + "caseId": "N02:t04", + "ruleId": "N02", + "torrentId": "t04", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t05", + "ruleId": "N02", + "torrentId": "t05", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t06", + "ruleId": "N02", + "torrentId": "t06", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t07", + "ruleId": "N02", + "torrentId": "t07", + "mp": { + "pri": null, + "trace": ["不匹配 N02#0 过滤规则"] + } + }, + { + "caseId": "N02:t08", + "ruleId": "N02", + "torrentId": "t08", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t09", + "ruleId": "N02", + "torrentId": "t09", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t10", + "ruleId": "N02", + "torrentId": "t10", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t11", + "ruleId": "N02", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", "不匹配 N02#0 过滤规则"] + } + }, + { + "caseId": "N02:t12", + "ruleId": "N02", + "torrentId": "t12", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t13", + "ruleId": "N02", + "torrentId": "t13", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t14", + "ruleId": "N02", + "torrentId": "t14", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t15", + "ruleId": "N02", + "torrentId": "t15", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t16", + "ruleId": "N02", + "torrentId": "t16", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t17", + "ruleId": "N02", + "torrentId": "t17", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t18", + "ruleId": "N02", + "torrentId": "t18", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t19", + "ruleId": "N02", + "torrentId": "t19", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t20", + "ruleId": "N02", + "torrentId": "t20", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t21", + "ruleId": "N02", + "torrentId": "t21", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t22", + "ruleId": "N02", + "torrentId": "t22", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t23", + "ruleId": "N02", + "torrentId": "t23", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t24", + "ruleId": "N02", + "torrentId": "t24", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t25", + "ruleId": "N02", + "torrentId": "t25", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t26", + "ruleId": "N02", + "torrentId": "t26", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t27", + "ruleId": "N02", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["不匹配 N02#0 过滤规则"] + } + }, + { + "caseId": "N02:t28", + "ruleId": "N02", + "torrentId": "t28", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t29", + "ruleId": "N02", + "torrentId": "t29", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t30", + "ruleId": "N02", + "torrentId": "t30", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t31", + "ruleId": "N02", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "不包含任何项 ['REMUX']", + "优先级为 1" + ] + } + }, + { + "caseId": "N02:t32", + "ruleId": "N02", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.]+Vision|DOVI|[\\\\s.]+DV[\\\\s.]+|杜比视界']", + "国配 杜比全景声 不匹配 N02#0 过滤规则" + ] + } + }, + { + "caseId": "N03:t01", + "ruleId": "N03", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N03:t02", + "ruleId": "N03", + "torrentId": "t02", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", + "沙丘2 中字 特效 不匹配 N03#0 过滤规则" + ] + } + }, + { + "caseId": "N03:t03", + "ruleId": "N03", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t04", + "ruleId": "N03", + "torrentId": "t04", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", + "沙丘2 | 国语 中字 不匹配 N03#0 过滤规则" + ] + } + }, + { + "caseId": "N03:t05", + "ruleId": "N03", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", + "简繁英字幕 不匹配 N03#0 过滤规则" + ] + } + }, + { + "caseId": "N03:t06", + "ruleId": "N03", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t07", + "ruleId": "N03", + "torrentId": "t07", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N03:t08", + "ruleId": "N03", + "torrentId": "t08", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N03:t09", + "ruleId": "N03", + "torrentId": "t09", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N03:t10", + "ruleId": "N03", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t11", + "ruleId": "N03", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t12", + "ruleId": "N03", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t13", + "ruleId": "N03", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t14", + "ruleId": "N03", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t15", + "ruleId": "N03", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", + "简繁日内封 不匹配 N03#0 过滤规则" + ] + } + }, + { + "caseId": "N03:t16", + "ruleId": "N03", + "torrentId": "t16", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N03:t17", + "ruleId": "N03", + "torrentId": "t17", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N03:t18", + "ruleId": "N03", + "torrentId": "t18", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N03:t19", + "ruleId": "N03", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t20", + "ruleId": "N03", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t21", + "ruleId": "N03", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t22", + "ruleId": "N03", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t23", + "ruleId": "N03", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", + "全12集 简体中字 不匹配 N03#0 过滤规则" + ] + } + }, + { + "caseId": "N03:t24", + "ruleId": "N03", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t25", + "ruleId": "N03", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t26", + "ruleId": "N03", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "60帧 国语 不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t27", + "ruleId": "N03", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t28", + "ruleId": "N03", + "torrentId": "t28", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "官方 中字 不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t29", + "ruleId": "N03", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", + "无间道 粤语 中字 不匹配 N03#0 过滤规则" + ] + } + }, + { + "caseId": "N03:t30", + "ruleId": "N03", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", "不匹配 N03#0 过滤规则"] + } + }, + { + "caseId": "N03:t31", + "ruleId": "N03", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N03:t32", + "ruleId": "N03", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['[\\\\s.]+HDR[\\\\s.]+|HDR10|HDR10\\\\+|HDRVivid']", + "国配 杜比全景声 不匹配 N03#0 过滤规则" + ] + } + }, + { + "caseId": "N04:t01", + "ruleId": "N04", + "torrentId": "t01", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t02", + "ruleId": "N04", + "torrentId": "t02", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N04:t03", + "ruleId": "N04", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t04", + "ruleId": "N04", + "torrentId": "t04", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "沙丘2 | 国语 中字 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t05", + "ruleId": "N04", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "简繁英字幕 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t06", + "ruleId": "N04", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t07", + "ruleId": "N04", + "torrentId": "t07", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t08", + "ruleId": "N04", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t09", + "ruleId": "N04", + "torrentId": "t09", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "熊家餐馆 第三季 全10集 中字 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t10", + "ruleId": "N04", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t11", + "ruleId": "N04", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t12", + "ruleId": "N04", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t13", + "ruleId": "N04", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t14", + "ruleId": "N04", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t15", + "ruleId": "N04", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "简繁日内封 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t16", + "ruleId": "N04", + "torrentId": "t16", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "流浪地球2 国语配音 中文字幕 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t17", + "ruleId": "N04", + "torrentId": "t17", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "奥本海默 CHS/ENG 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t18", + "ruleId": "N04", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t19", + "ruleId": "N04", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t20", + "ruleId": "N04", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t21", + "ruleId": "N04", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t22", + "ruleId": "N04", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t23", + "ruleId": "N04", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "全12集 简体中字 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t24", + "ruleId": "N04", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t25", + "ruleId": "N04", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t26", + "ruleId": "N04", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "60帧 国语 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t27", + "ruleId": "N04", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t28", + "ruleId": "N04", + "torrentId": "t28", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "官方 中字 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t29", + "ruleId": "N04", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "无间道 粤语 中字 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t30", + "ruleId": "N04", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N04:t31", + "ruleId": "N04", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N04:t32", + "ruleId": "N04", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['特效']", "国配 杜比全景声 不匹配 N04#0 过滤规则"] + } + }, + { + "caseId": "N05:t01", + "ruleId": "N05", + "torrentId": "t01", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t02", + "ruleId": "N05", + "torrentId": "t02", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "沙丘2 中字 特效 不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t03", + "ruleId": "N05", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t04", + "ruleId": "N05", + "torrentId": "t04", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N05:t05", + "ruleId": "N05", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "简繁英字幕 不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t06", + "ruleId": "N05", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t07", + "ruleId": "N05", + "torrentId": "t07", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t08", + "ruleId": "N05", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t09", + "ruleId": "N05", + "torrentId": "t09", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", + "熊家餐馆 第三季 全10集 中字 不匹配 N05#0 过滤规则" + ] + } + }, + { + "caseId": "N05:t10", + "ruleId": "N05", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t11", + "ruleId": "N05", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t12", + "ruleId": "N05", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t13", + "ruleId": "N05", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t14", + "ruleId": "N05", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t15", + "ruleId": "N05", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "简繁日内封 不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t16", + "ruleId": "N05", + "torrentId": "t16", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N05:t17", + "ruleId": "N05", + "torrentId": "t17", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "奥本海默 CHS/ENG 不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t18", + "ruleId": "N05", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t19", + "ruleId": "N05", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t20", + "ruleId": "N05", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t21", + "ruleId": "N05", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t22", + "ruleId": "N05", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t23", + "ruleId": "N05", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "全12集 简体中字 不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t24", + "ruleId": "N05", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t25", + "ruleId": "N05", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t26", + "ruleId": "N05", + "torrentId": "t26", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N05:t27", + "ruleId": "N05", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t28", + "ruleId": "N05", + "torrentId": "t28", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "官方 中字 不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t29", + "ruleId": "N05", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "无间道 粤语 中字 不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t30", + "ruleId": "N05", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t31", + "ruleId": "N05", + "torrentId": "t31", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['[国國][语語]配音|[国國]配|[国國][语語]']", "官种 特效中字 不匹配 N05#0 过滤规则"] + } + }, + { + "caseId": "N05:t32", + "ruleId": "N05", + "torrentId": "t32", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N06:t01", + "ruleId": "N06", + "torrentId": "t01", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t02", + "ruleId": "N06", + "torrentId": "t02", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "沙丘2 中字 特效 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t03", + "ruleId": "N06", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t04", + "ruleId": "N06", + "torrentId": "t04", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "沙丘2 | 国语 中字 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t05", + "ruleId": "N06", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "简繁英字幕 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t06", + "ruleId": "N06", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t07", + "ruleId": "N06", + "torrentId": "t07", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t08", + "ruleId": "N06", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t09", + "ruleId": "N06", + "torrentId": "t09", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "熊家餐馆 第三季 全10集 中字 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t10", + "ruleId": "N06", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t11", + "ruleId": "N06", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t12", + "ruleId": "N06", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t13", + "ruleId": "N06", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t14", + "ruleId": "N06", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t15", + "ruleId": "N06", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "简繁日内封 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t16", + "ruleId": "N06", + "torrentId": "t16", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "流浪地球2 国语配音 中文字幕 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t17", + "ruleId": "N06", + "torrentId": "t17", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "奥本海默 CHS/ENG 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t18", + "ruleId": "N06", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t19", + "ruleId": "N06", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t20", + "ruleId": "N06", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t21", + "ruleId": "N06", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t22", + "ruleId": "N06", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t23", + "ruleId": "N06", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "全12集 简体中字 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t24", + "ruleId": "N06", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t25", + "ruleId": "N06", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t26", + "ruleId": "N06", + "torrentId": "t26", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N06:t27", + "ruleId": "N06", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t28", + "ruleId": "N06", + "torrentId": "t28", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "官方 中字 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t29", + "ruleId": "N06", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "无间道 粤语 中字 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t30", + "ruleId": "N06", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t31", + "ruleId": "N06", + "torrentId": "t31", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "官种 特效中字 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N06:t32", + "ruleId": "N06", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['60fps|60帧']", "国配 杜比全景声 不匹配 N06#0 过滤规则"] + } + }, + { + "caseId": "N07:t01", + "ruleId": "N07", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t02", + "ruleId": "N07", + "torrentId": "t02", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t03", + "ruleId": "N07", + "torrentId": "t03", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t04", + "ruleId": "N07", + "torrentId": "t04", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t05", + "ruleId": "N07", + "torrentId": "t05", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t06", + "ruleId": "N07", + "torrentId": "t06", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t07", + "ruleId": "N07", + "torrentId": "t07", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t08", + "ruleId": "N07", + "torrentId": "t08", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t09", + "ruleId": "N07", + "torrentId": "t09", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t10", + "ruleId": "N07", + "torrentId": "t10", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t11", + "ruleId": "N07", + "torrentId": "t11", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t12", + "ruleId": "N07", + "torrentId": "t12", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t13", + "ruleId": "N07", + "torrentId": "t13", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t14", + "ruleId": "N07", + "torrentId": "t14", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t15", + "ruleId": "N07", + "torrentId": "t15", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t16", + "ruleId": "N07", + "torrentId": "t16", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t17", + "ruleId": "N07", + "torrentId": "t17", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t18", + "ruleId": "N07", + "torrentId": "t18", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t19", + "ruleId": "N07", + "torrentId": "t19", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t20", + "ruleId": "N07", + "torrentId": "t20", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t21", + "ruleId": "N07", + "torrentId": "t21", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t22", + "ruleId": "N07", + "torrentId": "t22", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t23", + "ruleId": "N07", + "torrentId": "t23", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t24", + "ruleId": "N07", + "torrentId": "t24", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t25", + "ruleId": "N07", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不匹配 N07#0 过滤规则"] + } + }, + { + "caseId": "N07:t26", + "ruleId": "N07", + "torrentId": "t26", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t27", + "ruleId": "N07", + "torrentId": "t27", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t28", + "ruleId": "N07", + "torrentId": "t28", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t29", + "ruleId": "N07", + "torrentId": "t29", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t30", + "ruleId": "N07", + "torrentId": "t30", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t31", + "ruleId": "N07", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N07:t32", + "ruleId": "N07", + "torrentId": "t32", + "mp": { + "pri": 100, + "trace": ["不包含任何项 ['3D']", "优先级为 1"] + } + }, + { + "caseId": "N08:t01", + "ruleId": "N08", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N08:t02", + "ruleId": "N08", + "torrentId": "t02", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "沙丘2 中字 特效 不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t03", + "ruleId": "N08", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t04", + "ruleId": "N08", + "torrentId": "t04", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", + "沙丘2 | 国语 中字 不匹配 N08#0 过滤规则" + ] + } + }, + { + "caseId": "N08:t05", + "ruleId": "N08", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "简繁英字幕 不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t06", + "ruleId": "N08", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t07", + "ruleId": "N08", + "torrentId": "t07", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N08:t08", + "ruleId": "N08", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t09", + "ruleId": "N08", + "torrentId": "t09", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", + "熊家餐馆 第三季 全10集 中字 不匹配 N08#0 过滤规则" + ] + } + }, + { + "caseId": "N08:t10", + "ruleId": "N08", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t11", + "ruleId": "N08", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t12", + "ruleId": "N08", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t13", + "ruleId": "N08", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t14", + "ruleId": "N08", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t15", + "ruleId": "N08", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "简繁日内封 不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t16", + "ruleId": "N08", + "torrentId": "t16", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", + "流浪地球2 国语配音 中文字幕 不匹配 N08#0 过滤规则" + ] + } + }, + { + "caseId": "N08:t17", + "ruleId": "N08", + "torrentId": "t17", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", + "奥本海默 CHS/ENG 不匹配 N08#0 过滤规则" + ] + } + }, + { + "caseId": "N08:t18", + "ruleId": "N08", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t19", + "ruleId": "N08", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t20", + "ruleId": "N08", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t21", + "ruleId": "N08", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t22", + "ruleId": "N08", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t23", + "ruleId": "N08", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "全12集 简体中字 不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t24", + "ruleId": "N08", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t25", + "ruleId": "N08", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t26", + "ruleId": "N08", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "60帧 国语 不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t27", + "ruleId": "N08", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t28", + "ruleId": "N08", + "torrentId": "t28", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "官方 中字 不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t29", + "ruleId": "N08", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": [ + "不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", + "无间道 粤语 中字 不匹配 N08#0 过滤规则" + ] + } + }, + { + "caseId": "N08:t30", + "ruleId": "N08", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t31", + "ruleId": "N08", + "torrentId": "t31", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['Dolby[\\\\s.+]+Atmos|Atmos|杜比全景[声聲]']", "官种 特效中字 不匹配 N08#0 过滤规则"] + } + }, + { + "caseId": "N08:t32", + "ruleId": "N08", + "torrentId": "t32", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N09:t01", + "ruleId": "N09", + "torrentId": "t01", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t02", + "ruleId": "N09", + "torrentId": "t02", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "沙丘2 中字 特效 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t03", + "ruleId": "N09", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t04", + "ruleId": "N09", + "torrentId": "t04", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "沙丘2 | 国语 中字 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t05", + "ruleId": "N09", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "简繁英字幕 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t06", + "ruleId": "N09", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t07", + "ruleId": "N09", + "torrentId": "t07", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t08", + "ruleId": "N09", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t09", + "ruleId": "N09", + "torrentId": "t09", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "熊家餐馆 第三季 全10集 中字 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t10", + "ruleId": "N09", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t11", + "ruleId": "N09", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t12", + "ruleId": "N09", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t13", + "ruleId": "N09", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t14", + "ruleId": "N09", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t15", + "ruleId": "N09", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "简繁日内封 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t16", + "ruleId": "N09", + "torrentId": "t16", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "流浪地球2 国语配音 中文字幕 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t17", + "ruleId": "N09", + "torrentId": "t17", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "奥本海默 CHS/ENG 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t18", + "ruleId": "N09", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t19", + "ruleId": "N09", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t20", + "ruleId": "N09", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t21", + "ruleId": "N09", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t22", + "ruleId": "N09", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t23", + "ruleId": "N09", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "全12集 简体中字 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t24", + "ruleId": "N09", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t25", + "ruleId": "N09", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t26", + "ruleId": "N09", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "60帧 国语 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t27", + "ruleId": "N09", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t28", + "ruleId": "N09", + "torrentId": "t28", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N09:t29", + "ruleId": "N09", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "无间道 粤语 中字 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t30", + "ruleId": "N09", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N09:t31", + "ruleId": "N09", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N09:t32", + "ruleId": "N09", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": ["不包含任何项 ['官方', '官种', '官组']", "国配 杜比全景声 不匹配 N09#0 过滤规则"] + } + }, + { + "caseId": "N10:t01", + "ruleId": "N10", + "torrentId": "t01", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t02", + "ruleId": "N10", + "torrentId": "t02", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "沙丘2 中字 特效 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "沙丘2 中字 特效 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t03", + "ruleId": "N10", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t04", + "ruleId": "N10", + "torrentId": "t04", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "沙丘2 | 国语 中字 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "沙丘2 | 国语 中字 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t05", + "ruleId": "N10", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "简繁英字幕 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "简繁英字幕 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t06", + "ruleId": "N10", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t07", + "ruleId": "N10", + "torrentId": "t07", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t08", + "ruleId": "N10", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t09", + "ruleId": "N10", + "torrentId": "t09", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "熊家餐馆 第三季 全10集 中字 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "熊家餐馆 第三季 全10集 中字 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t10", + "ruleId": "N10", + "torrentId": "t10", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t11", + "ruleId": "N10", + "torrentId": "t11", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t12", + "ruleId": "N10", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t13", + "ruleId": "N10", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t14", + "ruleId": "N10", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t15", + "ruleId": "N10", + "torrentId": "t15", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "简繁日内封 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "简繁日内封 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t16", + "ruleId": "N10", + "torrentId": "t16", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "流浪地球2 国语配音 中文字幕 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "流浪地球2 国语配音 中文字幕 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t17", + "ruleId": "N10", + "torrentId": "t17", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "奥本海默 CHS/ENG 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "奥本海默 CHS/ENG 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t18", + "ruleId": "N10", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t19", + "ruleId": "N10", + "torrentId": "t19", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t20", + "ruleId": "N10", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t21", + "ruleId": "N10", + "torrentId": "t21", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t22", + "ruleId": "N10", + "torrentId": "t22", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t23", + "ruleId": "N10", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "全12集 简体中字 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "全12集 简体中字 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t24", + "ruleId": "N10", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t25", + "ruleId": "N10", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t26", + "ruleId": "N10", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "60帧 国语 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "60帧 国语 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t27", + "ruleId": "N10", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t28", + "ruleId": "N10", + "torrentId": "t28", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N10:t29", + "ruleId": "N10", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "无间道 粤语 中字 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "无间道 粤语 中字 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t30", + "ruleId": "N10", + "torrentId": "t30", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t31", + "ruleId": "N10", + "torrentId": "t31", + "mp": { + "pri": null, + "trace": ["FREE值 0.5 不是 0", "官种 特效中字 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N10:t32", + "ruleId": "N10", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": ["FREE值 1.0 不是 0", "国配 杜比全景声 不匹配 N10#0 过滤规则"], + "rustTrace": ["FREE值 1 不是 0", "国配 杜比全景声 不匹配 N10#0 过滤规则"] + } + }, + { + "caseId": "N11:t01", + "ruleId": "N11", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t02", + "ruleId": "N11", + "torrentId": "t02", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t03", + "ruleId": "N11", + "torrentId": "t03", + "mp": { + "pri": null, + "trace": ["做种人数 4 小于 5", "不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t04", + "ruleId": "N11", + "torrentId": "t04", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t05", + "ruleId": "N11", + "torrentId": "t05", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t06", + "ruleId": "N11", + "torrentId": "t06", + "mp": { + "pri": null, + "trace": ["做种人数 1 小于 5", "不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t07", + "ruleId": "N11", + "torrentId": "t07", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t08", + "ruleId": "N11", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["做种人数 2 小于 5", "不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t09", + "ruleId": "N11", + "torrentId": "t09", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t10", + "ruleId": "N11", + "torrentId": "t10", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t11", + "ruleId": "N11", + "torrentId": "t11", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t12", + "ruleId": "N11", + "torrentId": "t12", + "mp": { + "pri": null, + "trace": ["做种人数 3 小于 5", "不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t13", + "ruleId": "N11", + "torrentId": "t13", + "mp": { + "pri": null, + "trace": ["做种人数 2 小于 5", "不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t14", + "ruleId": "N11", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["做种人数 1 小于 5", "不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t15", + "ruleId": "N11", + "torrentId": "t15", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t16", + "ruleId": "N11", + "torrentId": "t16", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t17", + "ruleId": "N11", + "torrentId": "t17", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t18", + "ruleId": "N11", + "torrentId": "t18", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t19", + "ruleId": "N11", + "torrentId": "t19", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t20", + "ruleId": "N11", + "torrentId": "t20", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t21", + "ruleId": "N11", + "torrentId": "t21", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t22", + "ruleId": "N11", + "torrentId": "t22", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t23", + "ruleId": "N11", + "torrentId": "t23", + "mp": { + "pri": null, + "trace": ["做种人数 3 小于 5", "全12集 简体中字 不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t24", + "ruleId": "N11", + "torrentId": "t24", + "mp": { + "pri": null, + "trace": ["做种人数 2 小于 5", "不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t25", + "ruleId": "N11", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["做种人数 2 小于 5", "不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t26", + "ruleId": "N11", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["做种人数 4 小于 5", "60帧 国语 不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t27", + "ruleId": "N11", + "torrentId": "t27", + "mp": { + "pri": null, + "trace": ["做种人数 3 小于 5", "不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N11:t28", + "ruleId": "N11", + "torrentId": "t28", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t29", + "ruleId": "N11", + "torrentId": "t29", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t30", + "ruleId": "N11", + "torrentId": "t30", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t31", + "ruleId": "N11", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "N11:t32", + "ruleId": "N11", + "torrentId": "t32", + "mp": { + "pri": null, + "trace": ["做种人数 2 小于 5", "国配 杜比全景声 不匹配 N11#0 过滤规则"] + } + }, + { + "caseId": "N12:t01", + "ruleId": "N12", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['[中国國繁简](/|\\\\s|\\\\\\\\|\\\\|)?[繁简英粤]|[英简繁](/|\\\\s|\\\\\\\\|\\\\|)?[中繁简]|繁體|简体|[中国國][字配]|国语|國語|中文|中字|简日|繁日|简繁|繁体|([\\\\s,.-\\\\[])(chs|cht)(|[\\\\s,.-\\\\]])|(?20000MB", + "沙丘2 | 国语 中字 不匹配 X17#0 过滤规则" + ], + "rustTrace": [ + "大小 8G 不在范围 <8000MB", + "大小 8G 不在范围 >20000MB", + "沙丘2 | 国语 中字 不匹配 X17#0 过滤规则" + ] + } + }, + { + "caseId": "X17:t05", + "ruleId": "X17", + "torrentId": "t05", + "mp": { + "pri": null, + "trace": ["大小 15.0G 不在范围 <8000MB", "大小 15.0G 不在范围 >20000MB", "简繁英字幕 不匹配 X17#0 过滤规则"], + "rustTrace": ["大小 15G 不在范围 <8000MB", "大小 15G 不在范围 >20000MB", "简繁英字幕 不匹配 X17#0 过滤规则"] + } + }, + { + "caseId": "X17:t06", + "ruleId": "X17", + "torrentId": "t06", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t07", + "ruleId": "X17", + "torrentId": "t07", + "mp": { + "pri": 99, + "trace": ["大小 20.0G 不在范围 <8000MB", "优先级为 2"], + "rustTrace": ["大小 20G 不在范围 <8000MB", "优先级为 2"] + } + }, + { + "caseId": "X17:t08", + "ruleId": "X17", + "torrentId": "t08", + "mp": { + "pri": null, + "trace": ["大小 18.0G 不在范围 <8000MB", "大小 18.0G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"], + "rustTrace": ["大小 18G 不在范围 <8000MB", "大小 18G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"] + } + }, + { + "caseId": "X17:t09", + "ruleId": "X17", + "torrentId": "t09", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t10", + "ruleId": "X17", + "torrentId": "t10", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t11", + "ruleId": "X17", + "torrentId": "t11", + "mp": { + "pri": 99, + "trace": ["大小 30.0G 不在范围 <8000MB", "优先级为 2"], + "rustTrace": ["大小 30G 不在范围 <8000MB", "优先级为 2"] + } + }, + { + "caseId": "X17:t12", + "ruleId": "X17", + "torrentId": "t12", + "mp": { + "pri": 99, + "trace": ["大小 40.0G 不在范围 <8000MB", "优先级为 2"], + "rustTrace": ["大小 40G 不在范围 <8000MB", "优先级为 2"] + } + }, + { + "caseId": "X17:t13", + "ruleId": "X17", + "torrentId": "t13", + "mp": { + "pri": 99, + "trace": ["大小 45.0G 不在范围 <8000MB", "优先级为 2"], + "rustTrace": ["大小 45G 不在范围 <8000MB", "优先级为 2"] + } + }, + { + "caseId": "X17:t14", + "ruleId": "X17", + "torrentId": "t14", + "mp": { + "pri": null, + "trace": ["大小 10.0G 不在范围 <8000MB", "大小 10.0G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"], + "rustTrace": ["大小 10G 不在范围 <8000MB", "大小 10G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"] + } + }, + { + "caseId": "X17:t15", + "ruleId": "X17", + "torrentId": "t15", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t16", + "ruleId": "X17", + "torrentId": "t16", + "mp": { + "pri": null, + "trace": [ + "大小 12.0G 不在范围 <8000MB", + "大小 12.0G 不在范围 >20000MB", + "流浪地球2 国语配音 中文字幕 不匹配 X17#0 过滤规则" + ], + "rustTrace": [ + "大小 12G 不在范围 <8000MB", + "大小 12G 不在范围 >20000MB", + "流浪地球2 国语配音 中文字幕 不匹配 X17#0 过滤规则" + ] + } + }, + { + "caseId": "X17:t17", + "ruleId": "X17", + "torrentId": "t17", + "mp": { + "pri": 99, + "trace": ["大小 30.0G 不在范围 <8000MB", "优先级为 2"], + "rustTrace": ["大小 30G 不在范围 <8000MB", "优先级为 2"] + } + }, + { + "caseId": "X17:t18", + "ruleId": "X17", + "torrentId": "t18", + "mp": { + "pri": null, + "trace": ["大小 15.0G 不在范围 <8000MB", "大小 15.0G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"], + "rustTrace": ["大小 15G 不在范围 <8000MB", "大小 15G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"] + } + }, + { + "caseId": "X17:t19", + "ruleId": "X17", + "torrentId": "t19", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t20", + "ruleId": "X17", + "torrentId": "t20", + "mp": { + "pri": null, + "trace": ["大小 14.0G 不在范围 <8000MB", "大小 14.0G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"], + "rustTrace": ["大小 14G 不在范围 <8000MB", "大小 14G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"] + } + }, + { + "caseId": "X17:t21", + "ruleId": "X17", + "torrentId": "t21", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t22", + "ruleId": "X17", + "torrentId": "t22", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t23", + "ruleId": "X17", + "torrentId": "t23", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t24", + "ruleId": "X17", + "torrentId": "t24", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t25", + "ruleId": "X17", + "torrentId": "t25", + "mp": { + "pri": null, + "trace": ["大小 8.0G 不在范围 <8000MB", "大小 8.0G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"], + "rustTrace": ["大小 8G 不在范围 <8000MB", "大小 8G 不在范围 >20000MB", "不匹配 X17#0 过滤规则"] + } + }, + { + "caseId": "X17:t26", + "ruleId": "X17", + "torrentId": "t26", + "mp": { + "pri": null, + "trace": ["大小 10.0G 不在范围 <8000MB", "大小 10.0G 不在范围 >20000MB", "60帧 国语 不匹配 X17#0 过滤规则"], + "rustTrace": ["大小 10G 不在范围 <8000MB", "大小 10G 不在范围 >20000MB", "60帧 国语 不匹配 X17#0 过滤规则"] + } + }, + { + "caseId": "X17:t27", + "ruleId": "X17", + "torrentId": "t27", + "mp": { + "pri": 99, + "trace": ["大小 50.0G 不在范围 <8000MB", "优先级为 2"], + "rustTrace": ["大小 50G 不在范围 <8000MB", "优先级为 2"] + } + }, + { + "caseId": "X17:t28", + "ruleId": "X17", + "torrentId": "t28", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t29", + "ruleId": "X17", + "torrentId": "t29", + "mp": { + "pri": null, + "trace": [ + "大小 12.0G 不在范围 <8000MB", + "大小 12.0G 不在范围 >20000MB", + "无间道 粤语 中字 不匹配 X17#0 过滤规则" + ], + "rustTrace": [ + "大小 12G 不在范围 <8000MB", + "大小 12G 不在范围 >20000MB", + "无间道 粤语 中字 不匹配 X17#0 过滤规则" + ] + } + }, + { + "caseId": "X17:t30", + "ruleId": "X17", + "torrentId": "t30", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t31", + "ruleId": "X17", + "torrentId": "t31", + "mp": { + "pri": 100, + "trace": ["优先级为 1"] + } + }, + { + "caseId": "X17:t32", + "ruleId": "X17", + "torrentId": "t32", + "mp": { + "pri": 99, + "trace": ["大小 28.0G 不在范围 <8000MB", "优先级为 2"], + "rustTrace": ["大小 28G 不在范围 <8000MB", "优先级为 2"] + } + }, + { + "caseId": "X18:t01", + "ruleId": "X18", + "torrentId": "t01", + "mp": { + "pri": 100, + "trace": [ + "不包含任何项 ['1080[pi]|x1080']", + "不包含任何项 ['[中国國繁简](/|\\\\s|\\\\\\\\|\\\\|)?[繁简英粤]|[英简繁](/|\\\\s|\\\\\\\\|\\\\|)?[中繁简]|繁體|简体|[中国國][字配]|国语|國語|中文|中字|简日|繁日|简繁|繁体|([\\\\s,.-\\\\[])(chs|cht)(|[\\\\s,.-\\\\]])|(?` 切层后 pyparsing RuleParser 的 as_list(); + * - cases:FilterModule.filter_torrents(多组串联,pri 取最后一组)的 pri_order 与 debug trace。 + * 我们用 parseRuleString → evaluateRuleChain 求同样的 pri。 + * 断言:解析结构全部一致;pri 不一致集合 == deviations 登记集合(补迁消除差异后测试会失败,提醒删登记)。 + */ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; +import { + BUILTIN_ATOMS, + catalogOf, + customAtomFromMp, + evaluateRuleChain, + fromMpParseList, + normalizeExpr, + parseRelease, + parseRuleString, + type RuleChainResult, + type RuleGroupSpec, + serializeLayers, +} from "../../src/index.ts"; + +interface Torrent { + id: string; + title: string; + description: string; + size: number; + seeders: number; + labels?: string[]; + downloadvolumefactor?: number; +} +interface Rule { + id: string; + groups: string[]; + mediainfo?: { original_language?: string }; +} +interface Case { + caseId: string; + ruleId: string; + torrentId: string; + mp: { pri: number | null; trace: string[]; rustTrace?: string[] }; +} +type DeviationKind = "intentional" | "missing" | "our_bug" | "mp_bug" | "not_expressible"; +interface Deviation { + kind: DeviationKind; + note: string; + mp?: number | null; + ours?: number | null; +} +interface Fixture { + generatedFrom: { commit: string }; + customRules: Record>; + torrents: Torrent[]; + rules: Rule[]; + parse: Record; + cases: Case[]; + deviations: Record; +} + +const fx = JSON.parse(readFileSync(new URL("./rules-cases.json", import.meta.url), "utf8")) as Fixture; +const custom = Object.entries(fx.customRules).map(([id, r]) => customAtomFromMp(id, r)); +const catalog = catalogOf(custom); +const customIds = custom.map((a) => a.id); +const torrents = new Map(fx.torrents.map((t) => [t.id, t])); +const rules = new Map(fx.rules.map((r) => [r.id, r])); + +function groupsOf(rule: Rule): RuleGroupSpec[] { + return rule.groups.map((s, i) => { + const p = parseRuleString(s, { customAtoms: customIds, allowUnknown: true }); + if (p.errors.length) throw new Error(`${rule.id}: ${JSON.stringify(p.errors)}`); + return { name: `${rule.id}#${i}`, layers: p.layers }; + }); +} + +function ours(c: Case): RuleChainResult { + const rule = rules.get(c.ruleId)!; + const t = torrents.get(c.torrentId)!; + const parsed = { ...parseRelease(t.title, t.description ? { subtitle: t.description } : {}), sizeBytes: t.size }; + return evaluateRuleChain( + groupsOf(rule), + parsed, + { + ...(t.description ? { subtitle: t.description } : {}), + labels: t.labels ?? [], + seeders: t.seeders, + downloadVolumeFactor: t.downloadvolumefactor ?? 1, + ...(rule.mediainfo?.original_language ? { originalLanguage: rule.mediainfo.original_language } : {}), + }, + catalog, + ); +} +const priOf = (r: RuleChainResult) => (r.passed ? (r.pri ?? 100) : null); + +describe("MP 规则串解析差分(rules-cases.json#parse)", () => { + it("≥40 条规则链,覆盖全部支持的内置关键字", () => { + expect(fx.rules.length).toBeGreaterThanOrEqual(40); + const used = new Set(Object.keys(fx.parse).flatMap((s) => s.match(/[A-Za-z0-9]+/g) ?? [])); + const supported = BUILTIN_ATOMS.filter((a) => a.supported).map((a) => a.id); + expect(supported.filter((id) => !used.has(id))).toEqual([]); + }); + + it("每个规则串的分层与层内结构 == MP RuleParser", () => { + for (const [s, mpLayers] of Object.entries(fx.parse)) { + const p = parseRuleString(s, { customAtoms: customIds, allowUnknown: true }); + expect(p.errors, s).toEqual([]); + expect( + p.layers.map((l) => l.expr), + s, + ).toEqual(mpLayers.map((l) => normalizeExpr(fromMpParseList(l)))); + } + }); + + it("序列化后再解析结构不变(规则串双向映射)", () => { + for (const s of Object.keys(fx.parse)) { + const a = parseRuleString(s, { customAtoms: customIds, allowUnknown: true }).layers; + const text = serializeLayers(a); + const b = parseRuleString(text, { customAtoms: customIds, allowUnknown: true }).layers; + expect(b, `${s} → ${text}`).toEqual(a); + } + }); +}); + +describe("MP 规则组过滤差分(rules-cases.json#cases)", () => { + it("pri 不一致集合 == deviations 登记集合", () => { + const actual: Record = {}; + for (const c of fx.cases) { + const o = priOf(ours(c)); + if (o !== c.mp.pri) actual[c.caseId] = { mp: c.mp.pri, ours: o }; + } + if (process.env.UMT_RULES_DUMP) console.log(`UMT_RULES_DUMP ${JSON.stringify(actual)}`); + expect(Object.keys(actual).sort()).toEqual(Object.keys(fx.deviations).sort()); + for (const [k, v] of Object.entries(fx.deviations)) { + expect(["intentional", "missing", "our_bug", "mp_bug", "not_expressible"]).toContain(v.kind); + expect(actual[k], k).toEqual({ mp: v.mp ?? null, ours: v.ours ?? null }); + } + // 一致率(报告用):登记外全部一致 + expect(fx.cases.length - Object.keys(actual).length).toBeGreaterThan(fx.cases.length * 0.9); + }); + + it("trace 可读:过滤给出未命中层与原子依据,放行给出命中层与 pri,未知条件写明不存在", () => { + for (const c of fx.cases) { + const r = ours(c); + const text = r.trace.join("\n"); + if (!r.passed) expect(text, c.caseId).toMatch(/未命中|硬排除|过滤/); + const withLayers = r.groups.some((g) => g.pri !== null); + if (r.passed && withLayers) expect(text, c.caseId).toMatch(/命中,pri=\d+/); + // MP trace 里提到的"不包含任何项"对应我们 ✗ 的原子依据 + if (c.mp.trace.some((x) => x.startsWith("不包含任何项")) && !r.passed) expect(text).toMatch(/✗/); + if (c.mp.trace.some((x) => /规则 \w+ 不存在/.test(x))) expect(text).toMatch(/条件不存在/); + } + }); +}); + +describe("规则串错误", () => { + it("未知条件给位置与相近名;音乐条件不支持;括号/空层", () => { + const a = parseRuleString("4K & CNSUBB > 1080P"); + expect(a.errors[0]).toMatchObject({ code: "unknown_atom", position: 5, layer: 0, atom: "CNSUBB" }); + expect(a.errors[0]!.suggestions).toContain("CNSUB"); + expect(parseRuleString("FLAC").errors[0]).toMatchObject({ code: "unsupported_atom", atom: "FLAC" }); + expect(parseRuleString("(4K | 1080P").errors[0]).toMatchObject({ code: "unbalanced_paren", position: 0 }); + expect(parseRuleString("4K)").errors[0]).toMatchObject({ code: "unbalanced_paren", position: 2 }); + expect(parseRuleString("4K > > 720P").errors[0]).toMatchObject({ code: "empty_layer", layer: 1, position: 4 }); + expect(parseRuleString("4K 1080P").errors[0]).toMatchObject({ code: "unexpected_token", position: 3 }); + expect(parseRuleString("4K & $").errors[0]).toMatchObject({ code: "invalid_char", position: 5 }); + expect(parseRuleString("").errors[0]!.code).toBe("empty"); + expect(parseRuleString("4k & !remux").layers.map((l) => l.expr)).toEqual([ + { + op: "and", + args: [ + { op: "atom", id: "4K" }, + { op: "not", arg: { op: "atom", id: "REMUX" } }, + ], + }, + ]); + }); + + it("序列化只加必要括号", () => { + const s = "SPECSUB & CNSUB > (4K | 1080P) & !(BLU | REMUX) > !!3D | 720P & H265"; + expect(serializeLayers(parseRuleString(s).layers)).toBe(s); + }); +}); diff --git a/packages/domain/test/rules-slot.test.ts b/packages/domain/test/rules-slot.test.ts new file mode 100644 index 00000000..6af23bf0 --- /dev/null +++ b/packages/domain/test/rules-slot.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, it } from "vitest"; +import { + currentPri, + evaluateCandidate, + parseRelease, + parseRuleString, + type RuleGroupSpec, + rankForSlot, + type SlotSpec, +} from "../src/index.ts"; + +const group = (name: string, s: string, extra: Partial = {}): RuleGroupSpec => ({ + name, + layers: s ? parseRuleString(s).layers : [], + ...extra, +}); +const base = (groups: RuleGroupSpec[], extra: Partial = {}): SlotSpec => ({ + name: "main", + hard: {}, + rules: { groups }, + ...extra, +}); +const rel = (t: string, subtitle?: string) => parseRelease(t, subtitle ? { subtitle } : {}); + +describe("版本目标接入规则组", () => { + it("硬排除命中 → reject(rule.hard_exclude),trace 带组名与修订号", () => { + const g = group("不要原盘", "", { + id: "g1", + revision: 3, + hardExcludes: parseRuleString("BLU | REMUX").layers.map((l) => l.expr), + }); + const ev = evaluateCandidate(base([g]), rel("Movie.2023.1080p.BluRay.REMUX.AVC-EPSiLON")); + expect(ev.decision).toBe("reject"); + if (ev.decision !== "reject") return; + expect(ev.violations).toEqual([ + { field: "rule", code: "hard_exclude", expected: "不要原盘", actual: ["BLU | REMUX"] }, + ]); + expect(ev.trace?.join("\n")).toContain("规则组「不要原盘」@r3 硬排除 BLU | REMUX 命中"); + }); + + it("多组串联取交集,pri 取最后一个带层的组", () => { + const spec = base([group("分辨率", "4K > 1080P"), group("字幕", "CNSUB > !CNSUB"), group("空组", "")]); + const a = evaluateCandidate(spec, rel("Dune.2024.1080p.WEB-DL.H264-HHWEB", "中字")); + expect(a).toMatchObject({ decision: "accept", pri: 100 }); + const b = evaluateCandidate(spec, rel("Dune.2024.2160p.WEB-DL.H265-FLUX")); + expect(b).toMatchObject({ decision: "accept", pri: 99 }); + const c = evaluateCandidate(spec, rel("Dune.2024.720p.WEBRip.x264-GRP")); + expect(c.decision).toBe("reject"); + if (c.decision === "reject") + expect(c.violations[0]).toMatchObject({ code: "no_layer_matched", expected: "分辨率" }); + }); + + it("ladderRequired=false 的软偏好组不中只降 pri,不拒绝", () => { + const spec = base([group("偏好", "SPECSUB", { ladderRequired: false })]); + const r = rel("X.2024.1080p.WEB-DL.H264-G", "特效中字"); + expect(evaluateCandidate(spec, r, null, { subtitle: "特效中字" })).toMatchObject({ pri: 100 }); + expect(evaluateCandidate(spec, rel("X.2024.1080p.WEB-DL.H264-G"))).toMatchObject({ decision: "accept", pri: 0 }); + }); + + it("有规则组阶梯时升级按 pri 严格提升(洗版)", () => { + const spec = base([group("洗版", "REMUX > BLURAY > WEBDL")], { upgrade: { enabled: true } }); + const up = evaluateCandidate(spec, rel("M.2023.1080p.BluRay.REMUX.AVC-E"), { + source: "WEB-DL", + resolution: "1080p", + pri: 98, + }); + expect(up).toMatchObject({ decision: "upgrade", pri: 100, fromPri: 98 }); + const same = evaluateCandidate(spec, rel("M.2023.1080p.WEB-DL.H264-G"), { source: "WEB-DL", pri: 98 }); + expect(same.decision).toBe("reject"); + expect(currentPri(spec, { source: "REMUX", resolution: "1080p" })).toBe(100); + }); + + it("作品原语言中文时 CNSUB 放行(MP tmdb 条件)", () => { + const spec = base([group("中字", "CNSUB")]); + const r = rel("Movie.2023.1080p.WEB-DL.H264-G"); + expect(evaluateCandidate(spec, r).decision).toBe("reject"); + expect(evaluateCandidate(spec, r, null, { originalLanguage: "zh" })).toMatchObject({ decision: "accept" }); + }); + + it("rankForSlot 按规则组 pri 排序", () => { + const spec = base([group("阶梯", "4K > 1080P > 720P")]); + const ranked = rankForSlot(spec, [ + rel("A.720p.WEB-DL.x264-G"), + rel("A.2160p.WEB-DL.x265-G"), + rel("A.1080p.WEB-DL.x264-G"), + ]); + expect(ranked.map((x) => x.evaluation.decision === "accept" && x.evaluation.pri)).toEqual([100, 99, 98]); + }); + + it("没有规则组时行为不变", () => { + const r = rel("Movie.2023.1080p.WEB-DL.H264-G"); + expect(evaluateCandidate(base([]), r)).toEqual(evaluateCandidate({ name: "main", hard: {} }, r)); + }); +}); diff --git a/scripts/mp-sync/gen-rules-differential.py b/scripts/mp-sync/gen-rules-differential.py new file mode 100644 index 00000000..810dd82a --- /dev/null +++ b/scripts/mp-sync/gen-rules-differential.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +"""规则组(W9-D)差分期望:MP 规则串解析 + 多规则组串联过滤的 pri 与 trace。 + +用法(必须用 MP 自己的 venv): + + MP_ROOT=/opt/moviepilot-plus/upstream/MoviePilot \ + $MP_ROOT/.venv/bin/python scripts/mp-sync/gen-rules-differential.py + +输出 packages/domain/test/differential/rules-cases.json: +- parse:每个规则串按 `>` 切层后 RuleParser.parse(layer).as_list()(pyparsing,Python 参考实现) +- cases:FilterModule.filter_torrents(Python 回退路径)对每个 (规则组链 × 种子) 的 pri_order; + trace 为 Python 路径的 debug 日志(逐条规则失败原因),rustTrace 为 filter_torrents_with_trace_fast 的调试输出 +- deviations 不由本脚本生成,重跑时保留已有登记。 +只读调用上游,不复制上游源码。 +""" +from __future__ import annotations + +import importlib.util +import json +import os +import sys +from pathlib import Path +from types import SimpleNamespace +from typing import Any, Optional +from unittest.mock import patch + +HERE = Path(__file__).resolve().parent +spec = importlib.util.spec_from_file_location("gen_differential", HERE / "gen-differential.py") +gd = importlib.util.module_from_spec(spec) # type: ignore[arg-type] +assert spec and spec.loader +spec.loader.exec_module(gd) # type: ignore[union-attr] + +OUT = gd.DOMAIN_DIFF / "rules-cases.json" +GiB = 1024 ** 3 + +EXTRA_TORRENTS: list[dict[str, Any]] = [ + {"id": "t29", "title": "Infernal.Affairs.2002.1080p.BluRay.x264.DTS-HK-GRP", "description": "无间道 粤语 中字", "size": 12 * GiB, "seeders": 6}, + {"id": "t30", "title": "Movie.2019.1080p.WEB-DL.SDR.H264-GRP", "description": "", "size": 5 * GiB, "seeders": 5}, + {"id": "t31", "title": "Show.S01E05.2160p.WEB-DL.HDR10+.H265-GRP", "description": "官种 特效中字", "labels": ["官种"], "size": 4 * GiB, "seeders": 30, "downloadvolumefactor": 0.5}, + {"id": "t32", "title": "Movie.2018.1080p.BluRay.Remux.AVC.Atmos-GRP", "description": "国配 杜比全景声", "size": 28 * GiB, "seeders": 2}, +] +TORRENTS = gd.TORRENTS + EXTRA_TORRENTS + +# groups:规则组链(按顺序串联取交集,pri 取最后一组);单组即单条规则串 +RULES: list[dict[str, Any]] = [ + *({"id": r["id"], "groups": [r["rule"]], **({"mediainfo": r["mediainfo"]} if r.get("mediainfo") else {})} for r in gd.RULES), + {"id": "X01", "groups": ["SPECSUB & CNSUB > CNSUB & 4K > 1080P & !BLU"]}, + {"id": "X02", "groups": ["HKVOI"]}, + {"id": "X03", "groups": ["SDR"]}, + {"id": "X04", "groups": ["HDR & !DOLBY"]}, + {"id": "X05", "groups": ["DOLBY | HDR"]}, + {"id": "X06", "groups": ["H264 > H265"]}, + {"id": "X07", "groups": ["!(BLU | REMUX) & 4K"]}, + {"id": "X08", "groups": ["BLURAY"]}, + {"id": "X09", "groups": ["FREE | SEED5"]}, + {"id": "X10", "groups": ["UHD & REMUX > 4K"]}, + {"id": "X11", "groups": ["CNVOI | HKVOI > !CNVOI"]}, + {"id": "X12", "groups": ["!!4K"]}, + {"id": "X13", "groups": ["4K & (CNSUB | SPECSUB) & !3D"]}, + {"id": "X14", "groups": ["ATMOS & (REMUX | BLU)"]}, + {"id": "X15", "groups": ["GZ > FREE > 4K"]}, + {"id": "X16", "groups": ["CNVOI"], "mediainfo": {"original_language": "zh"}}, + {"id": "X17", "groups": ["SIZESMALL > SIZEBIG"]}, + {"id": "X18", "groups": ["1080P & CNSUB | 4K & !CNSUB"]}, + {"id": "X19", "groups": ["60FPS | 3D > ATMOS"]}, + {"id": "M01", "groups": ["4K | 1080P", "CNSUB > !CNSUB"]}, + {"id": "M02", "groups": ["!BLU", "4K > 1080P > 720P"]}, + {"id": "M03", "groups": ["4K > 1080P", "WEBDL"]}, + {"id": "M04", "groups": ["CNSUB", "SIZEMOVIE", "H265 > H264"]}, + {"id": "M05", "groups": ["REMUX > BLU > WEBDL", "!DOLBY"]}, +] + + +def main() -> int: + root = gd._filter_mp_root(None) + if str(root) not in sys.path: + sys.path.insert(0, str(root)) + old = os.getcwd() + os.chdir(root) + try: + from app.adapters.system import rust as rust_accel + from app.application.rules import BUILTIN_RULE_SET, RuleParser + from app.domain.context import TorrentInfo + from app.modules import filter as filter_mod + from app.modules.filter import FilterModule + finally: + os.chdir(old) + + rule_set = {**BUILTIN_RULE_SET, **gd.CUSTOM_RULES} + + class _RuleHelper: + def __init__(self, groups): + self._groups = groups + + def get_rule_group_by_media(self, media=None, group_names=None): # noqa: ARG002 + return self._groups + + def mk(t: dict) -> Any: + return TorrentInfo( + site_name="S", title=t["title"], description=t.get("description") or "", + size=t["size"], seeders=t.get("seeders") or 0, labels=t.get("labels") or [], + downloadvolumefactor=t.get("downloadvolumefactor", 1.0), + ) + + parser = RuleParser() + parse_out: dict[str, Any] = {} + for r in RULES: + for g in r["groups"]: + if g in parse_out: + continue + with patch("app.application.rules._filter_rule_parser", None): + parse_out[g] = [parser.expr.parse_string(layer.strip()).as_list()[0] for layer in g.split(">")] + + def run(r: dict, t: dict) -> tuple[Optional[int], list[str], list[str]]: + module = FilterModule() + groups = [SimpleNamespace(name=f"{r['id']}#{i}", rule_string=g) for i, g in enumerate(r["groups"])] + module.rulehelper = _RuleHelper(groups) + module.rule_set = rule_set + mi = SimpleNamespace(**r["mediainfo"]) if r.get("mediainfo") else None + logs: list[str] = [] + with patch("app.modules.filter.rust_accel.is_enabled", return_value=False), \ + patch.object(filter_mod.logger, "debug", side_effect=lambda m, *a, **k: logs.append(str(m))): + res = module.filter_torrents(rule_groups=[g.name for g in groups], torrent_list=[mk(t)], mediainfo=mi) + pri = res[0].pri_order if res else None + rust_trace: list[str] = [] + native = getattr(rust_accel, "_moviepilot_rust", None) + if rust_accel.is_enabled() and native is not None and hasattr(native, "filter_torrents_with_trace_fast"): + try: + _m, rust_trace = native.filter_torrents_with_trace_fast( + [vars(g) for g in groups], [mk(t)], rule_set, mi, filter_mod._rust_parse_options(), + ) + except BaseException: # noqa: BLE001 + rust_trace = [] + return pri, logs, [str(x) for x in rust_trace] + + cases = [] + for r in RULES: + for t in TORRENTS: + pri, logs, rtrace = run(r, t) + # 去掉每行重复的 "种子 S - <标题> " 前缀;Rust trace 与 Python 相同时不重复存 + prefix = f"种子 S - {t['title']} " + strip = lambda xs: [x[len(prefix):].strip() if x.startswith(prefix) else x for x in xs] # noqa: E731 + mp: dict[str, Any] = {"pri": pri, "trace": strip(logs)} + if rtrace and rtrace != logs: + mp["rustTrace"] = strip(rtrace) + cases.append({"caseId": f"{r['id']}:{t['id']}", "ruleId": r["id"], "torrentId": t["id"], "mp": mp}) + + deviations: dict = {} + if OUT.exists(): + try: + deviations = json.loads(OUT.read_text("utf-8")).get("deviations", {}) + except Exception: # noqa: BLE001 + deviations = {} + doc = { + "generatedFrom": { + "repo": "MoviePilot", "commit": gd._filter_mp_commit(root), + "engine": "python-fallback (FilterModule, rust_accel disabled); parse = pyparsing RuleParser", + "generator": "scripts/mp-sync/gen-rules-differential.py", + "semantics": "groups 串联取交集,pri = 最后一组命中层的 pri_order(100 起逐层 -1);null = 被过滤", + }, + "customRules": gd.CUSTOM_RULES, + "torrents": TORRENTS, + "rules": RULES, + "parse": parse_out, + "cases": cases, + "deviations": deviations, + } + OUT.write_text(json.dumps(doc, ensure_ascii=False, indent=1) + "\n", "utf-8") + print(f"rules: rules={len(RULES)} cases={len(cases)} parse={len(parse_out)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main())