""" 规则域:用户规则组配置访问、内置规则定义与规则解析器, 过滤模块与 Agent 工具共享同一事实来源。 """ import threading from typing import Dict, List, Optional from pyparsing import Forward, Literal, Word, alphas, infix_notation, opAssoc, alphanums, Combine, nums, ParseResults from app.adapters.system import rust as rust_accel from app.application.configuration import get_configured_system_config from app.domain.context import MediaInfo from app.schemas.rule import CustomRule from app.schemas.system import FilterRuleGroup from app.schemas.types import SystemConfigKey class RuleHelper: """读取用户过滤规则配置,并按媒体上下文选择适用规则组。""" @staticmethod def get_rule_groups() -> List[FilterRuleGroup]: """返回用户配置的全部过滤规则组。""" rule_groups: List[dict] = get_configured_system_config().get( SystemConfigKey.UserFilterRuleGroups ) if not rule_groups: return [] return [FilterRuleGroup(**group) for group in rule_groups] def get_rule_group(self, group_name: str) -> Optional[FilterRuleGroup]: """按名称返回过滤规则组。""" return next( (group for group in self.get_rule_groups() if group.name == group_name), None, ) def get_rule_group_by_media( self, media: Optional[MediaInfo] = None, group_names: Optional[list] = None, ) -> List[FilterRuleGroup]: """按媒体类型、分类和候选名称筛选适用规则组。""" rule_groups = self.get_rule_groups() if group_names: rule_groups = [ group for group in rule_groups if group.name in group_names ] return [ group for group in rule_groups if not group.media_type or ( media and ( (not group.category and group.media_type == media.type.value) or group.category == media.category ) ) ] @staticmethod def get_custom_rules() -> List[CustomRule]: """返回用户配置的全部自定义过滤规则。""" rules: List[dict] = get_configured_system_config().get(SystemConfigKey.CustomFilterRules) if not rules: return [] return [CustomRule(**rule) for rule in rules] def get_custom_rule(self, rule_id: str) -> Optional[CustomRule]: """按 ID 返回一条自定义过滤规则。""" return next( (rule for rule in self.get_custom_rules() if rule.id == rule_id), None, ) # 内置规则只在这里维护一份,便于过滤模块和 Agent 工具共享同一套事实来源。 BUILTIN_RULE_SET: Dict[str, dict] = { # 蓝光原盘 "BLU": { "include": [ r"(?i)(\bBlu-?Ray\b.*\b(?:VC-?1|AVC|MPEG-?2)\b|\b(?:UHD|4K|2160p)\b(?:.*Blu-?Ray)?.*\b(?:HEVC|H\.?265)\b|\bBlu-?Ray\b.*\b(?:UHD|4K|2160p)\b.*\b(?:HEVC|H\.?265)\b|\b(?:COMPLETE|FULL)\b.*\b(?:(?:UHD|4K|2160p)\b.*)?Blu-?Ray\b|\b(BD25|BD50|BD66|BD100|BDMV|MiniBD)\b)" ], "exclude": [ r"(?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(? ParseResults: """ 解析给定的表达式。 参数: expression -- 要解析的表达式 返回: 解析结果 """ rust_result = rust_accel.parse_filter_rule(expression) if rust_result is not None: return _RustParseResults(rust_result) return self.expr.parse_string(expression) class _RustParseResults(list): """ 包装 Rust 解析结果,提供本模块调用方使用的 as_list/asList 接口。 """ def as_list(self) -> list: """ 返回兼容 pyparsing.ParseResults.as_list 的列表结构。 """ return list(self) def asList(self) -> list: # noqa: N802 """ 返回兼容 pyparsing.ParseResults.asList 的列表结构。 """ return self.as_list() if __name__ == '__main__': # 测试代码 expression_str = """ SPECSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & 4K & !BLU & !REMUX & !WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > CNSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > CNSUB & CNVOI & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > SPECSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > CNSUB & 4K & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > SPECSUB & CNVOI & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & CNVOI & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > CNSUB & 4K & WEBDL & 60FPS & !DOLBY & !SDR & !3D > SPECSUB & CNVOI & 4K & WEBDL & !DOLBY & HDR & !3D > CNSUB & CNVOI & 4K & WEBDL & !DOLBY & HDR & !3D > SPECSUB & CNVOI & 4K & WEBDL & !DOLBY & !3D > CNSUB & CNVOI & 4K & WEBDL & !DOLBY & !3D > SPECSUB & 4K & WEBDL & !DOLBY & HDR & !3D > CNSUB & 4K & WEBDL & !DOLBY & HDR & !3D > SPECSUB & 4K & WEBDL & !DOLBY & !3D > CNSUB & 4K & WEBDL & !DOLBY & !3D > SPECSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > CNSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & !3D > CNSUB & CNVOI & 4K & !BLU & !WEBDL & !DOLBY & !3D > SPECSUB & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 4K & !BLU & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 4K & !BLU & !WEBDL & !DOLBY & !SDR & !3D > CNSUB & 4K & !BLU & !WEBDL & !DOLBY & !SDR & !3D > 4K & !BLU & !REMUX & !DOLBY & HDR & !3D > 4K & !BLURAY & !REMUX & !DOLBY & !3D > SPECSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > CNSUB & 1080P & !BLU & !REMUX & !WEBDL & !DOLBY & !3D > SPECSUB & 1080P & !BLU & !WEBDL & !DOLBY & HDR & !3D > CNSUB & 1080P & !BLU & !WEBDL & !DOLBY & HDR & !3D > SPECSUB & 1080P & !BLU & !WEBDL & !DOLBY & !3D > CNSUB & 1080P & !BLU & !WEBDL & !DOLBY & !3D > SPECSUB & 1080P & WEBDL & !DOLBY & HDR & !3D > CNSUB & 1080P & WEBDL & !DOLBY & HDR & !3D > SPECSUB & 1080P & WEBDL & !DOLBY & !3D > CNSUB & 1080P & WEBDL & !DOLBY & !3D > 1080P & !BLU & !REMUX & !DOLBY & HDR & !3D > 1080P & !BLU & !REMUX & !DOLBY & !3D """ for exp in expression_str.split('>'): parsed_expr = RuleParser().parse(exp.strip()) print(parsed_expr.asList())