diff --git a/app/agent/tools/impl/_filter_rule_utils.py b/app/agent/tools/impl/_filter_rule_utils.py index 3f128b1ce..b3eacab8c 100644 --- a/app/agent/tools/impl/_filter_rule_utils.py +++ b/app/agent/tools/impl/_filter_rule_utils.py @@ -4,15 +4,13 @@ import copy import re from typing import Any, Dict, Iterable, Optional -from app.runtime.events import eventmanager from app.application.agentdata import get_agent_subscribe_port from app.application.configuration import get_configured_system_config -from app.application.rules import RuleHelper -from app.application.rules import RuleParser -from app.application.rules import BUILTIN_RULE_SET +from app.application.rules import BUILTIN_RULE_SET, RuleHelper, RuleParser +from app.runtime.events import eventmanager +from app.schemas.event import ConfigChangeEventData from app.schemas.rule import CustomRule from app.schemas.system import FilterRuleGroup -from app.schemas.event import ConfigChangeEventData from app.schemas.types import EventType, SystemConfigKey RULE_ID_PATTERN = re.compile(r"^[A-Za-z0-9]+$") @@ -27,8 +25,10 @@ MEDIA_TYPE_ALIASES = { "tv": "电视剧", "series": "电视剧", "show": "电视剧", + "music": "音乐", "电影": "电影", "电视剧": "电视剧", + "音乐": "音乐", } RULE_STRING_SYNTAX = { @@ -76,9 +76,9 @@ def normalize_media_type(value: Optional[str]) -> Optional[str]: if not value: return None normalized = MEDIA_TYPE_ALIASES.get(value.lower(), value) - if normalized not in {"电影", "电视剧"}: + if normalized not in {"电影", "电视剧", "音乐"}: raise ValueError( - "media_type 仅支持 '电影'、'电视剧'、'movie' 或 'tv'" + "media_type 仅支持 '电影'、'电视剧'、'音乐'、'movie'、'tv' 或 'music'" ) return normalized diff --git a/app/agent/tools/impl/add_rule_group.py b/app/agent/tools/impl/add_rule_group.py index 909666e36..23e8b28e7 100644 --- a/app/agent/tools/impl/add_rule_group.py +++ b/app/agent/tools/impl/add_rule_group.py @@ -6,7 +6,6 @@ from typing import Optional, Type from pydantic import BaseModel, Field from app.agent.tools.base import MoviePilotTool -from app.agent.tools.tags import ToolTag from app.agent.tools.impl._filter_rule_utils import ( build_custom_rule_map, collect_rule_group_usages, @@ -17,6 +16,7 @@ from app.agent.tools.impl._filter_rule_utils import ( save_system_config, serialize_rule_group, ) +from app.agent.tools.tags import ToolTag from app.runtime.log import logger from app.schemas.types import SystemConfigKey @@ -35,7 +35,7 @@ class AddRuleGroupInput(BaseModel): ) media_type: Optional[str] = Field( None, - description="Optional media type scope: '电影', '电视剧', 'movie', or 'tv'.", + description="Optional media type scope: '电影', '电视剧', '音乐', 'movie', 'tv', or 'music'.", ) category: Optional[str] = Field( None, diff --git a/app/application/rules.py b/app/application/rules.py index 88e85fa18..16b6656b9 100644 --- a/app/application/rules.py +++ b/app/application/rules.py @@ -6,7 +6,18 @@ import threading from typing import Dict, List, Optional -from pyparsing import Forward, Literal, Word, alphas, infix_notation, opAssoc, alphanums, Combine, nums, ParseResults +from pyparsing import ( + Combine, + Forward, + Literal, + ParseResults, + Word, + alphanums, + alphas, + infix_notation, + nums, + opAssoc, +) from app.adapters.system import rust as rust_accel from app.application.configuration import get_configured_system_config @@ -53,10 +64,8 @@ class RuleHelper: if not group.media_type or ( media - and ( - (not group.category and group.media_type == media.type.value) - or group.category == media.category - ) + and group.media_type == media.type.value + and (not group.category or group.category == media.category) ) ] diff --git a/app/schemas/rule.py b/app/schemas/rule.py index 9ec9522fd..6db6392fb 100644 --- a/app/schemas/rule.py +++ b/app/schemas/rule.py @@ -31,7 +31,7 @@ class FilterRuleGroup(BaseModel): name: Optional[str] = None # 规则串 rule_string: Optional[str] = None - # 适用类媒体类型 None-全部 电影/电视剧 + # 适用媒体类型 None-全部 电影/电视剧/音乐 media_type: Optional[str] = None # 适用媒体类别 None-全部 对应二级分类 category: Optional[str] = None diff --git a/tests/test_rule_group_media.py b/tests/test_rule_group_media.py new file mode 100644 index 000000000..69df0cfd4 --- /dev/null +++ b/tests/test_rule_group_media.py @@ -0,0 +1,68 @@ +from app.agent.tools.impl._filter_rule_utils import normalize_media_type +from app.application.rules import RuleHelper +from app.domain.context import MediaInfo, MusicInfo, TorrentInfo +from app.modules.filter import FilterModule +from app.schemas.rule import FilterRuleGroup +from app.schemas.types import MediaType + + +def test_agent_rule_group_media_type_accepts_music_aliases(): + """Agent 规则组写入入口应统一接受中英文音乐类型。""" + assert normalize_media_type("music") == MediaType.MUSIC.value + assert normalize_media_type("音乐") == MediaType.MUSIC.value + + +def test_music_rule_group_matches_music_media(monkeypatch): + """音乐规则组应在音乐搜索和订阅的过滤上下文中生效。""" + helper = RuleHelper() + groups = [ + FilterRuleGroup(name="music", rule_string="FLAC", media_type=MediaType.MUSIC.value), + FilterRuleGroup(name="movie", rule_string="BLURAY", media_type=MediaType.MOVIE.value), + ] + monkeypatch.setattr(helper, "get_rule_groups", lambda: groups) + + matched = helper.get_rule_group_by_media( + media=MusicInfo(title="Example"), + group_names=["music", "movie"], + ) + + assert [group.name for group in matched] == ["music"] + + +def test_music_rule_group_filters_music_torrents(monkeypatch): + """音乐专属规则组被选中后应实际过滤音乐资源。""" + helper = RuleHelper() + groups = [ + FilterRuleGroup(name="music", rule_string="FLAC", media_type=MediaType.MUSIC.value) + ] + monkeypatch.setattr(helper, "get_rule_groups", lambda: groups) + module = FilterModule() + module.rulehelper = helper + module.rule_set = {"FLAC": {"include": "FLAC"}} + lossless = TorrentInfo(title="Artist Album FLAC", description="") + lossy = TorrentInfo(title="Artist Album MP3 320kbps", description="") + + filtered = module.filter_torrents( + rule_groups=["music"], + torrent_list=[lossless, lossy], + mediainfo=MusicInfo(title="Album"), + ) + + assert filtered == [lossless] + + +def test_rule_group_category_cannot_cross_media_types(monkeypatch): + """二级分类相同时仍必须先匹配规则组的主媒体类型。""" + helper = RuleHelper() + groups = [ + FilterRuleGroup( + name="movie-category", + rule_string="BLURAY", + media_type=MediaType.MOVIE.value, + category="shared", + ) + ] + monkeypatch.setattr(helper, "get_rule_groups", lambda: groups) + media = MediaInfo(type=MediaType.TV, category="shared") + + assert helper.get_rule_group_by_media(media=media, group_names=["movie-category"]) == []