diff --git a/app/chain/message.py b/app/chain/message.py index eb7a09e4..89409325 100644 --- a/app/chain/message.py +++ b/app/chain/message.py @@ -2635,6 +2635,8 @@ class MediaInteractionChain(ChainBase): download_dirs = self._get_download_dirs(media_info) if not download_dirs: 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.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 = [ DownloadDirectory( name=dir_info.name, @@ -3265,11 +3272,13 @@ class MediaInteractionChain(ChainBase): media_type=dir_info.media_type, media_category=dir_info.media_category, ) - for dir_info in DirectoryHelper().get_download_dirs() - if dir_info.download_path and cls._match_download_dir_media(dir_info, media_info) + for dir_info in dir_infos + if cls._match_download_dir_media(dir_info, media_info) ] if not download_dirs: return [] + if len(download_dirs) == 1: + return download_dirs return [cls._build_auto_download_dir(), *download_dirs] @classmethod diff --git a/tests/test_media_interaction.py b/tests/test_media_interaction.py index b98eea3f..24facbd8 100644 --- a/tests/test_media_interaction.py +++ b/tests/test_media_interaction.py @@ -65,7 +65,7 @@ def _build_tv_context(title: str = "葬送的芙莉莲") -> Context: def _build_download_dirs() -> list[TransferDirectoryConf]: - """构造消息交互可选择的下载目录配置。""" + """构造不同媒体类型各一个下载目录的配置。""" return [ TransferDirectoryConf( 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(): """已有传统媒体交互时,用户回复应优先交给传统交互处理。""" 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(): - """支持按钮的渠道选择资源后,应先发送下载目录按钮而不是立即下载。""" + """匹配当前媒体的目录有多个时,应先发送下载目录按钮而不是立即下载。""" chain = MediaInteractionChain() context = _build_context() request = media_interaction_manager.create_or_replace( @@ -313,7 +353,7 @@ def test_torrent_selection_prompts_download_dir_buttons_before_download(): with patch( "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( "app.chain.message.DownloadChain.download_single" ) as download_single: @@ -334,12 +374,93 @@ def test_torrent_selection_prompts_download_dir_buttons_before_download(): assert "请选择下载目录" in notification.title assert "1. 自动匹配目录" 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 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(): - """不支持按钮的渠道选择资源后,应提示用户回复数字选择下载目录。""" + """不支持按钮的渠道在多个匹配目录时,应提示用户回复数字选择下载目录。""" chain = MediaInteractionChain() context = _build_context() request = media_interaction_manager.create_or_replace( @@ -357,7 +478,7 @@ def test_torrent_selection_prompts_text_download_dir_for_plain_channel(): with patch( "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: handled = chain.handle_text_interaction( channel=MessageChannel.Wechat, @@ -374,6 +495,7 @@ def test_torrent_selection_prompts_text_download_dir_for_plain_channel(): assert notification.buttons is None assert "1. 自动匹配目录" in notification.text assert "2. 电影下载 (/downloads/movies)" in notification.text + assert "3. 4K电影下载 (/downloads/uhd-movies)" 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( "app.chain.message.DirectoryHelper.get_download_dirs", - return_value=_build_download_dirs(), + return_value=_build_multiple_movie_download_dirs(), ), patch( "app.chain.message.DownloadChain.download_single", return_value="hash", @@ -440,7 +562,7 @@ def test_download_dir_callback_runs_pending_single_download_with_save_path(): with patch( "app.chain.message.DirectoryHelper.get_download_dirs", - return_value=_build_download_dirs(), + return_value=_build_multiple_movie_download_dirs(), ), patch( "app.chain.message.DownloadChain.download_single", return_value="hash", @@ -482,7 +604,7 @@ def test_download_dir_text_reply_runs_pending_single_download_without_save_path( with patch( "app.chain.message.DirectoryHelper.get_download_dirs", - return_value=_build_download_dirs(), + return_value=_build_multiple_movie_download_dirs(), ), patch( "app.chain.message.DownloadChain.download_single", 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) 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"