mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-28 19:47:41 +08:00
feat(filter): 支持音乐优先级规则组
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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"]) == []
|
||||||
Reference in New Issue
Block a user