diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index aa100736..2a13680c 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -76,16 +76,17 @@ class SubscribeChain(ChainBase): return normalized @classmethod - def __get_episode_priority(cls, subscribe: Subscribe) -> Dict[str, int]: + def __get_episode_priority(cls, subscribe: Subscribe, + total_episode: Optional[int] = None) -> Dict[str, int]: """ 获取订阅按集洗版优先级状态。 """ - episode_priority = cls.__normalize_episode_priority(getattr(subscribe, "episode_priority", None)) + episode_priority = cls.__normalize_episode_priority(subscribe.episode_priority) if episode_priority: return episode_priority if subscribe.best_version and subscribe.type == MediaType.TV.value and subscribe.current_priority is not None: - target_episodes = cls.__get_best_version_target_episodes(subscribe) + target_episodes = cls.__get_best_version_target_episodes(subscribe, total_episode=total_episode) return { str(episode): int(subscribe.current_priority) for episode in target_episodes @@ -100,7 +101,8 @@ class SubscribeChain(ChainBase): return cls.__get_episode_priority(subscribe) @classmethod - def __get_best_version_target_episodes(cls, subscribe: Subscribe) -> List[int]: + def __get_best_version_target_episodes(cls, subscribe: Subscribe, + total_episode: Optional[int] = None) -> List[int]: """ 获取洗版订阅目标剧集范围。 """ @@ -108,36 +110,75 @@ class SubscribeChain(ChainBase): return [] start_episode = subscribe.start_episode or 1 - total_episode = subscribe.total_episode or 0 + total_episode = total_episode or subscribe.total_episode or 0 if total_episode < start_episode: return [] return list(range(start_episode, total_episode + 1)) + @classmethod + def __get_downloaded_best_version_episodes(cls, subscribe: Subscribe, + total_episode: Optional[int] = None) -> List[int]: + """ + 获取洗版订阅目标范围内已下载到任意版本的剧集。 + + 分集洗版的完成态要求 priority==100,但订阅目标满足查询有时只需要确认 + 目标集是否已下载过任意版本,因此这里按 note 与 episode_priority>0 统计。 + """ + if subscribe.type != MediaType.TV.value: + return [] + + start_episode = subscribe.start_episode or 1 + total_episode = total_episode or subscribe.total_episode or 0 + if total_episode < start_episode: + return [] + target_episodes = set(range(start_episode, total_episode + 1)) + downloaded = set() + for episode in subscribe.note or []: + try: + episode_number = int(episode) + except (TypeError, ValueError): + continue + if episode_number in target_episodes: + downloaded.add(episode_number) + for episode, priority in (subscribe.episode_priority or {}).items(): + if not str(episode).isdigit(): + continue + try: + if float(priority) > 0: + episode_number = int(episode) + if episode_number in target_episodes: + downloaded.add(episode_number) + except (TypeError, ValueError): + continue + return sorted(downloaded) + @classmethod def __get_pending_best_version_episodes_with_priority( cls, subscribe: Subscribe, episode_priority: Optional[dict] = None, + total_episode: Optional[int] = None, ) -> List[int]: """ 使用指定按集优先级状态获取当前仍需继续洗版的剧集。 """ - target_episodes = cls.__get_best_version_target_episodes(subscribe) + target_episodes = cls.__get_best_version_target_episodes(subscribe, total_episode=total_episode) if not target_episodes: return [] if episode_priority is None: - normalized = cls.__get_episode_priority(subscribe) + normalized = cls.__get_episode_priority(subscribe, total_episode=total_episode) else: normalized = cls.__normalize_episode_priority(episode_priority) return [episode for episode in target_episodes if normalized.get(str(episode)) != 100] @classmethod - def _get_pending_best_version_episodes(cls, subscribe: Subscribe) -> List[int]: + def _get_pending_best_version_episodes(cls, subscribe: Subscribe, + total_episode: Optional[int] = None) -> List[int]: """ 获取当前仍需继续洗版的剧集。 """ - return cls.__get_pending_best_version_episodes_with_priority(subscribe) + return cls.__get_pending_best_version_episodes_with_priority(subscribe, total_episode=total_episode) @classmethod def compute_completed_episode(cls, subscribe: Subscribe) -> Optional[int]: @@ -324,7 +365,7 @@ class SubscribeChain(ChainBase): 判断当前订阅是否启用了电视剧全集洗版。 """ return ( - bool(getattr(subscribe, "best_version_full", 0)) + bool(subscribe.best_version_full) and bool(subscribe.best_version) and subscribe.type == MediaType.TV.value ) @@ -2813,7 +2854,8 @@ class SubscribeChain(ChainBase): season=begin_season, episodes=episodes, total_episode=total_episode, - start_episode=start_episode + start_episode=start_episode, + require_complete_coverage=no_exist_season.require_complete_coverage ) # 根据订阅已下载集数更新缺失集数 if downloaded_episodes: @@ -2841,6 +2883,7 @@ class SubscribeChain(ChainBase): episodes=episodes, total_episode=total, start_episode=start, + require_complete_coverage=no_exist_season.require_complete_coverage ) else: # 开始集数 @@ -2855,6 +2898,7 @@ class SubscribeChain(ChainBase): episodes=episodes, total_episode=total_episode, start_episode=start, + require_complete_coverage=False, ) logger.info(f'订阅 {subscribe_name} 缺失剧集数更新为:{no_exists}') return False, no_exists @@ -3070,72 +3114,12 @@ class SubscribeChain(ChainBase): """ self.__refresh_total_episode_before_completion(subscribe=subscribe, mediainfo=mediainfo) - # 非洗版 - if not subscribe.best_version: - # 每季总集数 - totals = {} - if subscribe.season and subscribe.total_episode: - totals = { - subscribe.season: subscribe.total_episode - } - # 查询媒体库缺失的媒体信息 - exist_flag, no_exists = DownloadChain().get_no_exists_info( - meta=meta, - mediainfo=mediainfo, - totals=totals - ) - else: - # 洗版,如果已经满足了优先级,则认为已经洗版完成 - if self.__is_best_version_complete(subscribe): - exist_flag = True - no_exists = {} - else: - exist_flag = False - if meta.type == MediaType.TV: - pending_episodes = [] if self.__is_full_best_version_enabled( - subscribe - ) else self._get_pending_best_version_episodes(subscribe) - # 对于电视剧,构造缺失的媒体信息 - no_exists = { - mediakey: { - subscribe.season: schemas.NotExistMediaInfo( - season=subscribe.season, - episodes=pending_episodes, - total_episode=subscribe.total_episode, - start_episode=subscribe.start_episode or 1, - # 完整覆盖约束会影响整季文件探测、显式集列表匹配和多集拆包降级。 - require_complete_coverage=self.__is_full_best_version_enabled(subscribe)) - } - } - else: - no_exists = {} - - # 如果媒体已存在,执行订阅完成操作 - if exist_flag: - if not subscribe.best_version: - logger.info(f'{mediainfo.title_year} 媒体库中已存在') - self.finish_subscribe_or_not(subscribe=subscribe, meta=meta, mediainfo=mediainfo, force=True) - return True, no_exists - - # 获取已下载的集数或电影 - downloaded = self.__get_downloaded(subscribe) - if self.__is_full_best_version_enabled(subscribe): - # 全集洗版必须保留整季缺失范围,避免下载链路从整包中拆选单集。 - downloaded = [] - if meta.type == MediaType.TV: - # 对于电视剧类型,整合缺失集数并剔除已下载的集数 - exist_flag, no_exists = self.__get_subscribe_no_exits( - subscribe_name=f'{subscribe.name} {meta.season}', - no_exists=no_exists, - mediakey=mediakey, - begin_season=meta.begin_season, - total_episode=subscribe.total_episode, - start_episode=subscribe.start_episode, - downloaded_episodes=downloaded - ) - elif meta.type == MediaType.MOVIE: - # 对于电影类型,直接根据是否已下载判断 - exist_flag = bool(downloaded) + exist_flag, no_exists = self.resolve_subscribe_missing( + subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + mediakey=mediakey, + ) # 如果已下载完毕,执行订阅完成操作 if exist_flag: @@ -3146,6 +3130,113 @@ class SubscribeChain(ChainBase): # 返回结果,表示媒体未完全下载或存在 return False, no_exists + def resolve_subscribe_missing(self, subscribe: Subscribe, meta: MetaBase, + mediainfo: MediaInfo, + mediakey: Optional[Union[str, int]] = None, + best_version_accept_downloaded: bool = False): + """ + 按主程序订阅口径查询当前目标是否仍有缺失,不推进订阅状态。 + + 该方法只组合媒体库缺集、订阅范围、下载历史和洗版优先级,用于外部策略在 + 完成前复用主程序"还要不要搜索/下载"的判断口径。它不得完成订阅、写入 + lack_episode、发送事件或修改数据库。 + + best_version_accept_downloaded 仅用于分集洗版的外部完成守卫:为 True 时, + priority>0 的目标集视为已满足;默认 False 保持主程序洗版完成需 priority==100 + 的搜索/完成口径。 + """ + mediakey = mediakey or subscribe.tmdbid or subscribe.doubanid + effective_total_episode = self.__resolve_effective_total_episode(subscribe, mediainfo) + + if not subscribe.best_version: + totals = {} + if subscribe.season and effective_total_episode: + totals = { + subscribe.season: effective_total_episode + } + exist_flag, no_exists = DownloadChain().get_no_exists_info( + meta=meta, + mediainfo=mediainfo, + totals=totals + ) + elif meta.type != MediaType.TV and self.__is_best_version_complete(subscribe): + return True, {} + else: + exist_flag = False + if meta.type == MediaType.TV: + if self.__is_full_best_version_enabled(subscribe): + pending_episodes = [] + elif best_version_accept_downloaded: + downloaded = set(self.__get_downloaded_best_version_episodes( + subscribe, total_episode=effective_total_episode + )) + start_episode = subscribe.start_episode or 1 + pending_episodes = [ + episode for episode in range(start_episode, effective_total_episode + 1) + if episode not in downloaded + ] + if not pending_episodes: + return True, {} + else: + pending_episodes = self._get_pending_best_version_episodes( + subscribe, total_episode=effective_total_episode + ) + if not pending_episodes: + return True, {} + no_exists = { + mediakey: { + subscribe.season: schemas.NotExistMediaInfo( + season=subscribe.season, + episodes=pending_episodes, + total_episode=effective_total_episode, + start_episode=subscribe.start_episode or 1, + require_complete_coverage=self.__is_full_best_version_enabled(subscribe)) + } + } + else: + no_exists = {} + + if exist_flag: + return True, no_exists + + downloaded = self.__get_downloaded(subscribe) + if self.__is_full_best_version_enabled(subscribe): + downloaded = [] + if meta.type == MediaType.TV: + return self.__get_subscribe_no_exits( + subscribe_name=f'{subscribe.name} {meta.season}', + no_exists=no_exists, + mediakey=mediakey, + begin_season=meta.begin_season, + total_episode=effective_total_episode, + start_episode=subscribe.start_episode, + downloaded_episodes=downloaded + ) + if meta.type == MediaType.MOVIE: + return bool(downloaded), no_exists + return False, no_exists + + @staticmethod + def __resolve_effective_total_episode(subscribe: Subscribe, mediainfo: MediaInfo) -> int: + """ + 只读计算完成前有效总集数,不触发事件、不写回订阅。 + + 主流程会通过 ``__refresh_total_episode_before_completion`` 持久化增长后的总集数; + 该查询接口只需要同样避免旧 total 造成误判,因此仅使用当前 mediainfo 中更大的 + 季集数作为临时目标范围。 + """ + current_total = subscribe.total_episode or 0 + if subscribe.type != MediaType.TV.value: + return current_total + if subscribe.manual_total_episode: + return current_total + if subscribe.season is None: + return current_total + media_total = len((mediainfo.seasons or {}).get(subscribe.season) or []) + if media_total > current_total: + return media_total + return current_total + @staticmethod def __apply_episodes_refresh(current_total: int, season: Optional[int], *, mediainfo: Optional[MediaInfo] = None, diff --git a/tests/test_subscribe_chain.py b/tests/test_subscribe_chain.py index bbd5c6f0..1af06f12 100644 --- a/tests/test_subscribe_chain.py +++ b/tests/test_subscribe_chain.py @@ -126,11 +126,13 @@ def _load_subscribe_chain_class(): setattr(self, key, value) class _NotExistMediaInfo: - def __init__(self, season=None, episodes=None, total_episode=None, start_episode=None): + def __init__(self, season=None, episodes=None, total_episode=None, start_episode=None, + require_complete_coverage=False): self.season = season self.episodes = episodes or [] self.total_episode = total_episode self.start_episode = start_episode + self.require_complete_coverage = require_complete_coverage class _SubscribeEpisodeInfo: def __init__(self): @@ -461,7 +463,13 @@ class SubscribeChainTest(TestCase): """自定义开始集跳过季初集数时,缺失整季需要转成显式目标集。""" no_exists = { "media-key": { - 1: SimpleNamespace(season=1, episodes=[], total_episode=48, start_episode=1) + 1: SimpleNamespace( + season=1, + episodes=[], + total_episode=48, + start_episode=1, + require_complete_coverage=False, + ) } } @@ -483,7 +491,13 @@ class SubscribeChainTest(TestCase): """自定义开始集没有缩小范围时,仍保留空集列表表示整季缺失。""" no_exists = { "media-key": { - 1: SimpleNamespace(season=1, episodes=[], total_episode=48, start_episode=1) + 1: SimpleNamespace( + season=1, + episodes=[], + total_episode=48, + start_episode=1, + require_complete_coverage=False, + ) } } @@ -501,6 +515,331 @@ class SubscribeChainTest(TestCase): self.assertEqual(result["media-key"][1].start_episode, 1) self.assertEqual(result["media-key"][1].total_episode, 48) + def test_resolve_subscribe_missing_combines_library_gap_and_download_history_without_side_effects(self): + """目标满足查询应复用主程序媒体库缺集与订阅下载历史的合并口径,且不推进订阅状态。""" + subscribe = self._build_subscribe( + best_version=0, + total_episode=20, + lack_episode=10, + note=list(range(11, 21)), + ) + meta = SimpleNamespace(type=MediaType.TV, begin_season=1, season=1) + mediainfo = SimpleNamespace( + type=MediaType.TV, + seasons={1: list(range(1, 21))}, + title_year="Test Show (2026)", + ) + library_missing = { + 1: { + 1: SimpleNamespace( + season=1, + episodes=list(range(11, 21)), + total_episode=20, + start_episode=11, + require_complete_coverage=False, + ) + } + } + updates = [] + + class _DownloadChain: + def get_no_exists_info(self, **kwargs): + self.kwargs = kwargs + return False, library_missing + + class _SubscribeOper: + def update(self, subscribe_id, payload): + updates.append((subscribe_id, payload)) + + chain = SubscribeChain() + chain.finish_subscribe_or_not = lambda **_kwargs: self.fail("resolve_subscribe_missing must not finish") + + with patch.object(SUBSCRIBE_CHAIN_MODULE, "DownloadChain", _DownloadChain), patch.object( + SUBSCRIBE_CHAIN_MODULE, + "SubscribeOper", + _SubscribeOper, + ): + satisfied, no_exists = chain.resolve_subscribe_missing( + subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + mediakey=1, + ) + + self.assertTrue(satisfied) + self.assertEqual(no_exists, {}) + self.assertEqual(updates, []) + + def test_resolve_subscribe_missing_keeps_library_gap_when_download_history_does_not_cover_it(self): + """订阅前媒体库已有部分剧集时,目标满足查询应保留仍需下载的媒体库缺口。""" + subscribe = self._build_subscribe( + best_version=0, + total_episode=20, + lack_episode=20, + note=[], + ) + meta = SimpleNamespace(type=MediaType.TV, begin_season=1, season=1) + mediainfo = SimpleNamespace( + type=MediaType.TV, + seasons={1: list(range(1, 21))}, + title_year="Test Show (2026)", + ) + library_missing = { + 1: { + 1: SimpleNamespace( + season=1, + episodes=list(range(11, 21)), + total_episode=20, + start_episode=11, + require_complete_coverage=False, + ) + } + } + + class _DownloadChain: + def get_no_exists_info(self, **_kwargs): + return False, library_missing + + with patch.object(SUBSCRIBE_CHAIN_MODULE, "DownloadChain", _DownloadChain): + satisfied, no_exists = SubscribeChain().resolve_subscribe_missing( + subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + mediakey=1, + ) + + self.assertFalse(satisfied) + self.assertEqual(no_exists[1][1].episodes, list(range(11, 21))) + self.assertEqual(no_exists[1][1].start_episode, 1) + self.assertEqual(no_exists[1][1].total_episode, 20) + + def test_resolve_subscribe_missing_uses_readonly_effective_total_from_mediainfo(self): + """只读目标查询应使用最新媒体信息扩大有效总集数,但不能写回订阅或发送刷新事件。""" + subscribe = self._build_subscribe( + best_version=0, + total_episode=10, + lack_episode=0, + note=list(range(1, 11)), + ) + meta = SimpleNamespace(type=MediaType.TV, begin_season=1, season=1) + mediainfo = SimpleNamespace( + type=MediaType.TV, + seasons={1: list(range(1, 21))}, + title_year="Test Show (2026)", + ) + captured_totals = [] + + class _DownloadChain: + def get_no_exists_info(self, **kwargs): + captured_totals.append(kwargs["totals"]) + return False, { + 1: { + 1: SimpleNamespace( + season=1, + episodes=list(range(11, 21)), + total_episode=20, + start_episode=11, + require_complete_coverage=False, + ) + } + } + + class _EventManager: + def send_event(self, *_args, **_kwargs): + raise AssertionError("resolve_subscribe_missing must not send refresh events") + + with patch.object(SUBSCRIBE_CHAIN_MODULE, "DownloadChain", _DownloadChain), patch.object( + SUBSCRIBE_CHAIN_MODULE, + "eventmanager", + _EventManager(), + ): + satisfied, no_exists = SubscribeChain().resolve_subscribe_missing( + subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + mediakey=1, + ) + + self.assertFalse(satisfied) + self.assertEqual(captured_totals, [{1: 20}]) + self.assertEqual(no_exists[1][1].episodes, list(range(11, 21))) + self.assertEqual(subscribe.total_episode, 10) + self.assertEqual(subscribe.lack_episode, 0) + self.assertEqual(subscribe.note, list(range(1, 11))) + + def test_resolve_subscribe_missing_accepts_downloaded_episode_best_version_targets(self): + """外部完成守卫可按任意已下载版本判定分集洗版目标已满足。""" + subscribe = self._build_subscribe( + best_version=1, + best_version_full=0, + total_episode=3, + note=[1], + episode_priority={"2": 80, "3": 99}, + ) + meta = SimpleNamespace(type=MediaType.TV, begin_season=1, season=1) + mediainfo = SimpleNamespace( + type=MediaType.TV, + seasons={1: [1, 2, 3]}, + title_year="Test Show (2026)", + ) + + satisfied, no_exists = SubscribeChain().resolve_subscribe_missing( + subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + mediakey=1, + best_version_accept_downloaded=True, + ) + + self.assertTrue(satisfied) + self.assertEqual(no_exists, {}) + + def test_resolve_subscribe_missing_default_best_version_requires_top_priority(self): + """主程序洗版完成口径默认仍要求目标分集达到最高优先级。""" + subscribe = self._build_subscribe( + best_version=1, + best_version_full=0, + total_episode=3, + note=[1], + episode_priority={"2": 80, "3": 99}, + ) + meta = SimpleNamespace(type=MediaType.TV, begin_season=1, season=1) + mediainfo = SimpleNamespace( + type=MediaType.TV, + seasons={1: [1, 2, 3]}, + title_year="Test Show (2026)", + ) + + satisfied, no_exists = SubscribeChain().resolve_subscribe_missing( + subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + mediakey=1, + ) + + self.assertFalse(satisfied) + self.assertEqual(no_exists[1][1].episodes, [1, 2, 3]) + self.assertEqual(no_exists[1][1].total_episode, 3) + + def test_resolve_subscribe_missing_default_best_version_uses_readonly_effective_total(self): + """只读目标查询扩大有效总集数时,默认洗版口径应把新增集纳入待洗范围。""" + subscribe = self._build_subscribe( + best_version=1, + best_version_full=0, + total_episode=3, + episode_priority={"1": 100, "2": 100, "3": 100}, + ) + meta = SimpleNamespace(type=MediaType.TV, begin_season=1, season=1) + mediainfo = SimpleNamespace( + type=MediaType.TV, + seasons={1: [1, 2, 3, 4, 5]}, + title_year="Test Show (2026)", + ) + + satisfied, no_exists = SubscribeChain().resolve_subscribe_missing( + subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + mediakey=1, + ) + + self.assertFalse(satisfied) + self.assertEqual(no_exists[1][1].episodes, [4, 5]) + self.assertEqual(no_exists[1][1].total_episode, 5) + self.assertEqual(subscribe.total_episode, 3) + + def test_resolve_subscribe_missing_accept_downloaded_keeps_best_version_gap(self): + """任意版本满足口径仍应保留从未下载过的目标分集。""" + subscribe = self._build_subscribe( + best_version=1, + best_version_full=0, + total_episode=3, + note=[1], + episode_priority={"2": 80}, + ) + meta = SimpleNamespace(type=MediaType.TV, begin_season=1, season=1) + mediainfo = SimpleNamespace( + type=MediaType.TV, + seasons={1: [1, 2, 3]}, + title_year="Test Show (2026)", + ) + + satisfied, no_exists = SubscribeChain().resolve_subscribe_missing( + subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + mediakey=1, + best_version_accept_downloaded=True, + ) + + self.assertFalse(satisfied) + self.assertEqual(no_exists[1][1].episodes, [3]) + self.assertEqual(no_exists[1][1].total_episode, 3) + + def test_get_subscribe_no_exists_preserves_complete_coverage_requirement(self): + """缺集裁剪重建 NotExistMediaInfo 时必须保留全集洗版完整覆盖约束。""" + no_exists = { + "media-key": { + 1: SimpleNamespace( + season=1, + episodes=list(range(1, 13)), + total_episode=12, + start_episode=1, + require_complete_coverage=True, + ) + } + } + + exist_flag, result = SubscribeChain._SubscribeChain__get_subscribe_no_exits( + subscribe_name="主角 S01", + no_exists=no_exists, + mediakey="media-key", + begin_season=1, + total_episode=12, + start_episode=1, + downloaded_episodes=[1, 2, 3], + ) + + self.assertFalse(exist_flag) + self.assertTrue(result["media-key"][1].require_complete_coverage) + self.assertEqual(result["media-key"][1].episodes, list(range(4, 13))) + + def test_check_existing_media_refreshes_total_before_resolving_missing(self): + """主流程应先执行完成前总集数刷新,再复用无副作用缺集查询口径。""" + subscribe = self._build_subscribe(best_version=0, total_episode=10, lack_episode=0) + meta = SimpleNamespace(type=MediaType.TV, begin_season=1, season=1) + mediainfo = SimpleNamespace(type=MediaType.TV, title_year="Test Show (2026)") + calls = [] + + def fake_refresh(_self, subscribe, mediainfo): + calls.append(("refresh", subscribe.total_episode)) + subscribe.total_episode = 20 + + def fake_resolve(_self, subscribe, meta, mediainfo, mediakey=None): + calls.append(("resolve", subscribe.total_episode)) + return False, {"media-key": {1: SimpleNamespace(episodes=[11], total_episode=20, start_episode=1)}} + + chain = SubscribeChain() + with patch.object( + SubscribeChain, + "_SubscribeChain__refresh_total_episode_before_completion", + fake_refresh, + ), patch.object( + SubscribeChain, + "resolve_subscribe_missing", + fake_resolve, + ): + exist_flag, no_exists = chain.check_and_handle_existing_media( + subscribe=subscribe, + meta=meta, + mediainfo=mediainfo, + mediakey="media-key", + ) + + self.assertFalse(exist_flag) + self.assertEqual(calls, [("refresh", 10), ("resolve", 20)]) + self.assertEqual(no_exists["media-key"][1].episodes, [11]) + def test_best_version_full_pack_first_keeps_whole_missing_for_custom_start_episode(self): """分集洗版优先全集时,空集列表仍表示下载链按整季资源处理。""" subscribe = self._build_subscribe( @@ -588,7 +927,13 @@ class SubscribeChainTest(TestCase): ) no_exists = { "media-key": { - 1: SimpleNamespace(season=1, episodes=[2], total_episode=3, start_episode=1) + 1: SimpleNamespace( + season=1, + episodes=[2], + total_episode=3, + start_episode=1, + require_complete_coverage=False, + ) } } calls = [] @@ -632,7 +977,13 @@ class SubscribeChainTest(TestCase): ) no_exists = { "media-key": { - 1: SimpleNamespace(season=1, episodes=[2], total_episode=3, start_episode=1) + 1: SimpleNamespace( + season=1, + episodes=[2], + total_episode=3, + start_episode=1, + require_complete_coverage=False, + ) } } calls = [] @@ -680,7 +1031,13 @@ class SubscribeChainTest(TestCase): ) no_exists = { "media-key": { - 1: SimpleNamespace(season=1, episodes=[2], total_episode=3, start_episode=1) + 1: SimpleNamespace( + season=1, + episodes=[2], + total_episode=3, + start_episode=1, + require_complete_coverage=False, + ) } } calls = [] @@ -721,7 +1078,13 @@ class SubscribeChainTest(TestCase): ) no_exists = { "media-key": { - 1: SimpleNamespace(season=1, episodes=[2], total_episode=3, start_episode=1) + 1: SimpleNamespace( + season=1, + episodes=[2], + total_episode=3, + start_episode=1, + require_complete_coverage=False, + ) } } calls = []