From b41de1a9825e0b4288480e1f5d5c4b7a45e95673 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 19 Feb 2025 17:44:14 +0800 Subject: [PATCH] fix actions --- app/actions/__init__.py | 9 +++++ app/actions/fetch_downloads.py | 7 +--- app/actions/fetch_rss.py | 15 +++----- app/actions/fetch_torrents.py | 69 ++++++++++++++++++++++++++++++++++ app/actions/filter_torrents.py | 42 +++++++++++++++++++-- app/actions/search_torrents.py | 38 ------------------- app/actions/send_message.py | 26 +++++++++++-- app/api/endpoints/search.py | 4 +- app/chain/search.py | 15 +++++--- app/helper/rule.py | 6 +-- app/helper/torrent.py | 20 ++++++++++ 11 files changed, 179 insertions(+), 72 deletions(-) create mode 100644 app/actions/fetch_torrents.py delete mode 100644 app/actions/search_torrents.py diff --git a/app/actions/__init__.py b/app/actions/__init__.py index 8bea4aab..0444c90d 100644 --- a/app/actions/__init__.py +++ b/app/actions/__init__.py @@ -2,9 +2,14 @@ from abc import ABC, abstractmethod from pydantic.main import BaseModel +from app.chain import ChainBase from app.schemas import ActionContext, ActionParams +class ActionChain(ChainBase): + pass + + class BaseAction(BaseModel, ABC): """ 工作流动作基类 @@ -13,6 +18,10 @@ class BaseAction(BaseModel, ABC): # 完成标志 _done_flag = False + def __init__(self): + super().__init__() + self.chain = ActionChain() + @property @abstractmethod def name(self) -> str: diff --git a/app/actions/fetch_downloads.py b/app/actions/fetch_downloads.py index 491fc6ba..9aa7ed07 100644 --- a/app/actions/fetch_downloads.py +++ b/app/actions/fetch_downloads.py @@ -1,5 +1,4 @@ from app.actions import BaseAction -from app.chain.download import DownloadChain from app.schemas import ActionParams, ActionContext @@ -17,10 +16,6 @@ class FetchDownloadsAction(BaseAction): _downloads = [] - def __init__(self): - super().__init__() - self.downloadchain = DownloadChain() - @property def name(self) -> str: return "获取下载任务" @@ -41,7 +36,7 @@ class FetchDownloadsAction(BaseAction): """ self._downloads = context.downloads for download in self._downloads: - torrents = self.downloadchain.list_torrents(hashs=[download.download_id]) + torrents = self.chain.list_torrents(hashs=[download.download_id]) if not torrents: download.completed = True continue diff --git a/app/actions/fetch_rss.py b/app/actions/fetch_rss.py index 9ea9eca4..5d14673e 100644 --- a/app/actions/fetch_rss.py +++ b/app/actions/fetch_rss.py @@ -3,7 +3,6 @@ from typing import Optional from pydantic import Field from app.actions import BaseAction -from app.chain.media import MediaChain from app.core.config import settings from app.core.context import Context from app.core.metainfo import MetaInfo @@ -22,7 +21,6 @@ class FetchRssParams(ActionParams): content_type: Optional[str] = Field(None, description="Content-Type") referer: Optional[str] = Field(None, description="Referer") ua: Optional[str] = Field(None, description="User-Agent") - recognize: Optional[bool] = Field(False, description="是否识别") class FetchRssAction(BaseAction): @@ -35,7 +33,6 @@ class FetchRssAction(BaseAction): def __init__(self): super().__init__() self.rsshelper = RssHelper() - self.mediachain = MediaChain() @property def name(self) -> str: @@ -83,13 +80,11 @@ class FetchRssAction(BaseAction): size=item.get("size"), pubdate=item["pubdate"].strftime("%Y-%m-%d %H:%M:%S") if item.get("pubdate") else None, ) - meta, mediainfo = None, None - if params.recognize: - meta = MetaInfo(title=torrentinfo.title, subtitle=torrentinfo.description) - mediainfo = self.mediachain.recognize_media(meta) - if not mediainfo: - logger.warning(f"{torrentinfo.title} 未识别到媒体信息") - continue + meta = MetaInfo(title=torrentinfo.title, subtitle=torrentinfo.description) + mediainfo = self.chain.recognize_media(meta) + if not mediainfo: + logger.warning(f"{torrentinfo.title} 未识别到媒体信息") + continue self._rss_torrents.append(Context(meta_info=meta, media_info=mediainfo, torrent_info=torrentinfo)) if self._rss_torrents: diff --git a/app/actions/fetch_torrents.py b/app/actions/fetch_torrents.py new file mode 100644 index 00000000..3292f2bc --- /dev/null +++ b/app/actions/fetch_torrents.py @@ -0,0 +1,69 @@ +from typing import Optional, List + +from pydantic import Field + +from app.actions import BaseAction +from app.chain.search import SearchChain +from app.log import logger +from app.schemas import ActionParams, ActionContext, MediaType + + +class FetchTorrentsParams(ActionParams): + """ + 获取站点资源参数 + """ + name: str = Field(None, description="资源名称") + year: Optional[int] = Field(None, description="年份") + type: Optional[str] = Field(None, description="资源类型 (电影/电视剧)") + season: Optional[int] = Field(None, description="季度") + sites: Optional[List[int]] = Field([], description="站点列表") + + +class FetchTorrentsAction(BaseAction): + """ + 搜索站点资源 + """ + + _torrents = [] + + def __init__(self): + super().__init__() + self.searchchain = SearchChain() + + @property + def name(self) -> str: + return "获取站点资源" + + @property + def description(self) -> str: + return "根据关键字搜索站点种子资源" + + @property + def success(self) -> bool: + return True if self._torrents else False + + async def execute(self, params: FetchTorrentsParams, context: ActionContext) -> ActionContext: + """ + 搜索站点,获取资源列表 + """ + torrents = self.searchchain.search_by_title(title=params.name, sites=params.sites) + for torrent in torrents: + if params.year and torrent.meta_info.year != params.year: + continue + if params.type and torrent.media_info and torrent.media_info.type != MediaType(params.type): + continue + if params.season and torrent.meta_info.begin_season != params.season: + continue + # 识别媒体信息 + torrent.media_info = self.chain.recognize_media(torrent.meta_info) + if not torrent.media_info: + logger.warning(f"{torrent.torrent_info.title} 未识别到媒体信息") + continue + self._torrents.append(torrent) + + if self._torrents: + context.torrents.extend(self._torrents) + logger.info(f"搜索到 {len(self._torrents)} 条资源") + + self.job_done() + return context diff --git a/app/actions/filter_torrents.py b/app/actions/filter_torrents.py index 5db23e78..c94d54a2 100644 --- a/app/actions/filter_torrents.py +++ b/app/actions/filter_torrents.py @@ -1,4 +1,9 @@ +from typing import Optional, List + +from pydantic import Field + from app.actions import BaseAction +from app.helper.torrent import TorrentHelper from app.schemas import ActionParams, ActionContext @@ -6,7 +11,10 @@ class FilterTorrentsParams(ActionParams): """ 过滤资源数据参数 """ - pass + rule_groups: Optional[List[str]] = Field([], description="规则组") + include: Optional[str] = Field(None, description="包含规则") + exclude: Optional[str] = Field(None, description="排除规则") + size: Optional[str] = Field(None, description="资源大小范围(MB)") class FilterTorrentsAction(BaseAction): @@ -14,6 +22,12 @@ class FilterTorrentsAction(BaseAction): 过滤资源数据 """ + _torrents = [] + + def __init__(self): + super().__init__() + self.torrenthelper = TorrentHelper() + @property def name(self) -> str: return "过滤资源数据" @@ -24,7 +38,29 @@ class FilterTorrentsAction(BaseAction): @property def success(self) -> bool: - return True + return self.done async def execute(self, params: FilterTorrentsParams, context: ActionContext) -> ActionContext: - pass + """ + 过滤torrents中的资源 + """ + for torrent in context.torrents: + if self.torrenthelper.filter_torrent( + torrent_info=torrent.torrent_info, + filter_params={ + "include": params.include, + "exclude": params.exclude, + "size": params.size + } + ): + if self.chain.filter_torrents( + rule_groups=params.rule_groups, + torrent_list=[torrent.torrent_info], + mediainfo=torrent.media_info + ): + self._torrents.append(torrent) + + context.torrents = self._torrents + + self.job_done() + return context diff --git a/app/actions/search_torrents.py b/app/actions/search_torrents.py deleted file mode 100644 index 43964e8c..00000000 --- a/app/actions/search_torrents.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Optional - -from pydantic import Field - -from app.actions import BaseAction -from app.schemas import ActionParams, ActionContext - - -class SearchTorrentsParams(ActionParams): - """ - 搜索站点资源参数 - """ - name: str = Field(None, description="资源名称") - year: Optional[int] = Field(None, description="年份") - type: Optional[str] = Field(None, description="资源类型 (电影/电视剧)") - season: Optional[int] = Field(None, description="季度") - recognize: Optional[bool] = Field(False, description="是否识别") - - -class SearchTorrentsAction(BaseAction): - """ - 搜索站点资源 - """ - - @property - def name(self) -> str: - return "搜索站点资源" - - @property - def description(self) -> str: - return "根据关键字搜索站点种子资源" - - @property - def success(self) -> bool: - return True - - async def execute(self, params: SearchTorrentsParams, context: ActionContext) -> ActionContext: - pass diff --git a/app/actions/send_message.py b/app/actions/send_message.py index e50d6ef8..25a96bc9 100644 --- a/app/actions/send_message.py +++ b/app/actions/send_message.py @@ -1,12 +1,17 @@ +from typing import List, Optional, Union + +from pydantic import Field + from app.actions import BaseAction -from app.schemas import ActionParams, ActionContext +from app.schemas import ActionParams, ActionContext, MessageChannel class SendMessageParams(ActionParams): """ 发送消息参数 """ - pass + channel: Optional[List[str]] = Field([], description="消息渠道") + userid: Optional[Union[str, int]] = Field(None, description="用户ID") class SendMessageAction(BaseAction): @@ -24,7 +29,20 @@ class SendMessageAction(BaseAction): @property def success(self) -> bool: - return True + return self.done async def execute(self, params: SendMessageParams, context: ActionContext) -> ActionContext: - pass + """ + 发送messages中的消息 + """ + for message in context.messages: + if params.channel: + message.channel = MessageChannel(params.channel) + if params.userid: + message.userid = params.userid + self.chain.post_message(message) + + context.messages = [] + + self.job_done() + return context diff --git a/app/api/endpoints/search.py b/app/api/endpoints/search.py index 6eaa79af..5b0c50e2 100644 --- a/app/api/endpoints/search.py +++ b/app/api/endpoints/search.py @@ -133,12 +133,12 @@ def search_by_id(mediaid: str, @router.get("/title", summary="模糊搜索资源", response_model=schemas.Response) def search_by_title(keyword: str = None, page: int = 0, - site: int = None, + sites: List[int] = None, _: schemas.TokenPayload = Depends(verify_token)) -> Any: """ 根据名称模糊搜索站点资源,支持分页,关键词为空是返回首页资源 """ - torrents = SearchChain().search_by_title(title=keyword, page=page, site=site) + torrents = SearchChain().search_by_title(title=keyword, page=page, sites=sites) if not torrents: return schemas.Response(success=False, message="未搜索到任何资源") return schemas.Response(success=True, data=[torrent.to_dict() for torrent in torrents]) diff --git a/app/chain/search.py b/app/chain/search.py index 56304f93..eb6496d1 100644 --- a/app/chain/search.py +++ b/app/chain/search.py @@ -61,19 +61,21 @@ class SearchChain(ChainBase): self.save_cache(bytes_results, self.__result_temp_file) return results - def search_by_title(self, title: str, page: int = 0, site: int = None) -> List[Context]: + def search_by_title(self, title: str, page: int = 0, + sites: List[int] = None, cache_local: bool = True) -> List[Context]: """ 根据标题搜索资源,不识别不过滤,直接返回站点内容 :param title: 标题,为空时返回所有站点首页内容 :param page: 页码 - :param site: 站点ID + :param sites: 站点ID列表 + :param cache_local: 是否缓存到本地 """ if title: logger.info(f'开始搜索资源,关键词:{title} ...') else: - logger.info(f'开始浏览资源,站点:{site} ...') + logger.info(f'开始浏览资源,站点:{sites} ...') # 搜索 - torrents = self.__search_all_sites(keywords=[title], sites=[site] if site else None, page=page) or [] + torrents = self.__search_all_sites(keywords=[title], sites=sites if sites else None, page=page) or [] if not torrents: logger.warn(f'{title} 未搜索到资源') return [] @@ -81,8 +83,9 @@ class SearchChain(ChainBase): contexts = [Context(meta_info=MetaInfo(title=torrent.title, subtitle=torrent.description), torrent_info=torrent) for torrent in torrents] # 保存到本地文件 - bytes_results = pickle.dumps(contexts) - self.save_cache(bytes_results, self.__result_temp_file) + if cache_local: + bytes_results = pickle.dumps(contexts) + self.save_cache(bytes_results, self.__result_temp_file) return contexts def last_search_results(self) -> List[Context]: diff --git a/app/helper/rule.py b/app/helper/rule.py index 9ec6273b..61bbe4a8 100644 --- a/app/helper/rule.py +++ b/app/helper/rule.py @@ -33,7 +33,7 @@ class RuleHelper: return group return None - def get_rule_group_by_media(self, media: MediaInfo, group_names: list = None) -> List[FilterRuleGroup]: + def get_rule_group_by_media(self, media: MediaInfo = None, group_names: list = None) -> List[FilterRuleGroup]: """ 根据媒体信息获取规则组 """ @@ -44,9 +44,9 @@ class RuleHelper: for group in rule_groups: if not group.media_type: ret_groups.append(group) - elif not group.category and group.media_type == media.type.value: + elif media and not group.category and group.media_type == media.type.value: ret_groups.append(group) - elif group.category == media.category: + elif media and group.category == media.category: ret_groups.append(group) return ret_groups diff --git a/app/helper/torrent.py b/app/helper/torrent.py index 102e669a..b582c136 100644 --- a/app/helper/torrent.py +++ b/app/helper/torrent.py @@ -445,6 +445,26 @@ class TorrentHelper(metaclass=Singleton): logger.info(f"{torrent_info.title} 不匹配特效规则 {effect}") return False + # 大小 + size_range = filter_params.get("size") + if size_range.find("-") != -1: + # 区间 + size_min, size_max = size_range.split("-") + size_min = float(size_min.strip()) * 1024 * 1024 + size_max = float(size_max.strip()) * 1024 * 1024 + if torrent_info.size < size_min or torrent_info.size > size_max: + return False + elif size_range.startswith(">"): + # 大于 + size_min = float(size_range[1:].strip()) * 1024 * 1024 + if torrent_info.size < size_min: + return False + elif size_range.startswith("<"): + # 小于 + size_max = float(size_range[1:].strip()) * 1024 * 1024 + if torrent_info.size > size_max: + return False + return True @staticmethod