feat(filter): 支持音乐优先级规则组

This commit is contained in:
jxxghp
2026-08-25 16:34:07 +08:00
parent c727d9c9f1
commit 605bf8174a
5 changed files with 92 additions and 15 deletions
+7 -7
View File
@@ -4,15 +4,13 @@ import copy
import re import re
from typing import Any, Dict, Iterable, Optional 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.agentdata import get_agent_subscribe_port
from app.application.configuration import get_configured_system_config from app.application.configuration import get_configured_system_config
from app.application.rules import RuleHelper from app.application.rules import BUILTIN_RULE_SET, RuleHelper, RuleParser
from app.application.rules import RuleParser from app.runtime.events import eventmanager
from app.application.rules import BUILTIN_RULE_SET from app.schemas.event import ConfigChangeEventData
from app.schemas.rule import CustomRule from app.schemas.rule import CustomRule
from app.schemas.system import FilterRuleGroup from app.schemas.system import FilterRuleGroup
from app.schemas.event import ConfigChangeEventData
from app.schemas.types import EventType, SystemConfigKey from app.schemas.types import EventType, SystemConfigKey
RULE_ID_PATTERN = re.compile(r"^[A-Za-z0-9]+$") RULE_ID_PATTERN = re.compile(r"^[A-Za-z0-9]+$")
@@ -27,8 +25,10 @@ MEDIA_TYPE_ALIASES = {
"tv": "电视剧", "tv": "电视剧",
"series": "电视剧", "series": "电视剧",
"show": "电视剧", "show": "电视剧",
"music": "音乐",
"电影": "电影", "电影": "电影",
"电视剧": "电视剧", "电视剧": "电视剧",
"音乐": "音乐",
} }
RULE_STRING_SYNTAX = { RULE_STRING_SYNTAX = {
@@ -76,9 +76,9 @@ def normalize_media_type(value: Optional[str]) -> Optional[str]:
if not value: if not value:
return None return None
normalized = MEDIA_TYPE_ALIASES.get(value.lower(), value) normalized = MEDIA_TYPE_ALIASES.get(value.lower(), value)
if normalized not in {"电影", "电视剧"}: if normalized not in {"电影", "电视剧", "音乐"}:
raise ValueError( raise ValueError(
"media_type 仅支持 '电影''电视剧''movie''tv'" "media_type 仅支持 '电影''电视剧''音乐''movie''tv''music'"
) )
return normalized return normalized
+2 -2
View File
@@ -6,7 +6,6 @@ from typing import Optional, Type
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from app.agent.tools.base import MoviePilotTool from app.agent.tools.base import MoviePilotTool
from app.agent.tools.tags import ToolTag
from app.agent.tools.impl._filter_rule_utils import ( from app.agent.tools.impl._filter_rule_utils import (
build_custom_rule_map, build_custom_rule_map,
collect_rule_group_usages, collect_rule_group_usages,
@@ -17,6 +16,7 @@ from app.agent.tools.impl._filter_rule_utils import (
save_system_config, save_system_config,
serialize_rule_group, serialize_rule_group,
) )
from app.agent.tools.tags import ToolTag
from app.runtime.log import logger from app.runtime.log import logger
from app.schemas.types import SystemConfigKey from app.schemas.types import SystemConfigKey
@@ -35,7 +35,7 @@ class AddRuleGroupInput(BaseModel):
) )
media_type: Optional[str] = Field( media_type: Optional[str] = Field(
None, None,
description="Optional media type scope: '电影', '电视剧', 'movie', or 'tv'.", description="Optional media type scope: '电影', '电视剧', '音乐', 'movie', 'tv', or 'music'.",
) )
category: Optional[str] = Field( category: Optional[str] = Field(
None, None,
+14 -5
View File
@@ -6,7 +6,18 @@
import threading import threading
from typing import Dict, List, Optional 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.adapters.system import rust as rust_accel
from app.application.configuration import get_configured_system_config from app.application.configuration import get_configured_system_config
@@ -53,10 +64,8 @@ class RuleHelper:
if not group.media_type if not group.media_type
or ( or (
media media
and ( and group.media_type == media.type.value
(not group.category and group.media_type == media.type.value) and (not group.category or group.category == media.category)
or group.category == media.category
)
) )
] ]
+1 -1
View File
@@ -31,7 +31,7 @@ class FilterRuleGroup(BaseModel):
name: Optional[str] = None name: Optional[str] = None
# 规则串 # 规则串
rule_string: Optional[str] = None rule_string: Optional[str] = None
# 适用媒体类型 None-全部 电影/电视剧 # 适用媒体类型 None-全部 电影/电视剧/音乐
media_type: Optional[str] = None media_type: Optional[str] = None
# 适用媒体类别 None-全部 对应二级分类 # 适用媒体类别 None-全部 对应二级分类
category: Optional[str] = None category: Optional[str] = None
+68
View File
@@ -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"]) == []