Skip download dir selection for single matches

This commit is contained in:
jxxghp
2026-06-27 22:29:55 +08:00
parent 2a8708498c
commit a9197c434e
2 changed files with 142 additions and 12 deletions
+11 -2
View File
@@ -2635,6 +2635,8 @@ class MediaInteractionChain(ChainBase):
download_dirs = self._get_download_dirs(media_info) download_dirs = self._get_download_dirs(media_info)
if not download_dirs: if not download_dirs:
return False return False
if len(download_dirs) == 1 and not self._is_auto_download_dir(download_dirs[0]):
return False
request.pending_torrent_page = request.page request.pending_torrent_page = request.page
request.phase = "download-dir" request.phase = "download-dir"
@@ -3252,6 +3254,11 @@ class MediaInteractionChain(ChainBase):
""" """
获取可供消息交互选择的下载目录。 获取可供消息交互选择的下载目录。
""" """
dir_infos = [
dir_info
for dir_info in DirectoryHelper().get_download_dirs()
if dir_info.download_path
]
download_dirs = [ download_dirs = [
DownloadDirectory( DownloadDirectory(
name=dir_info.name, name=dir_info.name,
@@ -3265,11 +3272,13 @@ class MediaInteractionChain(ChainBase):
media_type=dir_info.media_type, media_type=dir_info.media_type,
media_category=dir_info.media_category, media_category=dir_info.media_category,
) )
for dir_info in DirectoryHelper().get_download_dirs() for dir_info in dir_infos
if dir_info.download_path and cls._match_download_dir_media(dir_info, media_info) if cls._match_download_dir_media(dir_info, media_info)
] ]
if not download_dirs: if not download_dirs:
return [] return []
if len(download_dirs) == 1:
return download_dirs
return [cls._build_auto_download_dir(), *download_dirs] return [cls._build_auto_download_dir(), *download_dirs]
@classmethod @classmethod
+131 -10
View File
@@ -65,7 +65,7 @@ def _build_tv_context(title: str = "葬送的芙莉莲") -> Context:
def _build_download_dirs() -> list[TransferDirectoryConf]: def _build_download_dirs() -> list[TransferDirectoryConf]:
"""构造消息交互可选择的下载目录配置。""" """构造不同媒体类型各一个下载目录配置。"""
return [ return [
TransferDirectoryConf( TransferDirectoryConf(
name="电影下载", name="电影下载",
@@ -85,6 +85,46 @@ def _build_download_dirs() -> list[TransferDirectoryConf]:
] ]
def _build_multiple_movie_download_dirs() -> list[TransferDirectoryConf]:
"""构造多个匹配电影类型的下载目录配置。"""
return [
TransferDirectoryConf(
name="电影下载",
storage="local",
download_path="/downloads/movies",
priority=1,
media_type=MediaType.MOVIE.value,
),
TransferDirectoryConf(
name="4K电影下载",
storage="local",
download_path="/downloads/uhd-movies",
priority=2,
media_type=MediaType.MOVIE.value,
),
TransferDirectoryConf(
name="动画下载",
storage="rclone",
download_path="/media/anime",
priority=3,
media_type=MediaType.TV.value,
media_category="动漫",
),
]
def _build_single_download_dir() -> list[TransferDirectoryConf]:
"""构造只有一个下载目录的配置。"""
return [
TransferDirectoryConf(
name="默认下载",
storage="local",
download_path="/downloads",
priority=1,
),
]
def test_message_routes_text_reply_to_media_interaction_before_ai(): def test_message_routes_text_reply_to_media_interaction_before_ai():
"""已有传统媒体交互时,用户回复应优先交给传统交互处理。""" """已有传统媒体交互时,用户回复应优先交给传统交互处理。"""
chain = MessageChain() chain = MessageChain()
@@ -295,7 +335,7 @@ def test_media_interaction_legacy_page_callback_updates_existing_request():
def test_torrent_selection_prompts_download_dir_buttons_before_download(): def test_torrent_selection_prompts_download_dir_buttons_before_download():
"""支持按钮的渠道选择资源后,应先发送下载目录按钮而不是立即下载。""" """匹配当前媒体的目录有多个时,应先发送下载目录按钮而不是立即下载。"""
chain = MediaInteractionChain() chain = MediaInteractionChain()
context = _build_context() context = _build_context()
request = media_interaction_manager.create_or_replace( request = media_interaction_manager.create_or_replace(
@@ -313,7 +353,7 @@ def test_torrent_selection_prompts_download_dir_buttons_before_download():
with patch( with patch(
"app.chain.message.DirectoryHelper.get_download_dirs", "app.chain.message.DirectoryHelper.get_download_dirs",
return_value=_build_download_dirs(), return_value=_build_multiple_movie_download_dirs(),
), patch.object(chain, "post_message") as post_message, patch( ), patch.object(chain, "post_message") as post_message, patch(
"app.chain.message.DownloadChain.download_single" "app.chain.message.DownloadChain.download_single"
) as download_single: ) as download_single:
@@ -334,12 +374,93 @@ def test_torrent_selection_prompts_download_dir_buttons_before_download():
assert "请选择下载目录" in notification.title assert "请选择下载目录" in notification.title
assert "1. 自动匹配目录" in notification.text assert "1. 自动匹配目录" in notification.text
assert "2. 电影下载 (/downloads/movies)" in notification.text assert "2. 电影下载 (/downloads/movies)" in notification.text
assert "3. 4K电影下载 (/downloads/uhd-movies)" in notification.text
assert "动画下载" not in notification.text assert "动画下载" not in notification.text
assert notification.buttons[0][0]["callback_data"] == f"media:{request.request_id}:download-dir:1" assert notification.buttons[0][0]["callback_data"] == f"media:{request.request_id}:download-dir:1"
def test_torrent_selection_skips_download_dir_when_only_one_dir_matches_media():
"""匹配当前媒体的目录只有一个时,应跳过目录选择并交给下载链自动匹配。"""
chain = MediaInteractionChain()
context = _build_context()
request = media_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
source="telegram-test",
username="tester",
action="Search",
keyword="星际穿越",
title="星际穿越",
meta=_build_meta("星际穿越"),
items=[context],
)
request.phase = "torrent"
with patch(
"app.chain.message.DirectoryHelper.get_download_dirs",
return_value=_build_download_dirs(),
), patch.object(chain, "post_message") as post_message, patch(
"app.chain.message.DownloadChain.download_single",
return_value="hash",
) as download_single:
handled = chain.handle_text_interaction(
channel=MessageChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
text="1",
)
assert handled
assert request.phase == "torrent"
post_message.assert_not_called()
download_single.assert_called_once()
assert download_single.call_args.args[0] is context
assert "save_path" not in download_single.call_args.kwargs
def test_torrent_selection_skips_download_dir_when_user_has_single_dir():
"""用户只有一个下载目录时,也应跳过目录选择并交给下载链自动匹配。"""
chain = MediaInteractionChain()
context = _build_context()
request = media_interaction_manager.create_or_replace(
user_id="10001",
channel=MessageChannel.Telegram,
source="telegram-test",
username="tester",
action="Search",
keyword="星际穿越",
title="星际穿越",
meta=_build_meta("星际穿越"),
items=[context],
)
request.phase = "torrent"
with patch(
"app.chain.message.DirectoryHelper.get_download_dirs",
return_value=_build_single_download_dir(),
), patch.object(chain, "post_message") as post_message, patch(
"app.chain.message.DownloadChain.download_single",
return_value="hash",
) as download_single:
handled = chain.handle_text_interaction(
channel=MessageChannel.Telegram,
source="telegram-test",
userid="10001",
username="tester",
text="1",
)
assert handled
assert request.phase == "torrent"
post_message.assert_not_called()
download_single.assert_called_once()
assert download_single.call_args.args[0] is context
assert "save_path" not in download_single.call_args.kwargs
def test_torrent_selection_prompts_text_download_dir_for_plain_channel(): def test_torrent_selection_prompts_text_download_dir_for_plain_channel():
"""不支持按钮的渠道选择资源后,应提示用户回复数字选择下载目录。""" """不支持按钮的渠道在多个匹配目录时,应提示用户回复数字选择下载目录。"""
chain = MediaInteractionChain() chain = MediaInteractionChain()
context = _build_context() context = _build_context()
request = media_interaction_manager.create_or_replace( request = media_interaction_manager.create_or_replace(
@@ -357,7 +478,7 @@ def test_torrent_selection_prompts_text_download_dir_for_plain_channel():
with patch( with patch(
"app.chain.message.DirectoryHelper.get_download_dirs", "app.chain.message.DirectoryHelper.get_download_dirs",
return_value=_build_download_dirs(), return_value=_build_multiple_movie_download_dirs(),
), patch.object(chain, "post_message") as post_message: ), patch.object(chain, "post_message") as post_message:
handled = chain.handle_text_interaction( handled = chain.handle_text_interaction(
channel=MessageChannel.Wechat, channel=MessageChannel.Wechat,
@@ -374,6 +495,7 @@ def test_torrent_selection_prompts_text_download_dir_for_plain_channel():
assert notification.buttons is None assert notification.buttons is None
assert "1. 自动匹配目录" in notification.text assert "1. 自动匹配目录" in notification.text
assert "2. 电影下载 (/downloads/movies)" in notification.text assert "2. 电影下载 (/downloads/movies)" in notification.text
assert "3. 4K电影下载 (/downloads/uhd-movies)" in notification.text
assert "动画下载" not in notification.text assert "动画下载" not in notification.text
@@ -398,7 +520,7 @@ def test_download_dir_callback_runs_pending_single_download_without_save_path_fo
with patch( with patch(
"app.chain.message.DirectoryHelper.get_download_dirs", "app.chain.message.DirectoryHelper.get_download_dirs",
return_value=_build_download_dirs(), return_value=_build_multiple_movie_download_dirs(),
), patch( ), patch(
"app.chain.message.DownloadChain.download_single", "app.chain.message.DownloadChain.download_single",
return_value="hash", return_value="hash",
@@ -440,7 +562,7 @@ def test_download_dir_callback_runs_pending_single_download_with_save_path():
with patch( with patch(
"app.chain.message.DirectoryHelper.get_download_dirs", "app.chain.message.DirectoryHelper.get_download_dirs",
return_value=_build_download_dirs(), return_value=_build_multiple_movie_download_dirs(),
), patch( ), patch(
"app.chain.message.DownloadChain.download_single", "app.chain.message.DownloadChain.download_single",
return_value="hash", return_value="hash",
@@ -482,7 +604,7 @@ def test_download_dir_text_reply_runs_pending_single_download_without_save_path(
with patch( with patch(
"app.chain.message.DirectoryHelper.get_download_dirs", "app.chain.message.DirectoryHelper.get_download_dirs",
return_value=_build_download_dirs(), return_value=_build_multiple_movie_download_dirs(),
), patch( ), patch(
"app.chain.message.DownloadChain.download_single", "app.chain.message.DownloadChain.download_single",
return_value="hash", return_value="hash",
@@ -515,7 +637,6 @@ def test_get_download_dirs_keeps_matching_tv_category_dir():
download_dirs = chain._get_download_dirs(context.media_info) download_dirs = chain._get_download_dirs(context.media_info)
assert [download_dir.name for download_dir in download_dirs] == [ assert [download_dir.name for download_dir in download_dirs] == [
"自动匹配目录",
"动画下载", "动画下载",
] ]
assert download_dirs[1].save_path == "rclone:/media/anime" assert download_dirs[0].save_path == "rclone:/media/anime"