fix transfer

This commit is contained in:
jxxghp 2023-06-11 14:52:23 +08:00
parent f57e801236
commit 760f603076
9 changed files with 189 additions and 149 deletions

View File

@ -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)

View File

@ -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}")
# 解析中种子hashTMDB 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)

View File

@ -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)

View File

@ -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

View File

@ -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:
"""

View File

@ -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

View File

@ -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'))

View File

@ -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:
"""

View File

@ -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,