mirror of
https://github.com/d0zingcat/MoviePilot-Plugins.git
synced 2026-05-13 23:16:47 +00:00
Merge pull request #358 from hotlcc/develop-20240603
This commit is contained in:
@@ -641,13 +641,14 @@
|
||||
},
|
||||
"DownloaderHelper": {
|
||||
"name": "下载器助手",
|
||||
"description": "自动做种、站点标签、自动删种。",
|
||||
"description": "自动标签、自动做种、自动删种。",
|
||||
"labels": "下载管理,仪表板",
|
||||
"version": "2.7",
|
||||
"version": "2.8",
|
||||
"icon": "DownloaderHelper.png",
|
||||
"author": "hotlcc",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v2.8": "优化了仪表板组件高度;优化了活动种子状态;优化了自动标签,优化站点标签,新增BT/PT标签;监听原种删除事件补充删种逻辑。更新后若插件功能异常,请重启一次MP。",
|
||||
"v2.7": "实时速率仪表板样式微调。",
|
||||
"v2.6": "新增仪表板实时速率组件,支持单独展示qb和tr的实时速率(tr未测试,有问题提Issue并@hotlcc)。",
|
||||
"v2.5": "优化通知类型;降低认证级别要求,使MP非认证用户可用,但无法使用【站点名称优先】功能。主程序需升级至v1.9.2及以上版本,否则插件功能异常!",
|
||||
|
||||
@@ -32,11 +32,11 @@ class DownloaderHelper(_PluginBase):
|
||||
# 插件名称
|
||||
plugin_name = "下载器助手"
|
||||
# 插件描述
|
||||
plugin_desc = "自动做种、站点标签、自动删种。"
|
||||
plugin_desc = "自动标签、自动做种、自动删种。"
|
||||
# 插件图标
|
||||
plugin_icon = "DownloaderHelper.png"
|
||||
# 插件版本
|
||||
plugin_version = "2.7"
|
||||
plugin_version = "2.8"
|
||||
# 插件作者
|
||||
plugin_author = "hotlcc"
|
||||
# 作者主页
|
||||
@@ -103,6 +103,12 @@ class DownloaderHelper(_PluginBase):
|
||||
'mdi-download-box': 'M5 3h14a2 2 0 0 1 2 2v14c0 1.11-.89 2-2 2H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m3 14h8v-2H8zm8-7h-2.5V7h-3v3H8l4 4z',
|
||||
'mdi-content-save': 'M15 9H5V5h10m-3 14a3 3 0 0 1-3-3a3 3 0 0 1 3-3a3 3 0 0 1 3 3a3 3 0 0 1-3 3m5-16H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7z',
|
||||
}
|
||||
# 公共 tracker url
|
||||
__public_tracker_urls = [
|
||||
"** [DHT] **",
|
||||
"** [PeX] **",
|
||||
"** [LSD] **",
|
||||
]
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
"""
|
||||
@@ -202,6 +208,7 @@ class DownloaderHelper(_PluginBase):
|
||||
'cron': '0/30 * * * *',
|
||||
'exclude_tags': 'BT,刷流',
|
||||
'dashboard_widget_refresh': 5,
|
||||
'dashboard_speed_widget_refresh': 5,
|
||||
}
|
||||
# 合并默认配置
|
||||
config_suggest.update(self.__config_default)
|
||||
@@ -221,6 +228,11 @@ class DownloaderHelper(_PluginBase):
|
||||
},
|
||||
'content': [{
|
||||
'component': 'VRow',
|
||||
'props': {
|
||||
'style': {
|
||||
'margin-top': '0'
|
||||
}
|
||||
},
|
||||
'content': [{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
@@ -244,9 +256,9 @@ class DownloaderHelper(_PluginBase):
|
||||
'content': [{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': f'{d.short_id}_enable_seeding',
|
||||
'label': '自动做种',
|
||||
'hint': '是否开启自动做种功能'
|
||||
'model': f'{d.short_id}_enable_tagging',
|
||||
'label': '自动标签',
|
||||
'hint': '是否开启自动标签功能;包含BT/PT标签和站点标签(仅PT有效);不受【排除种子标签】限制。'
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
@@ -258,9 +270,9 @@ class DownloaderHelper(_PluginBase):
|
||||
'content': [{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': f'{d.short_id}_enable_tagging',
|
||||
'label': '站点标签',
|
||||
'hint': '是否开启站点标签功能'
|
||||
'model': f'{d.short_id}_enable_seeding',
|
||||
'label': '自动做种',
|
||||
'hint': '是否开启自动做种功能;受【排除种子标签】限制。'
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
@@ -274,7 +286,7 @@ class DownloaderHelper(_PluginBase):
|
||||
'props': {
|
||||
'model': f'{d.short_id}_enable_delete',
|
||||
'label': '自动删种',
|
||||
'hint': '是否开启自动删种功能'
|
||||
'hint': '是否开启自动删种功能;受【排除种子标签】限制。'
|
||||
}
|
||||
}]
|
||||
}]
|
||||
@@ -346,7 +358,7 @@ class DownloaderHelper(_PluginBase):
|
||||
'props': {
|
||||
'model': 'listen_download_event',
|
||||
'label': '监听下载事件',
|
||||
'hint': '监听下载添加事件。当MoviePilot添加下载任务时,会触发执行本插件进行自动做种和添加站点标签。'
|
||||
'hint': '监听下载添加事件。当MoviePilot添加下载任务时,会触发本插件进行自动标签和自动做种。'
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
@@ -360,7 +372,7 @@ class DownloaderHelper(_PluginBase):
|
||||
'props': {
|
||||
'model': 'listen_source_file_event',
|
||||
'label': '监听源文件事件',
|
||||
'hint': '监听源文件删除事件。当在【历史记录】中删除源文件时,会自动触发运行本插件任务进行自动删种。'
|
||||
'hint': '监听源文件删除事件以及原种删除事件。当在【历史记录】中删除源文件时,会触发本插件进行自动删种。'
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
@@ -505,7 +517,7 @@ class DownloaderHelper(_PluginBase):
|
||||
'<tracker-domain>:<site-domain>\n\n'
|
||||
'例如:\n'
|
||||
'chdbits.xyz:ptchdbits.co',
|
||||
'hint': 'Tracker映射。用于在站点打标签时,指定tracker和站点域名不同的种子的域名对应关系;前面为tracker域名(完整域名或者主域名皆可),中间是英文冒号,后面是站点域名。'
|
||||
'hint': 'Tracker映射。用于在站点标签时,指定tracker和站点域名不同的种子的域名对应关系;前面为tracker域名(完整域名或者主域名皆可),中间是英文冒号,后面是站点域名。'
|
||||
}
|
||||
}]
|
||||
}]
|
||||
@@ -1380,46 +1392,78 @@ class DownloaderHelper(_PluginBase):
|
||||
return site_tag, delete_suggest
|
||||
|
||||
@classmethod
|
||||
def __check_need_delete_for_qbittorrent(cls, torrent: TorrentDictionary, deleted_event_data: dict = None) -> bool:
|
||||
def __check_need_delete_for_qbittorrent(cls, torrent: TorrentDictionary, context: TaskContext) -> bool:
|
||||
"""
|
||||
检查qb种子是否满足删除条件
|
||||
:param deleted_event_data: 任务执行伴随的源文件删除事件数据
|
||||
:param context: 任务上下文
|
||||
"""
|
||||
if not torrent:
|
||||
if not torrent or not context:
|
||||
return False
|
||||
|
||||
# 根据种子状态判断是否应该删种:状态为丢失文件时需要删除
|
||||
if torrent.get('state') == 'missingFiles':
|
||||
return True
|
||||
|
||||
# 根据伴随的源文件删除事件判断是否应该删种:如果当前种子和事件匹配并且种子中已经不存在数据文件时就需要删除
|
||||
match, torrent_data_path = cls.__check_torrent_match_file_for_qbittorrent(torrent=torrent,
|
||||
source_file_info=deleted_event_data)
|
||||
if not match:
|
||||
return False
|
||||
# 如果匹配的种子数据路径不存在,说明数据文件已经(全部)被删除了,那么就允许删种
|
||||
return not os.path.exists(torrent_data_path)
|
||||
# 源文件删除事件数据
|
||||
download_file_deleted_event_data = context.get_download_file_deleted_event_data()
|
||||
# 下载任务删除事件数据
|
||||
download_deleted_event_data = context.get_download_deleted_event_data()
|
||||
|
||||
# 源文件删除事件触发
|
||||
if download_file_deleted_event_data:
|
||||
# 根据伴随的源文件删除事件判断是否应该删种:如果当前种子和事件匹配并且种子中已经不存在数据文件时就需要删除
|
||||
match, torrent_data_path = cls.__check_torrent_match_file_for_qbittorrent(torrent=torrent,
|
||||
source_file_info=download_file_deleted_event_data)
|
||||
if not match:
|
||||
return False
|
||||
# 如果匹配的种子数据路径不存在,说明数据文件已经(全部)被删除了,那么就允许删种
|
||||
return not os.path.exists(torrent_data_path)
|
||||
# 下载任务删除事件触发
|
||||
elif download_deleted_event_data:
|
||||
torrent_info = download_deleted_event_data
|
||||
match = cls.__check_torrent_match_torrent_info(torrent_hash=torrent.get('hash'),
|
||||
torrent_data_file_name=torrent.get('name'),
|
||||
torrent_size=torrent.get('total_size'),
|
||||
torrent_info=torrent_info)
|
||||
return match
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def __check_need_delete_for_transmission(cls, torrent: Torrent, deleted_event_data: dict = None) -> bool:
|
||||
def __check_need_delete_for_transmission(cls, torrent: Torrent, context: TaskContext) -> bool:
|
||||
"""
|
||||
检查tr种子是否满足删除条件
|
||||
:param deleted_event_data: 任务执行伴随的源文件删除事件数据
|
||||
"""
|
||||
if not torrent:
|
||||
if not torrent or not context:
|
||||
return False
|
||||
|
||||
# 根据种子状态判断是否应该删种:状态为丢失文件时需要删除
|
||||
if torrent.error == 3 and torrent.error_string and 'No data found' in torrent.error_string:
|
||||
return True
|
||||
|
||||
# 根据伴随的源文件删除事件判断是否应该删种:如果当前种子和事件匹配并且种子中已经不存在数据文件时就需要删除
|
||||
match, torrent_data_path = cls.__check_torrent_match_file_for_transmission(torrent=torrent,
|
||||
source_file_info=deleted_event_data)
|
||||
if not match:
|
||||
return False
|
||||
# 如果匹配的种子数据路径不存在,说明数据文件已经(全部)被删除了,那么就允许删种
|
||||
return not os.path.exists(torrent_data_path)
|
||||
# 源文件删除事件数据
|
||||
download_file_deleted_event_data = context.get_download_file_deleted_event_data()
|
||||
# 下载任务删除事件数据
|
||||
download_deleted_event_data = context.get_download_deleted_event_data()
|
||||
|
||||
# 源文件删除事件触发
|
||||
if download_file_deleted_event_data:
|
||||
# 根据伴随的源文件删除事件判断是否应该删种:如果当前种子和事件匹配并且种子中已经不存在数据文件时就需要删除
|
||||
match, torrent_data_path = cls.__check_torrent_match_file_for_transmission(torrent=torrent,
|
||||
source_file_info=download_file_deleted_event_data)
|
||||
if not match:
|
||||
return False
|
||||
# 如果匹配的种子数据路径不存在,说明数据文件已经(全部)被删除了,那么就允许删种
|
||||
return not os.path.exists(torrent_data_path)
|
||||
# 下载任务删除事件触发
|
||||
elif download_deleted_event_data:
|
||||
torrent_info = download_deleted_event_data
|
||||
match = cls.__check_torrent_match_torrent_info(torrent_hash=torrent.hashString,
|
||||
torrent_data_file_name=torrent.name,
|
||||
torrent_size=torrent.total_size,
|
||||
torrent_info=torrent_info)
|
||||
return match
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def __check_torrent_match_file_for_qbittorrent(cls, torrent: TorrentDictionary,
|
||||
@@ -1489,6 +1533,25 @@ class DownloaderHelper(_PluginBase):
|
||||
|
||||
return False, None
|
||||
|
||||
@classmethod
|
||||
def __check_torrent_match_torrent_info(cls,
|
||||
torrent_hash: str,
|
||||
torrent_data_file_name: str,
|
||||
torrent_size: int,
|
||||
torrent_info: dict) -> bool:
|
||||
"""
|
||||
判断种子是否和种子信息匹配
|
||||
:param torrent_hash: 种子hash
|
||||
:param torrent_data_file_name: 种子数据文件名
|
||||
:param torrent_size: 种子大小
|
||||
:param torrent_info: 被判断的其它种子信息
|
||||
:return: 是否匹配
|
||||
"""
|
||||
if not torrent_hash or not torrent_data_file_name or not torrent_size or not torrent_info:
|
||||
return False
|
||||
return torrent_data_file_name == torrent_info.get("title") \
|
||||
and torrent_size == torrent_info.get("size")
|
||||
|
||||
def __send_notify(self, context: TaskContext):
|
||||
"""
|
||||
发送通知
|
||||
@@ -1625,10 +1688,10 @@ class DownloaderHelper(_PluginBase):
|
||||
return context
|
||||
if not context.is_selected_qb_downloader():
|
||||
return context
|
||||
enable_seeding = True if self.__get_config_item(
|
||||
config_key='qb_enable_seeding') and context.is_enabled_seeding() else False
|
||||
enable_tagging = True if self.__get_config_item(
|
||||
config_key='qb_enable_tagging') and context.is_enabled_tagging() else False
|
||||
enable_seeding = True if self.__get_config_item(
|
||||
config_key='qb_enable_seeding') and context.is_enabled_seeding() else False
|
||||
enable_delete = True if self.__get_config_item(
|
||||
config_key='qb_enable_delete') and context.is_enabled_delete() else False
|
||||
if not enable_seeding and not enable_tagging and not enable_delete:
|
||||
@@ -1666,24 +1729,24 @@ class DownloaderHelper(_PluginBase):
|
||||
return context
|
||||
|
||||
logger.info(
|
||||
f'子任务执行状态: 自动做种={enable_seeding}, 自动打标={enable_tagging}, 自动删种={enable_delete}')
|
||||
f'子任务执行状态: 自动标签={enable_tagging}, 自动做种={enable_seeding}, 自动删种={enable_delete}')
|
||||
|
||||
# 做种
|
||||
# 自动标签
|
||||
if enable_tagging:
|
||||
result.set_tagging(self.__tagging_batch_for_qbittorrent(qbittorrent=qbittorrent, torrents=torrents))
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn(f'插件服务正在退出,任务终止[{downloader_name}]')
|
||||
return context
|
||||
# 自动做种
|
||||
if enable_seeding:
|
||||
result.set_seeding(self.__seeding_batch_for_qbittorrent(torrents=torrents))
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn(f'插件服务正在退出,任务终止[{downloader_name}]')
|
||||
return context
|
||||
# 打标
|
||||
if enable_tagging:
|
||||
result.set_tagging(self.__tagging_batch_for_qbittorrent(torrents=torrents))
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn(f'插件服务正在退出,任务终止[{downloader_name}]')
|
||||
return context
|
||||
# 删种
|
||||
# 自动删种
|
||||
if enable_delete:
|
||||
result.set_delete(self.__delete_batch_for_qbittorrent(qbittorrent=qbittorrent, torrents=torrents,
|
||||
deleted_event_data=context.get_deleted_event_data()))
|
||||
context=context))
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn(f'插件服务正在退出,任务终止[{downloader_name}]')
|
||||
return context
|
||||
@@ -1696,10 +1759,10 @@ class DownloaderHelper(_PluginBase):
|
||||
|
||||
def __seeding_batch_for_qbittorrent(self, torrents: List[TorrentDictionary]) -> int:
|
||||
"""
|
||||
qb批量做种
|
||||
qb批量自动做种
|
||||
:return: 做种数
|
||||
"""
|
||||
logger.info('[QB]批量做种开始...')
|
||||
logger.info('[QB]批量自动做种开始...')
|
||||
count = 0
|
||||
if not torrents:
|
||||
return count
|
||||
@@ -1709,12 +1772,12 @@ class DownloaderHelper(_PluginBase):
|
||||
return count
|
||||
if self.__seeding_single_for_qbittorrent(torrent=torrent):
|
||||
count += 1
|
||||
logger.info('[QB]批量做种结束')
|
||||
logger.info('[QB]批量自动做种结束')
|
||||
return count
|
||||
|
||||
def __seeding_single_for_qbittorrent(self, torrent: TorrentDictionary) -> bool:
|
||||
"""
|
||||
qb单个做种
|
||||
qb单个自动做种
|
||||
:return: 是否执行
|
||||
"""
|
||||
if not torrent:
|
||||
@@ -1728,15 +1791,17 @@ class DownloaderHelper(_PluginBase):
|
||||
if not need_seeding:
|
||||
return False
|
||||
torrent.resume()
|
||||
logger.info(f"[QB]单个做种完成: hash = {torrent.get('hash')}, name = {torrent.get('name')}")
|
||||
logger.info(f"[QB]单个自动做种完成: hash = {torrent.get('hash')}, name = {torrent.get('name')}")
|
||||
return True
|
||||
|
||||
def __tagging_batch_for_qbittorrent(self, torrents: List[TorrentDictionary]) -> int:
|
||||
def __tagging_batch_for_qbittorrent(self,
|
||||
qbittorrent: Qbittorrent,
|
||||
torrents: List[TorrentDictionary]) -> int:
|
||||
"""
|
||||
qb批量打标
|
||||
qb批量自动标签
|
||||
:return: 打标数
|
||||
"""
|
||||
logger.info('[QB]批量打标开始...')
|
||||
logger.info('[QB]批量自动标签开始...')
|
||||
count = 0
|
||||
if not torrents:
|
||||
return count
|
||||
@@ -1744,49 +1809,110 @@ class DownloaderHelper(_PluginBase):
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn('插件服务正在退出,子任务终止')
|
||||
return count
|
||||
if self.__tagging_single_for_qbittorrent(torrent=torrent):
|
||||
if self.__tagging_single_for_qbittorrent(qbittorrent=qbittorrent, torrent=torrent):
|
||||
count += 1
|
||||
logger.info('[QB]批量打标结束')
|
||||
logger.info('[QB]批量自动标签结束')
|
||||
return count
|
||||
|
||||
def __tagging_single_for_qbittorrent(self, torrent: TorrentDictionary) -> bool:
|
||||
def __tagging_single_for_qbittorrent(self,
|
||||
qbittorrent: Qbittorrent,
|
||||
torrent: TorrentDictionary) -> bool:
|
||||
"""
|
||||
qb单个打标签
|
||||
qb单个自动标签
|
||||
:return: 是否执行
|
||||
"""
|
||||
if not torrent:
|
||||
return False
|
||||
|
||||
hash_str = torrent.get('hash')
|
||||
# 种子当前已经存在的标签
|
||||
torrent_tags = self.__split_tags(torrent.get('tags'))
|
||||
# 判断种子中是否存在排除的标签
|
||||
if self.__exists_exclude_tag(torrent_tags):
|
||||
return False
|
||||
# 种子的tracker地址
|
||||
tracker_url = self.__parse_tracker_for_qbittorrent(torrent=torrent)
|
||||
if not tracker_url:
|
||||
return False
|
||||
# 获取标签建议
|
||||
site_tag, delete_suggest = self.__consult_site_tag_by_tracker(tracker_url=tracker_url)
|
||||
# 移除建议删除的标签
|
||||
if delete_suggest and len(delete_suggest) > 0:
|
||||
to_deletes = [to_delete for to_delete in delete_suggest if to_delete in torrent_tags]
|
||||
if to_deletes and len(to_deletes) > 0:
|
||||
torrent.remove_tags(to_deletes)
|
||||
# 如果本次不需要打标签
|
||||
if not site_tag or site_tag in torrent_tags:
|
||||
# 需要移除的标签
|
||||
remove_tags = None
|
||||
# 要添加的标签
|
||||
add_tags = []
|
||||
|
||||
# 处理BT/PT标签
|
||||
if "BT" not in torrent_tags and "PT" not in torrent_tags:
|
||||
is_private = self.__check_private_torrent_for_qbittorrent(qbittorrent=qbittorrent, hash_str=hash_str)
|
||||
btpt_tag = "PT" if is_private else "BT"
|
||||
add_tags.append(btpt_tag)
|
||||
|
||||
# 处理站点标签
|
||||
# BT种子与站点无关,故排除BT标签
|
||||
if "BT" not in torrent_tags and "BT" not in add_tags:
|
||||
# 种子的tracker地址
|
||||
tracker_url = self.__parse_tracker_for_qbittorrent(torrent=torrent)
|
||||
if tracker_url:
|
||||
# 获取标签建议
|
||||
site_tag, delete_suggest = self.__consult_site_tag_by_tracker(tracker_url=tracker_url)
|
||||
# 移除建议删除的标签
|
||||
if delete_suggest:
|
||||
remove_tags = [to_delete for to_delete in delete_suggest if to_delete and to_delete in torrent_tags]
|
||||
# 如果本次需要打标签
|
||||
if site_tag and site_tag not in torrent_tags and site_tag not in add_tags:
|
||||
add_tags.append(site_tag)
|
||||
|
||||
if not remove_tags and not add_tags:
|
||||
return False
|
||||
if remove_tags:
|
||||
torrent.remove_tags(tags=remove_tags)
|
||||
# 打标签
|
||||
torrent.add_tags(site_tag)
|
||||
logger.info(f"[QB]单个打标成功: hash = {torrent.get('hash')}, name = {torrent.get('name')}")
|
||||
if add_tags:
|
||||
torrent.add_tags(tags=add_tags)
|
||||
logger.info(f"[QB]单个自动标签成功: hash = {hash_str}, name = {torrent.get('name')}")
|
||||
# Flush 标签
|
||||
self.__flush_torrent_tags_for_qbittorrent(torrent=torrent, remove_tags=remove_tags, add_tags=add_tags)
|
||||
return True
|
||||
|
||||
def __delete_batch_for_qbittorrent(self, qbittorrent: Qbittorrent, torrents: List[TorrentDictionary],
|
||||
deleted_event_data: dict = None) -> int:
|
||||
def __flush_torrent_tags_for_qbittorrent(self, torrent: TorrentDictionary, remove_tags: List[str], add_tags: List[str]):
|
||||
"""
|
||||
qb批量删种
|
||||
qb Flush 标签到种子信息中(即更新内存数据)
|
||||
"""
|
||||
try:
|
||||
if not torrent:
|
||||
return
|
||||
torrent_tags = self.__split_tags(torrent.get('tags'))
|
||||
if remove_tags:
|
||||
for remove_tag in remove_tags:
|
||||
if remove_tag and remove_tag in torrent_tags:
|
||||
torrent_tags.remove(remove_tag)
|
||||
if add_tags:
|
||||
for add_tag in add_tags:
|
||||
if add_tag and add_tag not in torrent_tags:
|
||||
torrent_tags.add(add_tag)
|
||||
tag_str = ', '.join(torrent_tags)
|
||||
torrent.update({'tags': tag_str})
|
||||
except Exception as e:
|
||||
logger.error(f'Flush种子标签异常: {str(e)}', exc_info=True)
|
||||
|
||||
def __check_private_torrent_for_qbittorrent(self,
|
||||
qbittorrent: Qbittorrent,
|
||||
hash_str: str) -> bool:
|
||||
"""
|
||||
qb检查种子是否是私有种子
|
||||
:return: 是否是私有种子
|
||||
"""
|
||||
trackers = qbittorrent.qbc.torrents_trackers(torrent_hash=hash_str)
|
||||
if not trackers:
|
||||
return False
|
||||
for tracker in trackers:
|
||||
if not tracker:
|
||||
continue
|
||||
url = tracker.get("url")
|
||||
status = tracker.get("status")
|
||||
tier = tracker.get("tier")
|
||||
if url in self.__public_tracker_urls and status == 0 and tier == -1:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __delete_batch_for_qbittorrent(self, qbittorrent: Qbittorrent, torrents: List[TorrentDictionary],
|
||||
context: TaskContext) -> int:
|
||||
"""
|
||||
qb批量自动删种
|
||||
:return: 删种数
|
||||
"""
|
||||
logger.info('[QB]批量删种开始...')
|
||||
logger.info('[QB]批量自动删种开始...')
|
||||
count = 0
|
||||
if not torrents:
|
||||
return count
|
||||
@@ -1795,15 +1921,15 @@ class DownloaderHelper(_PluginBase):
|
||||
logger.warn('插件服务正在退出,子任务终止')
|
||||
return count
|
||||
if (self.__delete_single_for_qbittorrent(qbittorrent=qbittorrent, torrent=torrent,
|
||||
deleted_event_data=deleted_event_data)):
|
||||
context=context)):
|
||||
count += 1
|
||||
logger.info('[QB]批量删种结束')
|
||||
logger.info('[QB]批量自动删种结束')
|
||||
return count
|
||||
|
||||
def __delete_single_for_qbittorrent(self, qbittorrent: Qbittorrent, torrent: TorrentDictionary,
|
||||
deleted_event_data: dict = None) -> bool:
|
||||
context: TaskContext) -> bool:
|
||||
"""
|
||||
qb单个删种
|
||||
qb单个自动删种
|
||||
:return: 是否执行
|
||||
"""
|
||||
if not torrent:
|
||||
@@ -1813,10 +1939,10 @@ class DownloaderHelper(_PluginBase):
|
||||
# 判断种子中是否存在排除的标签
|
||||
if self.__exists_exclude_tag(torrent_tags):
|
||||
return False
|
||||
if not self.__check_need_delete_for_qbittorrent(torrent=torrent, deleted_event_data=deleted_event_data):
|
||||
if not self.__check_need_delete_for_qbittorrent(torrent=torrent, context=context):
|
||||
return False
|
||||
qbittorrent.delete_torrents(True, torrent.get('hash'))
|
||||
logger.info(f"[QB]单个删种完成: hash = {torrent.get('hash')}, name = {torrent.get('name')}")
|
||||
logger.info(f"[QB]单个自动删种完成: hash = {torrent.get('hash')}, name = {torrent.get('name')}")
|
||||
return True
|
||||
|
||||
def __run_for_transmission(self, context: TaskContext = None) -> TaskContext:
|
||||
@@ -1834,10 +1960,10 @@ class DownloaderHelper(_PluginBase):
|
||||
return context
|
||||
if not context.is_selected_tr_downloader():
|
||||
return context
|
||||
enable_seeding = True if self.__get_config_item(
|
||||
config_key='tr_enable_seeding') and context.is_enabled_seeding() else False
|
||||
enable_tagging = True if self.__get_config_item(
|
||||
config_key='tr_enable_tagging') and context.is_enabled_tagging() else False
|
||||
enable_seeding = True if self.__get_config_item(
|
||||
config_key='tr_enable_seeding') and context.is_enabled_seeding() else False
|
||||
enable_delete = True if self.__get_config_item(
|
||||
config_key='tr_enable_delete') and context.is_enabled_delete() else False
|
||||
if not enable_seeding and not enable_tagging and not enable_delete:
|
||||
@@ -1859,8 +1985,15 @@ class DownloaderHelper(_PluginBase):
|
||||
|
||||
context.save_result(result=result)
|
||||
|
||||
torrents, error = transmission.get_torrents()
|
||||
if error:
|
||||
# 获取全部种子
|
||||
# 需要 isPrivate 字段判断是否是私有种子
|
||||
arguments = transmission._trarg.copy()
|
||||
is_private_field = "isPrivate"
|
||||
if is_private_field not in arguments:
|
||||
arguments.append(is_private_field)
|
||||
try:
|
||||
torrents = transmission.trc.get_torrents(arguments=arguments)
|
||||
except Exception as e:
|
||||
logger.warn(f'从下载器[{downloader_name}]中获取种子失败,任务终止')
|
||||
return context
|
||||
if not torrents or len(torrents) <= 0:
|
||||
@@ -1877,24 +2010,24 @@ class DownloaderHelper(_PluginBase):
|
||||
return context
|
||||
|
||||
logger.info(
|
||||
f'子任务执行状态: 自动做种={enable_seeding}, 自动打标={enable_tagging}, 自动删种={enable_delete}')
|
||||
f'子任务执行状态: 自动标签={enable_tagging}, 自动做种={enable_seeding}, 自动删种={enable_delete}')
|
||||
|
||||
# 做种
|
||||
if enable_seeding:
|
||||
result.set_seeding(self.__seeding_batch_for_transmission(transmission=transmission, torrents=torrents))
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn(f'插件服务正在退出,任务终止[{downloader_name}]')
|
||||
return context
|
||||
# 打标
|
||||
# 自动标签
|
||||
if enable_tagging:
|
||||
result.set_tagging(self.__tagging_batch_for_transmission(transmission=transmission, torrents=torrents))
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn(f'插件服务正在退出,任务终止[{downloader_name}]')
|
||||
return context
|
||||
# 删种
|
||||
# 自动做种
|
||||
if enable_seeding:
|
||||
result.set_seeding(self.__seeding_batch_for_transmission(transmission=transmission, torrents=torrents))
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn(f'插件服务正在退出,任务终止[{downloader_name}]')
|
||||
return context
|
||||
# 自动删种
|
||||
if enable_delete:
|
||||
result.set_delete(self.__delete_batch_for_transmission(transmission=transmission, torrents=torrents,
|
||||
deleted_event_data=context.get_deleted_event_data()))
|
||||
context=context))
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn(f'插件服务正在退出,任务终止[{downloader_name}]')
|
||||
return context
|
||||
@@ -1907,10 +2040,10 @@ class DownloaderHelper(_PluginBase):
|
||||
|
||||
def __seeding_batch_for_transmission(self, transmission: Transmission, torrents: List[Torrent]) -> int:
|
||||
"""
|
||||
tr批量做种
|
||||
tr批量自动做种
|
||||
:return: 做种数
|
||||
"""
|
||||
logger.info('[TR]批量做种开始...')
|
||||
logger.info('[TR]批量自动做种开始...')
|
||||
count = 0
|
||||
if not torrents:
|
||||
return count
|
||||
@@ -1920,12 +2053,12 @@ class DownloaderHelper(_PluginBase):
|
||||
return count
|
||||
if self.__seeding_single_for_transmission(transmission=transmission, torrent=torrent):
|
||||
count += 1
|
||||
logger.info('[TR]批量做种结束')
|
||||
logger.info('[TR]批量自动做种结束')
|
||||
return count
|
||||
|
||||
def __seeding_single_for_transmission(self, transmission: Transmission, torrent: Torrent) -> bool:
|
||||
"""
|
||||
tr单个做种
|
||||
tr单个自动做种
|
||||
:return: 是否执行
|
||||
"""
|
||||
if not torrent:
|
||||
@@ -1939,15 +2072,15 @@ class DownloaderHelper(_PluginBase):
|
||||
if not need_seeding:
|
||||
return False
|
||||
transmission.start_torrents(torrent.hashString)
|
||||
logger.info(f"[TR]单个做种完成: hash = {torrent.hashString}, name = {torrent.get('name')}")
|
||||
logger.info(f"[TR]单个自动做种完成: hash = {torrent.hashString}, name = {torrent.get('name')}")
|
||||
return True
|
||||
|
||||
def __tagging_batch_for_transmission(self, transmission: Transmission, torrents: List[Torrent]) -> int:
|
||||
"""
|
||||
tr批量打标
|
||||
tr批量自动标签
|
||||
:return: 打标数
|
||||
"""
|
||||
logger.info('[TR]批量打标开始...')
|
||||
logger.info('[TR]批量自动标签开始...')
|
||||
count = 0
|
||||
if not torrents:
|
||||
return count
|
||||
@@ -1957,52 +2090,89 @@ class DownloaderHelper(_PluginBase):
|
||||
return count
|
||||
if self.__tagging_single_for_transmission(transmission=transmission, torrent=torrent):
|
||||
count += 1
|
||||
logger.info('[TR]批量打标结束')
|
||||
logger.info('[TR]批量自动标签结束')
|
||||
return count
|
||||
|
||||
def __tagging_single_for_transmission(self, transmission: Transmission, torrent: Torrent) -> bool:
|
||||
"""
|
||||
tr单个打标签
|
||||
tr单个自动标签
|
||||
:return: 是否执行
|
||||
"""
|
||||
if not torrent:
|
||||
return False
|
||||
|
||||
hash_str = torrent.hashString
|
||||
# 种子当前已经存在的标签
|
||||
torrent_tags = torrent.get('labels')
|
||||
# 判断种子中是否存在排除的标签
|
||||
if self.__exists_exclude_tag(torrent_tags):
|
||||
return False
|
||||
# 种子的tracker地址
|
||||
tracker_url = self.__parse_tracker_for_transmission(torrent=torrent)
|
||||
if not tracker_url:
|
||||
return False
|
||||
# 获取标签建议
|
||||
site_tag, delete_suggest = self.__consult_site_tag_by_tracker(tracker_url=tracker_url)
|
||||
# 种子标签副本
|
||||
torrent_tags_copy = torrent_tags.copy()
|
||||
# 移除建议删除的标签
|
||||
if delete_suggest and len(delete_suggest) > 0:
|
||||
for to_delete in delete_suggest:
|
||||
if to_delete and to_delete in torrent_tags_copy:
|
||||
torrent_tags_copy.remove(to_delete)
|
||||
# 如果本次需要打标签
|
||||
if site_tag and site_tag not in torrent_tags_copy:
|
||||
torrent_tags_copy.append(site_tag)
|
||||
torrent_tags = torrent.get('labels') or []
|
||||
# 需要移除的标签
|
||||
remove_tags = None
|
||||
# 要添加的标签
|
||||
add_tags = []
|
||||
|
||||
# 处理BT/PT标签
|
||||
if "BT" not in torrent_tags and "PT" not in torrent_tags:
|
||||
is_private = self.__check_private_torrent_for_transmission(torrent=torrent)
|
||||
btpt_tag = "PT" if is_private else "BT"
|
||||
add_tags.append(btpt_tag)
|
||||
|
||||
# 处理站点标签
|
||||
# BT种子与站点无关,故排除BT标签
|
||||
if "BT" not in torrent_tags and "BT" not in add_tags:
|
||||
# 种子的tracker地址
|
||||
tracker_url = self.__parse_tracker_for_transmission(torrent=torrent)
|
||||
if tracker_url:
|
||||
# 获取标签建议
|
||||
site_tag, delete_suggest = self.__consult_site_tag_by_tracker(tracker_url=tracker_url)
|
||||
# 移除建议删除的标签
|
||||
if delete_suggest:
|
||||
remove_tags = [to_delete for to_delete in delete_suggest if to_delete and to_delete in torrent_tags]
|
||||
# 如果本次需要打标签
|
||||
if site_tag and site_tag not in torrent_tags and site_tag not in add_tags:
|
||||
add_tags.append(site_tag)
|
||||
|
||||
# 如果没有变化就不继续保存
|
||||
if torrent_tags_copy == torrent_tags:
|
||||
if not remove_tags and not add_tags:
|
||||
return False
|
||||
torrent_tags_copy = torrent_tags.copy()
|
||||
if remove_tags:
|
||||
for remove_tag in remove_tags:
|
||||
torrent_tags_copy.remove(remove_tag)
|
||||
if add_tags:
|
||||
for add_tag in add_tags:
|
||||
torrent_tags_copy.append(add_tag)
|
||||
# 保存标签
|
||||
transmission.set_torrent_tag(torrent.hashString, torrent_tags_copy)
|
||||
logger.info(f"[TR]单个打标成功: hash = {torrent.hashString}, name = {torrent.get('name')}")
|
||||
transmission.set_torrent_tag(hash_str, torrent_tags_copy)
|
||||
logger.info(f"[TR]单个自动标签成功: hash = {hash_str}, name = {torrent.get('name')}")
|
||||
# Flush 标签
|
||||
self.__flush_torrent_tags_for_transmission(torrent=torrent, tags=torrent_tags_copy)
|
||||
return True
|
||||
|
||||
def __delete_batch_for_transmission(self, transmission: Transmission, torrents: List[Torrent],
|
||||
deleted_event_data: dict = None) -> int:
|
||||
def __flush_torrent_tags_for_transmission(self, torrent: Torrent, tags: List[str]):
|
||||
"""
|
||||
tr批量删种
|
||||
tr Flush 标签到种子信息中(即更新内存数据)
|
||||
"""
|
||||
try:
|
||||
if not torrent:
|
||||
return
|
||||
torrent.fields.update({'labels': tags})
|
||||
except Exception as e:
|
||||
logger.error(f'Flush种子标签异常: {str(e)}', exc_info=True)
|
||||
|
||||
def __check_private_torrent_for_transmission(self,
|
||||
torrent: Torrent) -> bool:
|
||||
"""
|
||||
tr检查种子是否是私有种子
|
||||
:return: 是否是私有种子
|
||||
"""
|
||||
return torrent.get("isPrivate")
|
||||
|
||||
def __delete_batch_for_transmission(self, transmission: Transmission, torrents: List[Torrent],
|
||||
context: TaskContext) -> int:
|
||||
"""
|
||||
tr批量自动删种
|
||||
:return: 删种数
|
||||
"""
|
||||
logger.info('[TR]批量删种开始...')
|
||||
logger.info('[TR]批量自动删种开始...')
|
||||
count = 0
|
||||
if not torrents:
|
||||
return count
|
||||
@@ -2011,15 +2181,15 @@ class DownloaderHelper(_PluginBase):
|
||||
logger.warn('插件服务正在退出,子任务终止')
|
||||
return count
|
||||
if (self.__delete_single_for_transmission(transmission=transmission, torrent=torrent,
|
||||
deleted_event_data=deleted_event_data)):
|
||||
context=context)):
|
||||
count += 1
|
||||
logger.info('[TR]批量删种结束')
|
||||
logger.info('[TR]批量自动删种结束')
|
||||
return count
|
||||
|
||||
def __delete_single_for_transmission(self, transmission: Transmission, torrent: Torrent,
|
||||
deleted_event_data: dict = None) -> bool:
|
||||
context: TaskContext) -> bool:
|
||||
"""
|
||||
tr单个删种
|
||||
tr单个自动删种
|
||||
:return: 是否执行
|
||||
"""
|
||||
if not torrent:
|
||||
@@ -2029,10 +2199,10 @@ class DownloaderHelper(_PluginBase):
|
||||
# 判断种子中是否存在排除的标签
|
||||
if self.__exists_exclude_tag(torrent_tags):
|
||||
return False
|
||||
if not self.__check_need_delete_for_transmission(torrent=torrent, deleted_event_data=deleted_event_data):
|
||||
if not self.__check_need_delete_for_transmission(torrent=torrent, context=context):
|
||||
return False
|
||||
transmission.delete_torrents(True, torrent.hashString)
|
||||
logger.info(f"'[TR]单个删种完成: hash = {torrent.hashString}, name = {torrent.get('name')}")
|
||||
logger.info(f"'[TR]单个自动删种完成: hash = {torrent.hashString}, name = {torrent.get('name')}")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
@@ -2504,7 +2674,7 @@ class DownloaderHelper(_PluginBase):
|
||||
'fixed-header': True,
|
||||
'density': 'compact',
|
||||
'style': {
|
||||
'height': '230px'
|
||||
'height': '242px'
|
||||
}
|
||||
},
|
||||
'content': [
|
||||
@@ -2627,21 +2797,24 @@ class DownloaderHelper(_PluginBase):
|
||||
}]
|
||||
}
|
||||
|
||||
def __build_dashboard_speed_widget_list_item_element(self, mdi_icon: str, label: str, value: str) -> dict:
|
||||
def __build_dashboard_speed_widget_list_item_element(self, mdi_icon: str, label: str, value: str, is_last: bool = False) -> dict:
|
||||
"""
|
||||
构造仪表板实时速率组件列表item元素
|
||||
"""
|
||||
if not mdi_icon or not label or not value:
|
||||
return None
|
||||
div_style = {
|
||||
'display': 'grid',
|
||||
'grid-template-areas': '"prepend content append"',
|
||||
'grid-template-columns': 'max-content 1fr auto',
|
||||
'padding-bottom': '16px'
|
||||
}
|
||||
if is_last:
|
||||
del div_style['padding-bottom']
|
||||
return {
|
||||
'component': 'div',
|
||||
'props': {
|
||||
'style': {
|
||||
'display': 'grid',
|
||||
'grid-template-areas': '"prepend content append"',
|
||||
'grid-template-columns': 'max-content 1fr auto',
|
||||
'padding-bottom': '16px'
|
||||
}
|
||||
'style': div_style
|
||||
},
|
||||
'content': [{
|
||||
'component': 'div',
|
||||
@@ -2700,13 +2873,13 @@ class DownloaderHelper(_PluginBase):
|
||||
list_items = [
|
||||
self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-cloud-upload', label='总上传量', value=data.upload_size),
|
||||
self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-download-box', label='总下载量', value=data.download_size),
|
||||
self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-content-save', label='磁盘剩余空间', value=data.free_space),
|
||||
self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-content-save', label='磁盘剩余空间', value=data.free_space, is_last=True),
|
||||
]
|
||||
return [{
|
||||
'component': 'div',
|
||||
'props': {
|
||||
'style': {
|
||||
'padding': '16px 0 20px 0'
|
||||
'padding': '16px 0 0 0'
|
||||
}
|
||||
},
|
||||
'content': [{
|
||||
@@ -2739,12 +2912,12 @@ class DownloaderHelper(_PluginBase):
|
||||
监听下载添加事件
|
||||
"""
|
||||
logger.info('监听到下载添加事件')
|
||||
if not event or not event.event_data:
|
||||
logger.warn('事件信息无效,忽略事件')
|
||||
return
|
||||
if not self.get_state() or not self.__get_config_item(config_key='listen_download_event'):
|
||||
logger.warn('插件状态无效或未开启监听,忽略事件')
|
||||
return
|
||||
if not event or not event.event_data:
|
||||
logger.warn('事件信息无效,忽略事件')
|
||||
return
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn('插件服务正在退出,忽略事件')
|
||||
return
|
||||
@@ -2769,12 +2942,12 @@ class DownloaderHelper(_PluginBase):
|
||||
监听源文件删除事件
|
||||
"""
|
||||
logger.info('监听到源文件删除事件')
|
||||
if not event or not event.event_data:
|
||||
logger.warn('事件信息无效,忽略事件')
|
||||
return
|
||||
if not self.get_state() or not self.__get_config_item(config_key='listen_source_file_event'):
|
||||
logger.warn('插件状态无效或未开启监听,忽略事件')
|
||||
return
|
||||
if not event or not event.event_data:
|
||||
logger.warn('事件信息无效,忽略事件')
|
||||
return
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn('插件服务正在退出,忽略事件')
|
||||
return
|
||||
@@ -2784,6 +2957,41 @@ class DownloaderHelper(_PluginBase):
|
||||
context = TaskContext().enable_seeding(False) \
|
||||
.enable_tagging(False) \
|
||||
.enable_delete(True) \
|
||||
.set_deleted_event_data(event.event_data)
|
||||
.set_download_file_deleted_event_data(event.event_data)
|
||||
self.__block_run(context=context)
|
||||
logger.info('源文件删除事件监听任务执行结束')
|
||||
|
||||
@eventmanager.register(EventType.DownloadDeleted)
|
||||
def listen_download_deleted_event(self, event: Event = None):
|
||||
"""
|
||||
监听下载任务删除事件
|
||||
"""
|
||||
logger.info('监听到下载任务删除事件')
|
||||
if not self.get_state() or not self.__get_config_item(config_key='listen_source_file_event'):
|
||||
logger.warn('插件状态无效或未开启监听,忽略事件')
|
||||
return
|
||||
if not event or not event.event_data:
|
||||
logger.warn('事件信息无效,忽略事件')
|
||||
return
|
||||
torrents = event.event_data.get("torrents")
|
||||
if not torrents:
|
||||
logger.warn('事件信息无效,忽略事件')
|
||||
return
|
||||
torrents = [torrent for torrent in torrents if torrent]
|
||||
if not torrents:
|
||||
logger.warn('事件信息无效,忽略事件')
|
||||
return
|
||||
if self.__exit_event.is_set():
|
||||
logger.warn('插件服务正在退出,忽略事件')
|
||||
return
|
||||
# 执行
|
||||
logger.info('下载任务删除事件监听任务执行开始...')
|
||||
# 针对下载任务删除事件只需要处理删种
|
||||
# 删除的种子信息
|
||||
torrent_info = torrents[0]
|
||||
context = TaskContext().enable_seeding(False) \
|
||||
.enable_tagging(False) \
|
||||
.enable_delete(True) \
|
||||
.set_download_deleted_event_data(torrent_info)
|
||||
self.__block_run(context=context)
|
||||
logger.info('下载任务删除事件监听任务执行结束')
|
||||
|
||||
@@ -69,6 +69,8 @@ class StateConvertor(IConvertor, metaclass=Singleton):
|
||||
return '等待'
|
||||
if data == TorrentState.CHECKING_DOWNLOAD.value:
|
||||
return '校验'
|
||||
if data == TorrentState.QUEUED_DOWNLOAD.value:
|
||||
return '排队'
|
||||
# tr
|
||||
if data == 6:
|
||||
return '做种'
|
||||
|
||||
@@ -95,7 +95,9 @@ class TaskContext:
|
||||
self.__selected_torrents = None
|
||||
|
||||
# 源文件删除事件数据
|
||||
self.__deleted_event_data = None
|
||||
self.__download_file_deleted_event_data = None
|
||||
# 下载任务删除事件数据
|
||||
self.__download_deleted_event_data = None
|
||||
|
||||
# 任务结果集
|
||||
self.__results: Optional[List[TaskResult]] = None
|
||||
@@ -226,18 +228,31 @@ class TaskContext:
|
||||
"""
|
||||
return self.__selected_torrents
|
||||
|
||||
def set_deleted_event_data(self, deleted_event_data: dict):
|
||||
def set_download_file_deleted_event_data(self, download_file_deleted_event_data: dict):
|
||||
"""
|
||||
设置源文件删除事件数据
|
||||
"""
|
||||
self.__deleted_event_data = deleted_event_data
|
||||
self.__download_file_deleted_event_data = download_file_deleted_event_data
|
||||
return self
|
||||
|
||||
def get_deleted_event_data(self) -> dict:
|
||||
def get_download_file_deleted_event_data(self) -> dict:
|
||||
"""
|
||||
获取源文件删除事件数据
|
||||
"""
|
||||
return self.__deleted_event_data
|
||||
return self.__download_file_deleted_event_data
|
||||
|
||||
def set_download_deleted_event_data(self, download_deleted_event_data: dict):
|
||||
"""
|
||||
设置下载任务删除事件数据
|
||||
"""
|
||||
self.__download_deleted_event_data = download_deleted_event_data
|
||||
return self
|
||||
|
||||
def get_download_deleted_event_data(self) -> dict:
|
||||
"""
|
||||
获取下载任务删除事件数据
|
||||
"""
|
||||
return self.__download_deleted_event_data
|
||||
|
||||
def save_result(self, result: TaskResult):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user