From 1c60d8ccd747f4508f6788857080487c2155be87 Mon Sep 17 00:00:00 2001 From: ch3njun Date: Tue, 16 Jun 2026 06:35:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9E=81=E5=BD=B1=E8=A7=86?= =?UTF-8?q?=E5=AA=92=E4=BD=93=E5=BA=93=E5=90=8C=E6=AD=A5=E4=B8=8D=E5=85=A8?= =?UTF-8?q?=EF=BC=9A=E5=B1=95=E5=BC=80=20BoxSet=20=E5=90=88=E9=9B=86?= =?UTF-8?q?=E5=AD=90=E9=A1=B9=20(#5954)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/modules/zspace/zspace.py | 9 ++- tests/test_zspace_sync_items.py | 106 ++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/app/modules/zspace/zspace.py b/app/modules/zspace/zspace.py index ce8f9649..bf82c521 100644 --- a/app/modules/zspace/zspace.py +++ b/app/modules/zspace/zspace.py @@ -848,7 +848,14 @@ class ZSpace: result = res.json() or {} items = result.get("Items") or [] for item in items: - if not item or item.get("Type") not in ["Movie", "Series"]: + if not item: + continue + if item.get("Type") == "BoxSet" and item.get("Id"): + for sub_item in self.get_items(parent=item.get("Id")): + if sub_item: + yield sub_item + continue + if item.get("Type") not in ["Movie", "Series"]: continue provider_ids = item.get("ProviderIds") or {} needs_detail = ( diff --git a/tests/test_zspace_sync_items.py b/tests/test_zspace_sync_items.py index f72baabd..996c8fbe 100644 --- a/tests/test_zspace_sync_items.py +++ b/tests/test_zspace_sync_items.py @@ -25,7 +25,7 @@ def _build_client() -> ZSpace: def test_get_items_fetches_all_recursive_pages() -> None: - """全量同步应递归查询并持续翻页,且忽略合集外壳。""" + """全量同步应递归查询并持续翻页,且忽略非媒体条目。""" client = _build_client() responses = [ _FakeResponse({ @@ -39,9 +39,9 @@ def test_get_items_fetches_all_recursive_pages() -> None: "Path": "/media/movie-1.mkv", }, { - "Id": "collection-1", - "Type": "BoxSet", - "Name": "电影合集", + "Id": "audio-1", + "Type": "Audio", + "Name": "音频一", }, ], "TotalRecordCount": 3, @@ -76,6 +76,104 @@ def test_get_items_fetches_all_recursive_pages() -> None: assert calls[1].kwargs["params"]["Limit"] == 2 +def test_get_items_expands_boxset_movies() -> None: + """电影合集应向下查询并返回全部子电影。""" + client = _build_client() + responses = [ + _FakeResponse({ + "Items": [{ + "Id": "collection-1", + "Type": "BoxSet", + "Name": "疯狂动物城(系列)", + }], + "TotalRecordCount": 1, + }), + _FakeResponse({ + "Items": [ + { + "Id": "movie-1", + "Type": "Movie", + "Name": "疯狂动物城", + "ProductionYear": None, + "ProviderIds": {}, + "Path": "", + }, + { + "Id": "movie-2", + "Type": "Movie", + "Name": "疯狂动物城2", + "ProductionYear": 2025, + "ProviderIds": {"Tmdb": "1084242"}, + "Path": "/media/疯狂动物城2.mkv", + }, + ], + "TotalRecordCount": 2, + }), + _FakeResponse({ + "Id": "movie-1", + "ParentId": "collection-1", + "Type": "Movie", + "Name": "疯狂动物城", + "ProductionYear": 2016, + "ProviderIds": {"Tmdb": "269149"}, + "Path": "/media/疯狂动物城.mkv", + }), + ] + + with patch("app.modules.zspace.zspace.RequestUtils") as request_utils_cls: + request_utils_cls.return_value.get_res.side_effect = responses + items = list(client.get_items(parent="library-id")) + + assert [item.item_id for item in items] == ["movie-1", "movie-2"] + assert [item.tmdbid for item in items] == [269149, 1084242] + calls = request_utils_cls.return_value.get_res.call_args_list + assert calls[0].kwargs["params"]["Recursive"] == "true" + assert calls[1].kwargs["params"]["ParentId"] == "collection-1" + + +def test_get_items_expands_boxset_series() -> None: + """电视剧合集应向下展开并返回全部子 Series。""" + client = _build_client() + responses = [ + _FakeResponse({ + "Items": [{ + "Id": "collection-1", + "Type": "BoxSet", + "Name": "绝命毒师", + }], + "TotalRecordCount": 1, + }), + _FakeResponse({ + "Items": [ + { + "Id": "series-1", + "Type": "Series", + "Name": "绝命毒师 第 1 季", + "ProductionYear": 2008, + "ProviderIds": {"Tmdb": "1396"}, + "Path": "/media/绝命毒师/Season 1", + }, + { + "Id": "series-2", + "Type": "Series", + "Name": "绝命毒师 第 2 季", + "ProductionYear": 2009, + "ProviderIds": {"Tmdb": "1396"}, + "Path": "/media/绝命毒师/Season 2", + }, + ], + "TotalRecordCount": 2, + }), + ] + + with patch("app.modules.zspace.zspace.RequestUtils") as request_utils_cls: + request_utils_cls.return_value.get_res.side_effect = responses + items = list(client.get_items(parent="library-id")) + + assert [item.item_id for item in items] == ["series-1", "series-2"] + assert [item.tmdbid for item in items] == [1396, 1396] + + def test_get_items_loads_detail_when_list_metadata_is_incomplete() -> None: """列表项缺少关键元数据时应使用详情接口补全。""" client = _build_client()