From 760f6030762766141dab6db5f86aeddc5af66a01 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 11 Jun 2023 14:52:23 +0800 Subject: [PATCH] fix transfer --- app/chain/__init__.py | 4 +- app/chain/transfer.py | 66 +++++++++++++++++------ app/command.py | 26 +++++---- app/modules/__init__.py | 3 +- app/modules/qbittorrent/__init__.py | 44 ++++++++++++++-- app/modules/qbittorrent/qbittorrent.py | 64 +++++----------------- app/modules/themoviedb/tmdb.py | 23 +++++--- app/modules/transmission/__init__.py | 41 ++++++++++++--- app/modules/transmission/transmission.py | 67 ++++++------------------ 9 files changed, 189 insertions(+), 149 deletions(-) diff --git a/app/chain/__init__.py b/app/chain/__init__.py index e4b2d400..46a5d28c 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -103,8 +103,8 @@ class ChainBase(AbstractSingleton, metaclass=Singleton): episodes: Set[int] = None) -> Optional[Tuple[Optional[str], str]]: return self.run_module("download", torrent_path=torrent_path, cookie=cookie, episodes=episodes) - def list_torrents(self, status: TorrentStatus) -> Optional[List[dict]]: - return self.run_module("list_torrents", status=status) + def list_torrents(self, status: TorrentStatus = None, hashs: Union[list, str] = None) -> Optional[List[dict]]: + return self.run_module("list_torrents", status=status, hashs=hashs) def remove_torrents(self, hashs: Union[str, list]) -> bool: return self.run_module("remove_torrents", hashs=hashs) diff --git a/app/chain/transfer.py b/app/chain/transfer.py index a0868f7d..e7ddd660 100644 --- a/app/chain/transfer.py +++ b/app/chain/transfer.py @@ -1,14 +1,15 @@ +import re from typing import List, Optional from app.chain import ChainBase -from app.core.metainfo import MetaInfo -from app.core.context import MediaInfo from app.core.config import settings +from app.core.context import MediaInfo from app.core.meta import MetaBase +from app.core.metainfo import MetaInfo from app.log import logger from app.utils.string import StringUtils from app.utils.system import SystemUtils -from app.utils.types import TorrentStatus, MediaType +from app.utils.types import TorrentStatus class TransferChain(ChainBase): @@ -16,16 +17,47 @@ class TransferChain(ChainBase): 文件转移处理链 """ - def process(self) -> bool: + def process(self, arg_str: str = None) -> bool: """ 获取下载器中的种子列表,并执行转移 """ - logger.info("开始执行下载器文件转移 ...") - # 从下载器获取种子列表 - torrents: Optional[List[dict]] = self.list_torrents(status=TorrentStatus.TRANSFER) - if not torrents: - logger.info("没有获取到已完成的下载任务") - return False + + def extract_hash_and_number(string: str): + """ + 从字符串中提取种子hash和编号 + """ + pattern = r'([a-fA-F0-9]{32}) (\d+)' + match = re.search(pattern, string) + if match: + hash_value = match.group(1) + number = match.group(2) + return hash_value, int(number) + else: + return None, None + + if arg_str: + logger.info(f"开始转移下载器文件,参数:{arg_str}") + # 解析中种子hash,TMDB ID + torrent_hash, tmdbid = extract_hash_and_number(arg_str) + if not hash or not tmdbid: + logger.error(f"参数错误,参数:{arg_str}") + return False + # 获取种子 + torrents: Optional[List[dict]] = self.list_torrents(hashs=torrent_hash) + if not torrents: + logger.error(f"没有获取到种子,参数:{arg_str}") + return False + # 查询媒体信息 + arg_mediainfo = self.recognize_media(meta=MetaInfo(torrents[0].get("title")), tmdbid=tmdbid) + else: + arg_mediainfo = None + logger.info("开始执行下载器文件转移 ...") + # 从下载器获取种子列表 + torrents: Optional[List[dict]] = self.list_torrents(status=TorrentStatus.TRANSFER) + if not torrents: + logger.info("没有获取到已完成的下载任务") + return False + logger.info(f"获取到 {len(torrents)} 个已完成的下载任务") # 识别 for torrent in torrents: @@ -35,11 +67,15 @@ class TransferChain(ChainBase): logger.warn(f'未识别到元数据,标题:{torrent.get("title")}') continue # 识别媒体信息 - mediainfo: MediaInfo = self.recognize_media(meta=meta) - if not mediainfo: - logger.warn(f'未识别到媒体信息,标题:{torrent.get("title")}') - self.post_message(title=f"{torrent.get('title')} 未识别到媒体信息,无法入库!") - continue + if not arg_mediainfo: + mediainfo: MediaInfo = self.recognize_media(meta=meta) + if not mediainfo: + logger.warn(f'未识别到媒体信息,标题:{torrent.get("title")}') + self.post_message(title=f"{torrent.get('title')} 未识别到媒体信息,无法入库!\n" + f"回复:/transfer {torrent.get('hash')} [tmdbid] 手动识别转移。") + continue + else: + mediainfo = arg_mediainfo logger.info(f"{torrent.get('title')} 识别为:{mediainfo.type.value} {mediainfo.get_title_string()}") # 更新媒体图片 self.obtain_image(mediainfo=mediainfo) diff --git a/app/command.py b/app/command.py index 17732d17..c79f44ab 100644 --- a/app/command.py +++ b/app/command.py @@ -40,12 +40,12 @@ class Command(metaclass=Singleton): "description": "同步豆瓣想看", "data": {} }, - "/subscriberefresh": { + "/subscribe_refresh": { "func": SubscribeChain().refresh, "description": "刷新订阅", "data": {} }, - "/subscribesearch": { + "/subscribe_search": { "func": SubscribeChain().search, "description": "搜索订阅", "data": { @@ -135,15 +135,20 @@ class Command(metaclass=Singleton): """ return self._commands.get(cmd, {}) - def execute(self, cmd: str) -> None: + def execute(self, cmd: str, data_str: str = "") -> None: """ 执行命令 """ command = self.get(cmd) if command: logger.info(f"开始执行:{command.get('description')} ...") - data = command['data'] if command.get('data') else {} - command['func'](**data) + cmd_data = command['data'] if command.get('data') else {} + if cmd_data: + command['func'](**cmd_data) + elif data_str: + command['func'](data_str) + else: + command['func']() @staticmethod def send_plugin_event(etype: EventType, data: dict) -> None: @@ -157,9 +162,12 @@ class Command(metaclass=Singleton): """ 注册命令执行事件 event_data: { - "cmd": "/xxx" + "cmd": "/xxx args" } """ - cmd = event.event_data.get('cmd') - if self.get(cmd): - self.execute(cmd) + event_str = event.event_data.get('cmd') + if event_str: + cmd = event_str.split()[0] + args = " ".join(event_str.split()[1:]) + if self.get(cmd): + self.execute(cmd, args) diff --git a/app/modules/__init__.py b/app/modules/__init__.py index 595cb32f..0683ea91 100644 --- a/app/modules/__init__.py +++ b/app/modules/__init__.py @@ -137,10 +137,11 @@ class _ModuleBase(metaclass=ABCMeta): """ pass - def list_torrents(self, status: TorrentStatus) -> Optional[List[dict]]: + def list_torrents(self, status: TorrentStatus = None, hashs: Union[list, str] = None) -> Optional[List[dict]]: """ 获取下载器种子列表 :param status: 种子状态 + :param hashs: 种子Hash :return: 下载器中符合状态的种子列表 """ pass diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index 35014afd..6fe5b74d 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -89,19 +89,53 @@ class QbittorrentModule(_ModuleBase): :param hashs: 种子Hash :return: 处理状态 """ - return self.qbittorrent.set_torrents_tag(ids=hashs, tag=['已整理']) + return self.qbittorrent.set_torrents_tag(ids=hashs, tags=['已整理']) - def list_torrents(self, status: TorrentStatus) -> Optional[List[dict]]: + def list_torrents(self, status: TorrentStatus = None, hashs: Union[list, str] = None) -> Optional[List[dict]]: """ 获取下载器种子列表 :param status: 种子状态 + :param hashs: 种子Hash :return: 下载器中符合状态的种子列表 """ - if status == TorrentStatus.TRANSFER: - torrents = self.qbittorrent.get_transfer_torrents(tag=settings.TORRENT_TAG or None) + ret_torrents = [] + if hashs: + # 按Hash获取 + torrents, _ = self.qbittorrent.get_torrents(ids=hashs) + for torrent in torrents: + content_path = torrent.get("content_path") + if content_path: + torrent_path = Path(content_path) + else: + torrent_path = Path(settings.DOWNLOAD_PATH) / torrent.get('name') + ret_torrents.append({ + 'title': torrent.get('name'), + 'path': torrent_path, + 'hash': torrent.get('hash'), + 'tags': torrent.get('tags') + }) + elif status == TorrentStatus.TRANSFER: + # 获取已完成且未整理的 + torrents = self.qbittorrent.get_completed_torrents(tags=settings.TORRENT_TAG) + for torrent in torrents: + tags = torrent.get("tags") or [] + if "已整理" in tags: + continue + # 内容路径 + content_path = torrent.get("content_path") + if content_path: + torrent_path = Path(content_path) + else: + torrent_path = Path(settings.DOWNLOAD_PATH) / torrent.get('name') + ret_torrents.append({ + 'title': torrent.get('name'), + 'path': torrent_path, + 'hash': torrent.get('hash'), + 'tags': torrent.get('tags') + }) else: return None - return torrents + return ret_torrents def remove_torrents(self, hashs: Union[str, list]) -> bool: """ diff --git a/app/modules/qbittorrent/qbittorrent.py b/app/modules/qbittorrent/qbittorrent.py index 25270063..24b87edb 100644 --- a/app/modules/qbittorrent/qbittorrent.py +++ b/app/modules/qbittorrent/qbittorrent.py @@ -1,5 +1,4 @@ import time -from pathlib import Path from typing import Optional, Union, Tuple, List import qbittorrentapi @@ -52,7 +51,7 @@ class Qbittorrent(metaclass=Singleton): def get_torrents(self, ids: Union[str, list] = None, status: Union[str, list] = None, - tag: Union[str, list] = None) -> Tuple[List[TorrentDictionary], bool]: + tags: Union[str, list] = None) -> Tuple[List[TorrentDictionary], bool]: """ 获取种子列表 return: 种子列表, 是否发生异常 @@ -62,17 +61,12 @@ class Qbittorrent(metaclass=Singleton): try: torrents = self.qbc.torrents_info(torrent_hashes=ids, status_filter=status) - if tag: + if tags: results = [] - if not isinstance(tag, list): - tag = [tag] + if not isinstance(tags, list): + tags = [tags] for torrent in torrents: - include_flag = True - for t in tag: - if t and t not in torrent.get("tags"): - include_flag = False - break - if include_flag: + if set(tags).issubset(set(torrent.get("tags"))): results.append(torrent) return results, False return torrents or [], False @@ -81,18 +75,18 @@ class Qbittorrent(metaclass=Singleton): return [], True def get_completed_torrents(self, ids: Union[str, list] = None, - tag: Union[str, list] = None) -> Optional[List[TorrentDictionary]]: + tags: Union[str, list] = None) -> Optional[List[TorrentDictionary]]: """ 获取已完成的种子 return: 种子列表, 如发生异常则返回None """ if not self.qbc: return None - torrents, error = self.get_torrents(status=["completed"], ids=ids, tag=tag) + torrents, error = self.get_torrents(status=["completed"], ids=ids, tags=tags) return None if error else torrents or [] def get_downloading_torrents(self, ids: Union[str, list] = None, - tag: Union[str, list] = None) -> Optional[List[TorrentDictionary]]: + tags: Union[str, list] = None) -> Optional[List[TorrentDictionary]]: """ 获取正在下载的种子 return: 种子列表, 如发生异常则返回None @@ -101,7 +95,7 @@ class Qbittorrent(metaclass=Singleton): return None torrents, error = self.get_torrents(ids=ids, status=["downloading"], - tag=tag) + tags=tags) return None if error else torrents or [] def remove_torrents_tag(self, ids: Union[str, list], tag: Union[str, list]) -> bool: @@ -117,7 +111,7 @@ class Qbittorrent(metaclass=Singleton): logger.error(f"移除种子Tag出错:{err}") return False - def set_torrents_tag(self, ids: Union[str, list], tag: list): + def set_torrents_tag(self, ids: Union[str, list], tags: list): """ 设置种子状态为已整理,以及是否强制做种 """ @@ -125,7 +119,7 @@ class Qbittorrent(metaclass=Singleton): return try: # 打标签 - self.qbc.torrents_add_tags(tags=tag, torrent_hashes=ids) + self.qbc.torrents_add_tags(tags=tags, torrent_hashes=ids) except Exception as err: logger.error(f"设置种子Tag出错:{err}") @@ -138,40 +132,6 @@ class Qbittorrent(metaclass=Singleton): except Exception as err: logger.error(f"设置强制作种出错:{err}") - def get_transfer_torrents(self, tag: Union[str, list] = None) -> Optional[List[dict]]: - """ - 获取下载文件转移任务种子 - """ - # 处理下载完成的任务 - torrents = self.get_completed_torrents() or [] - trans_tasks = [] - for torrent in torrents: - torrent_tags = torrent.get("tags") or "" - # 含"已整理"tag的不处理 - if "已整理" in torrent_tags: - continue - # 开启标签隔离,未包含指定标签的不处理 - if tag and tag not in torrent_tags: - logger.debug(f"{torrent.get('name')} 未包含指定标签:{tag}") - continue - path = torrent.get("save_path") - # 无法获取下载路径的不处理 - if not path: - logger.warn(f"未获取到 {torrent.get('name')} 下载保存路径") - continue - content_path = torrent.get("content_path") - if content_path: - torrent_path = Path(content_path) - else: - torrent_path = Path(settings.DOWNLOAD_PATH) / torrent.get('name') - trans_tasks.append({ - 'title': torrent.get('name'), - 'path': torrent_path, - 'hash': torrent.get('hash'), - 'tags': torrent.get('tags') - }) - return trans_tasks - def __get_last_add_torrentid_by_tag(self, tag: Union[str, list], status: Union[str, list] = None) -> Optional[str]: """ @@ -179,7 +139,7 @@ class Qbittorrent(metaclass=Singleton): :return: 种子ID """ try: - torrents, _ = self.get_torrents(status=status, tag=tag) + torrents, _ = self.get_torrents(status=status, tags=tag) except Exception as err: logger.error(f"获取种子列表出错:{err}") return None diff --git a/app/modules/themoviedb/tmdb.py b/app/modules/themoviedb/tmdb.py index b10c53ef..d9a55687 100644 --- a/app/modules/themoviedb/tmdb.py +++ b/app/modules/themoviedb/tmdb.py @@ -262,7 +262,7 @@ class TmdbHelper: continue index += 1 if not movie.get("names"): - movie = self.get_info(MediaType.MOVIE, movie.get("id")) + movie = self.get_info(mtype=MediaType.MOVIE, tmdbid=movie.get("id")) if movie and self.__compare_names(name, movie.get("names")): return movie if index > 5: @@ -319,7 +319,7 @@ class TmdbHelper: continue index += 1 if not tv.get("names"): - tv = self.get_info(MediaType.TV, tv.get("id")) + tv = self.get_info(mtype=MediaType.TV, tmdbid=tv.get("id")) if self.__compare_names(name, tv.get("names")): return tv if index > 5: @@ -374,7 +374,7 @@ class TmdbHelper: # 匹配别名、译名 for tv in tvs[:5]: if not tv.get("names"): - tv = self.get_info(MediaType.TV, tv.get("id")) + tv = self.get_info(mtype=MediaType.TV, tmdbid=tv.get("id")) if not self.__compare_names(name, tv.get("names")): continue if __season_match(tv_info=tv, _season_year=season_year): @@ -452,12 +452,12 @@ class TmdbHelper: for multi in multis[:5]: if multi.get("media_type") == "movie": if not multi.get("names"): - multi = self.get_info(MediaType.MOVIE, multi.get("id")) + multi = self.get_info(mtype=MediaType.MOVIE, tmdbid=multi.get("id")) if self.__compare_names(name, multi.get("names")): return multi elif multi.get("media_type") == "tv": if not multi.get("names"): - multi = self.get_info(MediaType.TV, multi.get("id")) + multi = self.get_info(mtype=MediaType.TV, tmdbid=multi.get("id")) if self.__compare_names(name, multi.get("names")): return multi return {} @@ -541,15 +541,24 @@ class TmdbHelper: genre_ids.append(genre.get('id')) return genre_ids - # 设置语言 + # 查询TMDB详ngeq if mtype == MediaType.MOVIE: tmdb_info = self.__get_movie_detail(tmdbid) if tmdb_info: tmdb_info['media_type'] = MediaType.MOVIE - else: + elif mtype == MediaType.TV: tmdb_info = self.__get_tv_detail(tmdbid) if tmdb_info: tmdb_info['media_type'] = MediaType.TV + else: + tmdb_info = self.__get_movie_detail(tmdbid) + if tmdb_info: + tmdb_info['media_type'] = MediaType.MOVIE + else: + tmdb_info = self.__get_tv_detail(tmdbid) + if tmdb_info: + tmdb_info['media_type'] = MediaType.TV + if tmdb_info: # 转换genreid tmdb_info['genre_ids'] = __get_genre_ids(tmdb_info.get('genres')) diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index 6725df65..99d0d347 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -3,13 +3,13 @@ from typing import Set, Tuple, Optional, Union, List from app.core.config import settings from app.core.metainfo import MetaInfo +from app.log import logger from app.modules import _ModuleBase from app.modules.transmission.transmission import Transmission from app.utils.types import TorrentStatus class TransmissionModule(_ModuleBase): - transmission: Transmission = None def init_module(self) -> None: @@ -77,19 +77,48 @@ class TransmissionModule(_ModuleBase): :param hashs: 种子Hash :return: 处理状态 """ - return self.transmission.set_torrent_tag(ids=hashs, tag=['已整理']) + return self.transmission.set_torrent_tag(ids=hashs, tags=['已整理']) - def list_torrents(self, status: TorrentStatus) -> Optional[List[dict]]: + def list_torrents(self, status: TorrentStatus = None, hashs: Union[list, str] = None) -> Optional[List[dict]]: """ 获取下载器种子列表 :param status: 种子状态 + :param hashs: 种子Hash :return: 下载器中符合状态的种子列表 """ - if status == TorrentStatus.TRANSFER: - torrents = self.transmission.get_transfer_torrents(tag=settings.TORRENT_TAG or None) + ret_torrents = [] + if hashs: + # 按Hash获取 + torrents, _ = self.transmission.get_torrents(ids=hashs) + for torrent in torrents: + ret_torrents.append({ + 'title': torrent.name, + 'path': Path(torrent.download_dir) / torrent.name, + 'hash': torrent.hashString, + 'tags': torrent.labels + }) + elif status == TorrentStatus.TRANSFER: + # 获取已完成且未整理的 + torrents = self.transmission.get_completed_torrents(tags=settings.TORRENT_TAG) + for torrent in torrents: + # 含"已整理"tag的不处理 + if "已整理" in torrent.labels or []: + continue + # 下载路径 + path = torrent.download_dir + # 无法获取下载路径的不处理 + if not path: + logger.debug(f"未获取到 {torrent.name} 下载保存路径") + continue + ret_torrents.append({ + 'title': torrent.name, + 'path': Path(path) / torrent.name, + 'hash': torrent.hashString, + 'tags': torrent.labels + }) else: return None - return torrents + return ret_torrents def remove_torrents(self, hashs: Union[str, list]) -> bool: """ diff --git a/app/modules/transmission/transmission.py b/app/modules/transmission/transmission.py index 790f1af7..0229992b 100644 --- a/app/modules/transmission/transmission.py +++ b/app/modules/transmission/transmission.py @@ -1,4 +1,3 @@ -from pathlib import Path from typing import Optional, Union, Tuple, List import transmission_rpc @@ -50,7 +49,7 @@ class Transmission(metaclass=Singleton): return None def get_torrents(self, ids: Union[str, list] = None, status: Union[str, list] = None, - tag: Union[str, list] = None) -> Tuple[List[Torrent], bool]: + tags: Union[str, list] = None) -> Tuple[List[Torrent], bool]: """ 获取种子列表 返回结果 种子列表, 是否有错误 @@ -64,25 +63,22 @@ class Transmission(metaclass=Singleton): return [], True if status and not isinstance(status, list): status = [status] - if tag and not isinstance(tag, list): - tag = [tag] + if tags and not isinstance(tags, list): + tags = [tags] ret_torrents = [] for torrent in torrents: + # 状态过滤 if status and torrent.status not in status: continue + # 种子标签 labels = torrent.labels if hasattr(torrent, "labels") else [] - include_flag = True - if tag: - for t in tag: - if t and t not in labels: - include_flag = False - break - if include_flag: - ret_torrents.append(torrent) + if tags and not set(tags).issubset(set(labels)): + continue + ret_torrents.append(torrent) return ret_torrents, False def get_completed_torrents(self, ids: Union[str, list] = None, - tag: Union[str, list] = None) -> Optional[List[Torrent]]: + tags: Union[str, list] = None) -> Optional[List[Torrent]]: """ 获取已完成的种子列表 return 种子列表, 发生错误时返回None @@ -90,14 +86,14 @@ class Transmission(metaclass=Singleton): if not self.trc: return None try: - torrents, error = self.get_torrents(status=["seeding", "seed_pending"], ids=ids, tag=tag) + torrents, error = self.get_torrents(status=["seeding", "seed_pending"], ids=ids, tags=tags) return None if error else torrents or [] except Exception as err: logger.error(f"获取已完成的种子列表出错:{err}") return None def get_downloading_torrents(self, ids: Union[str, list] = None, - tag: Union[str, list] = None) -> Optional[List[Torrent]]: + tags: Union[str, list] = None) -> Optional[List[Torrent]]: """ 获取正在下载的种子列表 return 种子列表, 发生错误时返回None @@ -107,58 +103,25 @@ class Transmission(metaclass=Singleton): try: torrents, error = self.get_torrents(ids=ids, status=["downloading", "download_pending"], - tag=tag) + tags=tags) return None if error else torrents or [] except Exception as err: logger.error(f"获取正在下载的种子列表出错:{err}") return None - def set_torrent_tag(self, ids: str, tag: list) -> bool: + def set_torrent_tag(self, ids: str, tags: list) -> bool: """ 设置种子标签 """ - if not ids or not tag: + if not ids or not tags: return False try: - self.trc.change_torrent(labels=tag, ids=ids) + self.trc.change_torrent(labels=tags, ids=ids) return True except Exception as err: logger.error(f"设置种子标签出错:{err}") return False - def get_transfer_torrents(self, tag: Union[str, list] = None) -> List[dict]: - """ - 获取下载文件转移任务种子 - """ - # 处理下载完成的任务 - torrents = self.get_completed_torrents() or [] - trans_tasks = [] - for torrent in torrents: - # 3.0版本以下的Transmission没有labels - if not hasattr(torrent, "labels"): - logger.error(f"版本可能过低,无labels属性,请安装3.0以上版本!") - break - torrent_tags = torrent.labels or "" - # 含"已整理"tag的不处理 - if "已整理" in torrent_tags: - continue - # 开启标签隔离,未包含指定标签的不处理 - if tag and tag not in torrent_tags: - logger.debug(f"{torrent.name} 未包含指定标签:{tag}") - continue - path = torrent.download_dir - # 无法获取下载路径的不处理 - if not path: - logger.debug(f"未获取到 {torrent.name} 下载保存路径") - continue - trans_tasks.append({ - 'title': torrent.name, - 'path': Path(path) / torrent.name, - 'hash': torrent.hashString, - 'tags': torrent.labels - }) - return trans_tasks - def add_torrent(self, content: Union[str, bytes], is_paused: bool = False, download_dir: str = None,