Merge pull request #358 from hotlcc/develop-20240603

This commit is contained in:
jxxghp
2024-06-03 20:01:09 +08:00
committed by GitHub
4 changed files with 389 additions and 163 deletions

View File

@@ -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及以上版本,否则插件功能异常!",

View File

@@ -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('下载任务删除事件监听任务执行结束')

View File

@@ -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 '做种'

View File

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