From aeccf78957448f145897e4b88001625e01676f52 Mon Sep 17 00:00:00 2001 From: PKC278 <52959804+PKC278@users.noreply.github.com> Date: Fri, 9 Jan 2026 23:05:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(rousi):=20=E6=96=B0=E5=A2=9E=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E5=8F=82=E6=95=B0=E6=94=AF=E6=8C=81=E4=BB=A5=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/modules/indexer/__init__.py | 2 + app/modules/indexer/spider/rousi.py | 60 +++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/app/modules/indexer/__init__.py b/app/modules/indexer/__init__.py index ba1e5931..fb6e8a78 100644 --- a/app/modules/indexer/__init__.py +++ b/app/modules/indexer/__init__.py @@ -217,6 +217,7 @@ class IndexerModule(_ModuleBase): error_flag, result = RousiSpider(site).search( keyword=search_word, mtype=mtype, + cat=cat, page=page ) else: @@ -311,6 +312,7 @@ class IndexerModule(_ModuleBase): error_flag, result = await RousiSpider(site).async_search( keyword=search_word, mtype=mtype, + cat=cat, page=page ) else: diff --git a/app/modules/indexer/spider/rousi.py b/app/modules/indexer/spider/rousi.py index 7dab1fdf..c2d9509f 100644 --- a/app/modules/indexer/spider/rousi.py +++ b/app/modules/indexer/spider/rousi.py @@ -49,12 +49,13 @@ class RousiSpider(metaclass=SingletonClass): self._apikey = indexer.get('apikey') self._timeout = indexer.get('timeout') or 15 - def __get_params(self, keyword: str, mtype: MediaType = None, page: Optional[int] = 0) -> dict: + def __get_params(self, keyword: str, mtype: MediaType = None, cat: Optional[str] = None, page: Optional[int] = 0) -> dict: """ 构建 API 请求参数 :param keyword: 搜索关键词 :param mtype: 媒体类型 (MOVIE/TV) + :param cat: 用户选择的分类 ID(逗号分隔的字符串) :param page: 页码(从 0 开始,API 需要从 1 开始) :return: 请求参数字典 """ @@ -65,9 +66,20 @@ class RousiSpider(metaclass=SingletonClass): if keyword: params["keyword"] = keyword - # 注意:API 的 category 参数为字符串类型,仅支持单个分类 - # 这里优先选择主分类,如需多分类搜索需要分别请求 - if mtype: + # API 支持多分类搜索,需要使用数组格式:category[]=xxx&category[]=yyy + # 优先使用用户选择的分类,如果用户未选择则根据 mtype 推断 + if cat: + # 用户选择了特定分类,需要将分类 ID 映射回 API 的 category name + category_names = self.__get_category_names_by_ids(cat) + if category_names: + # 如果只有一个分类,使用 category=xxx + if len(category_names) == 1: + params["category"] = category_names[0] + else: + # 多个分类使用数组格式 category[]=xxx + params["category[]"] = category_names + elif mtype: + # 用户未选择分类,根据媒体类型推断 if mtype == MediaType.MOVIE: params["category"] = "movie" elif mtype == MediaType.TV: @@ -75,6 +87,36 @@ class RousiSpider(metaclass=SingletonClass): return params + def __get_category_names_by_ids(self, cat: str) -> Optional[list]: + """ + 根据用户选择的分类 ID 获取 API 的 category names + + :param cat: 用户选择的分类 ID(逗号分隔的多个ID,如 "1,2,3") + :return: API 的 category names 列表(如 ["movie", "tv", "documentary"]) + """ + if not cat: + return None + + # ID 到 category name 的映射 + id_to_name = { + '1': 'movie', + '2': 'tv', + '3': 'documentary', + '4': 'animation', + '5': 'variety', + '6': 'sports', + '7': 'music', + '8': 'software', + '9': 'ebook', + '10': 'other' + } + + # 分割多个分类 ID 并映射为 category names + cat_ids = [c.strip() for c in cat.split(',') if c.strip()] + category_names = [id_to_name.get(cat_id) for cat_id in cat_ids if cat_id in id_to_name] + + return category_names if category_names else None + def __parse_result(self, results: List[dict]) -> List[dict]: """ 解析搜索结果 @@ -140,12 +182,13 @@ class RousiSpider(metaclass=SingletonClass): torrents.append(torrent) return torrents - def search(self, keyword: str, mtype: MediaType = None, page: Optional[int] = 0) -> Tuple[bool, List[dict]]: + def search(self, keyword: str, mtype: MediaType = None, cat: Optional[str] = None, page: Optional[int] = 0) -> Tuple[bool, List[dict]]: """ 同步搜索种子 :param keyword: 搜索关键词 :param mtype: 媒体类型 (MOVIE/TV) + :param cat: 用户选择的分类 ID(逗号分隔) :param page: 页码(从 0 开始) :return: (是否发生错误, 种子列表) """ @@ -153,7 +196,7 @@ class RousiSpider(metaclass=SingletonClass): logger.warn(f"{self._name} 未配置 API Key (Passkey)") return True, [] - params = self.__get_params(keyword, mtype, page) + params = self.__get_params(keyword, mtype, cat, page) headers = { "Authorization": f"Bearer {self._apikey}", "Accept": "application/json" @@ -184,12 +227,13 @@ class RousiSpider(metaclass=SingletonClass): logger.warn(f"{self._name} 搜索失败,无法连接 {self._domain}") return True, [] - async def async_search(self, keyword: str, mtype: MediaType = None, page: Optional[int] = 0) -> Tuple[bool, List[dict]]: + async def async_search(self, keyword: str, mtype: MediaType = None, cat: Optional[str] = None, page: Optional[int] = 0) -> Tuple[bool, List[dict]]: """ 异步搜索种子 :param keyword: 搜索关键词 :param mtype: 媒体类型 (MOVIE/TV) + :param cat: 用户选择的分类 ID(逗号分隔) :param page: 页码(从 0 开始) :return: (是否发生错误, 种子列表) """ @@ -197,7 +241,7 @@ class RousiSpider(metaclass=SingletonClass): logger.warn(f"{self._name} 未配置 API Key (Passkey)") return True, [] - params = self.__get_params(keyword, mtype, page) + params = self.__get_params(keyword, mtype, cat, page) headers = { "Authorization": f"Bearer {self._apikey}", "Accept": "application/json"