Merge remote-tracking branch 'origin/main'

This commit is contained in:
jxxghp
2024-06-08 11:33:30 +08:00
10 changed files with 551 additions and 262 deletions

View File

@@ -30,11 +30,12 @@
"name": "站点数据统计",
"description": "自动统计和展示站点数据。",
"labels": "站点,仪表板",
"version": "3.5",
"version": "3.6",
"icon": "statistic.png",
"author": "lightolly",
"level": 2,
"history": {
"v3.6": "支持站点数据统计刷新后触发插件事件",
"v3.5": "站点数据统计支持YemaPT",
"v3.4": "修复馒头站点数据统计",
"v3.3": "支持选择仪表板组件规格",
@@ -75,11 +76,12 @@
"name": "目录监控",
"description": "监控目录文件发生变化时实时整理到媒体库。",
"labels": "文件整理",
"version": "2.2",
"version": "2.3",
"icon": "directory.png",
"author": "jxxghp",
"level": 1,
"history": {
"v2.3": "特殊场景下补充转移成功历史记录",
"v2.2": "更新目录设置说明",
"v2.1": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削",
"v2.0": "增强API安全性",
@@ -134,11 +136,12 @@
"name": "媒体文件同步删除",
"description": "同步删除历史记录、源文件和下载任务。",
"labels": "文件整理",
"version": "1.6",
"version": "1.7",
"icon": "mediasyncdel.png",
"author": "thsrite",
"level": 1,
"history": {
"v1.7": "修复重新整理被一并删除问题",
"v1.6": "修复删除辅种",
"v1.5": "支持手动删除订阅历史记录(本次更新之后的历史)"
}
@@ -446,10 +449,13 @@
"name": "配置中心",
"description": "快速调整部分系统设定。",
"labels": "系统设置",
"version": "2.4",
"version": "2.5",
"icon": "setting.png",
"author": "jxxghp",
"level": 1
"level": 1,
"history": {
"v2.5": "增加Github加速服务器设置项"
}
},
"WorkWechatMsg": {
"name": "企微机器人消息推送",
@@ -641,13 +647,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需要升级至v1.9.3更新后若插件功能异常请重启一次MP。",
"v2.7": "实时速率仪表板样式微调。",
"v2.6": "新增仪表板实时速率组件支持单独展示qb和tr的实时速率tr未测试有问题提Issue并@hotlcc。",
"v2.5": "优化通知类型降低认证级别要求使MP非认证用户可用但无法使用【站点名称优先】功能。主程序需升级至v1.9.2及以上版本,否则插件功能异常!",
@@ -746,11 +753,13 @@
"name": "清理QB无效做种",
"description": "清理已经被站点删除的种子及对应源文件仅支持QB",
"labels": "Qbittorrent",
"version": "1.9",
"version": "2.0",
"icon": "clean_a.png",
"author": "DzAvril",
"level": 1,
"history": {
"v2.1": "1. 修复删除无效做种没有tg通知的问题。2. 检测未工作做种排除已暂停做种",
"v2.0": "修复检测不到无效做种的bug",
"v1.9": "增加自定义需删除做种的tracker的错误信息",
"v1.8": "增加远程命令切换全量通知修复bug",
"v1.7": "修复因消息内容包含'_'导致telegram API调用失败的问题",

View File

@@ -320,11 +320,12 @@ class BrushFlow(_PluginBase):
brush_config = self._brush_config
# 这里先过滤掉已删除的站点并保存,特别注意的是,这里保留了界面选择站点时的顺序,以便后续站点随机刷流或顺序刷流
site_id_to_public_status = {site.get("id"): site.get("public") for site in self.siteshelper.get_indexers()}
brush_config.brushsites = [
site_id for site_id in brush_config.brushsites
if site_id in site_id_to_public_status and not site_id_to_public_status[site_id]
]
if brush_config.brushsites:
site_id_to_public_status = {site.get("id"): site.get("public") for site in self.siteshelper.get_indexers()}
brush_config.brushsites = [
site_id for site_id in brush_config.brushsites
if site_id in site_id_to_public_status and not site_id_to_public_status[site_id]
]
self.__update_config()
@@ -859,8 +860,9 @@ class BrushFlow(_PluginBase):
{
'component': 'VSelect',
'props': {
'chips': True,
'multiple': True,
'chips': True,
'clearable': True,
'model': 'brushsites',
'label': '刷流站点',
'items': site_options

View File

@@ -28,7 +28,7 @@ class CleanInvalidSeed(_PluginBase):
# 插件图标
plugin_icon = "clean_a.png"
# 插件版本
plugin_version = "1.9"
plugin_version = "2.1"
# 插件作者
plugin_author = "DzAvril"
# 作者主页
@@ -174,6 +174,14 @@ class CleanInvalidSeed(_PluginBase):
if event:
event_data = event.event_data
if event_data:
if not (
event_data.get("action") == "detect_invalid_torrents"
or event_data.get("action") == "delete_invalid_torrents"
or event_data.get("action") == "detect_invalid_files"
or event_data.get("action") == "delete_invalid_files"
or event_data.get("action") == "toggle_notify_all"
):
return
self.post_message(
channel=event.event_data.get("channel"),
title="开始执行远程命令...",
@@ -285,9 +293,16 @@ class CleanInvalidSeed(_PluginBase):
# tracker未工作但暂时不能判定为失效做种需人工判断
tracker_not_working_torrents = []
working_tracker_set = set()
exclude_categories = self._exclude_categories.split("\n") if self._exclude_categories else []
exclude_labels = self._exclude_labels.split("\n") if self._exclude_labels else []
error_msgs = self._error_msg + self._custom_error_msg.split("\n") if self._exclude_labels else []
exclude_categories = (
self._exclude_categories.split("\n") if self._exclude_categories else []
)
exclude_labels = (
self._exclude_labels.split("\n") if self._exclude_labels else []
)
custom_msgs = (
self._custom_error_msg.split("\n") if self._custom_error_msg else []
)
error_msgs = self._error_msg + custom_msgs
# 第一轮筛选出所有未工作的种子
for torrent in all_torrents:
trackers = torrent.trackers
@@ -302,8 +317,7 @@ class CleanInvalidSeed(_PluginBase):
is_tracker_working = True
if not (
(tracker.get("status") == 4)
and (tracker.get("msg") in error_msgs)
(tracker.get("status") == 4) and (tracker.get("msg") in error_msgs)
):
is_invalid = False
working_tracker_set.add(tracker_domian)
@@ -311,17 +325,19 @@ class CleanInvalidSeed(_PluginBase):
if is_invalid:
temp_invalid_torrents.append(torrent)
elif not is_tracker_working:
tracker_not_working_torrents.append(torrent)
# 排除已暂停的种子
if not torrent.state_enum.is_paused:
tracker_not_working_torrents.append(torrent)
logger.info(f"初筛共有{len(temp_invalid_torrents)}个无效做种")
# 第二轮筛选出tracker有正常工作种子而当前种子未工作的避免因临时关站或tracker失效导致误删的问题
invalid_torrents = []
# 失效做种但通过种子分类排除的种子
invalid_torrents_exclude_categories = []
# 失效做种但通过种子标签排除的种子
invalid_torrents_exclude_labels = []
deleted_torrents = []
# 将invalid_torrents基本信息保存起来在种子被删除后依然可以打印这些信息
invalid_torrent_tuple_list = []
deleted_torrent_tuple_list = []
for torrent in temp_invalid_torrents:
trackers = torrent.trackers
for tracker in trackers:
@@ -330,14 +346,25 @@ class CleanInvalidSeed(_PluginBase):
tracker_domian = StringUtils.get_url_netloc((tracker.get("url")))[1]
if tracker_domian in working_tracker_set:
# tracker是正常的说明该种子是无效的
invalid_torrents.append(torrent)
invalid_torrent_tuple_list.append(
(
torrent.name,
torrent.category,
torrent.tags,
torrent.size,
tracker_domian,
tracker.msg,
)
)
if self._delete_invalid_torrents:
# 检查种子分类和标签是否排除
is_excluded = False
if torrent.category in exclude_categories:
is_excluded = True
invalid_torrents_exclude_categories.append(torrent)
torrent_labels = [tag.strip() for tag in torrent.tags.split(",")]
torrent_labels = [
tag.strip() for tag in torrent.tags.split(",")
]
for label in torrent_labels:
if label in exclude_labels:
is_excluded = True
@@ -345,33 +372,28 @@ class CleanInvalidSeed(_PluginBase):
if not is_excluded:
# 只删除种子不删除文件,以防其它站点辅种
self._qb.delete_torrents(False, torrent.get("hash"))
deleted_torrents.append(torrent)
deleted_torrent_tuple_list.append(
(
torrent.name,
torrent.category,
torrent.tags,
torrent.size,
tracker_domian,
tracker.msg,
)
)
break
invalid_msg = f"检测到{len(invalid_torrents)}个失效做种\n"
tracker_not_working_msg = (
f"检测到{len(tracker_not_working_torrents)}个tracker未工作做种请检查种子状态\n"
)
invalid_msg = f"检测到{len(invalid_torrent_tuple_list)}个失效做种\n"
tracker_not_working_msg = f"检测到{len(tracker_not_working_torrents)}个tracker未工作做种请检查种子状态\n"
if self._delete_invalid_torrents:
deleted_msg = f"删除{len(deleted_torrents)}个失效种子\n"
deleted_msg = f"删除{len(deleted_torrent_tuple_list)}个失效种子\n"
if len(exclude_categories) != 0:
exclude_categories_msg = (
f"分类排除{len(invalid_torrents_exclude_categories)}个失效种子未删除,请手动处理\n"
)
exclude_categories_msg = f"分类排除{len(invalid_torrents_exclude_categories)}个失效种子未删除,请手动处理\n"
if len(exclude_labels) != 0:
exclude_labels_msg = (
f"标签排除{len(invalid_torrents_exclude_labels)}个失效种子未删除,请手动处理\n"
)
for index in range(len(invalid_torrents)):
torrent = invalid_torrents[index]
trackers = torrent.trackers
tracker_msg = ""
for tracker in trackers:
if tracker.get("tier") == -1:
continue
tracker_domian = StringUtils.get_url_netloc((tracker.get("url")))[1]
tracker_msg += f" {tracker_domian}{tracker.msg} "
invalid_msg += f"{index + 1}. {torrent.name},分类:{torrent.category},标签:{torrent.tags}, 大小:{StringUtils.str_filesize(torrent.size)}Trackers: {tracker_msg}\n"
exclude_labels_msg = f"标签排除{len(invalid_torrents_exclude_labels)}个失效种子未删除,请手动处理\n"
for index in range(len(invalid_torrent_tuple_list)):
torrent = invalid_torrent_tuple_list[index]
invalid_msg += f"{index + 1}. {torrent[0]},分类:{torrent[1]},标签:{torrent[2]}, 大小:{StringUtils.str_filesize(torrent[3])}Trackers: {torrent[4]}{torrent[5]}\n"
for index in range(len(tracker_not_working_torrents)):
torrent = tracker_not_working_torrents[index]
@@ -406,16 +428,9 @@ class CleanInvalidSeed(_PluginBase):
tracker_msg += f" {tracker_domian}{tracker.msg} "
exclude_labels_msg += f"{index + 1}. {torrent.name},分类:{torrent.category},标签:{torrent.tags}, 大小:{StringUtils.str_filesize(torrent.size)}Trackers: {tracker_msg}\n"
for index in range(len(deleted_torrents)):
torrent = deleted_torrents[index]
trackers = torrent.trackers
tracker_msg = ""
for tracker in trackers:
if tracker.get("tier") == -1:
continue
tracker_domian = StringUtils.get_url_netloc((tracker.get("url")))[1]
tracker_msg += f" {tracker_domian}{tracker.msg} "
deleted_msg += f"{index + 1}. {torrent.name},分类:{torrent.category},标签:{torrent.tags}, 大小:{StringUtils.str_filesize(torrent.size)}Trackers: {tracker_msg}\n"
for index in range(len(deleted_torrent_tuple_list)):
torrent = deleted_torrent_tuple_list[index]
deleted_msg += f"{index + 1}. {torrent[0]},分类:{torrent[1]},标签:{torrent[2]}, 大小:{StringUtils.str_filesize(torrent[3])}Trackers: {torrent[4]}{torrent[5]}\n"
# 日志
logger.info(invalid_msg)
@@ -428,34 +443,34 @@ class CleanInvalidSeed(_PluginBase):
logger.info(exclude_labels_msg)
# 通知
if self._notify:
invalid_msg = invalid_msg.replace('_', '\_')
invalid_msg = invalid_msg.replace("_", "\_")
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
text=invalid_msg,
)
if self._notify_all:
tracker_not_working_msg = tracker_not_working_msg.replace('_', '\_')
tracker_not_working_msg = tracker_not_working_msg.replace("_", "\_")
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
text=tracker_not_working_msg,
)
if self._delete_invalid_torrents:
deleted_msg = deleted_msg.replace('_', '\_')
deleted_msg = deleted_msg.replace("_", "\_")
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
text=deleted_msg,
)
if self._notify_all:
exclude_categories_msg = exclude_categories_msg.replace('_', '\_')
exclude_categories_msg = exclude_categories_msg.replace("_", "\_")
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
text=exclude_categories_msg,
)
exclude_labels_msg = exclude_labels_msg.replace('_', '\_')
exclude_labels_msg = exclude_labels_msg.replace("_", "\_")
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
@@ -472,7 +487,9 @@ class CleanInvalidSeed(_PluginBase):
source_paths = []
total_size = 0
deleted_file_cnt = 0
exclude_key_words = self._exclude_keywords.split("\n") if self._exclude_keywords else []
exclude_key_words = (
self._exclude_keywords.split("\n") if self._exclude_keywords else []
)
if not self._download_dirs:
logger.error("未配置下载目录,无法检测未做种无效源文件")
self.post_message(
@@ -493,6 +510,15 @@ class CleanInvalidSeed(_PluginBase):
message = "检测未做种无效源文件:\n"
for source_path_str in source_paths:
source_path = Path(source_path_str)
# 判断source_path是否存在
if not source_path.exists():
logger.error(f"{source_path} 不存在,无法检测未做种无效源文件")
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【检测无效源文件】",
text=f"{source_path} 不存在,无法检测未做种无效源文件",
)
continue
source_files = []
# 获取source_path下的所有文件包括文件夹
for file in source_path.iterdir():
@@ -532,7 +558,7 @@ class CleanInvalidSeed(_PluginBase):
message += f"***已删除无效源文件,释放{StringUtils.str_filesize(total_size)}空间!***\n"
logger.info(message)
if self._notify:
message = message.replace('_', '\_')
message = message.replace("_", "\_")
self.post_message(
mtype=NotificationType.SiteMessage,
title=f"【清理无效做种】",
@@ -818,23 +844,3 @@ class CleanInvalidSeed(_PluginBase):
self._scheduler = None
except Exception as e:
logger.error("退出插件失败:%s" % str(e))
if __name__ == "__main__":
clean = CleanInvalidSeed()
config = {
"enabled": True,
"notify": True,
"download_dirs": "/sata16t/春天:/保种/春天\n/sata16t/观众:/保种/观众\n/sata16t/UB:/保种/UB\n/sata16t/听听歌:/保种/听听歌\n/ssd/Download/shualiu:/Downloads/shualiu",
"delete_invalid_torrents": False,
"delete_invalid_files": False,
"detect_invalid_files": True,
"notify_all": False,
"onlyonce": False,
"cron": "0 0 * * *",
"exclude_keywords": "ABF-075\nIPZZ-002-C_GG5\nIPZZ-061\n.!qB",
"exclude_categories": "电影",
"exclude_labels": "春天",
}
clean.init_plugin(config)
clean.clean_invalid_seed()

View File

@@ -17,7 +17,7 @@ class ConfigCenter(_PluginBase):
# 插件图标
plugin_icon = "setting.png"
# 插件版本
plugin_version = "2.4"
plugin_version = "2.5"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -37,7 +37,8 @@ class ConfigCenter(_PluginBase):
"GITHUB_TOKEN", "API_TOKEN", "TMDB_API_DOMAIN", "TMDB_IMAGE_DOMAIN", "WALLPAPER",
"RECOGNIZE_SOURCE", "SCRAP_FOLLOW_TMDB", "AUTO_DOWNLOAD_USER",
"OCR_HOST", "DOWNLOAD_SUBTITLE", "PLUGIN_MARKET", "MOVIE_RENAME_FORMAT",
"TV_RENAME_FORMAT", "FANART_ENABLE", "DOH_ENABLE", "SEARCH_MULTIPLE_NAME", "META_CACHE_EXPIRE"
"TV_RENAME_FORMAT", "FANART_ENABLE", "DOH_ENABLE", "SEARCH_MULTIPLE_NAME", "META_CACHE_EXPIRE",
"GITHUB_PROXY"
]
def init_plugin(self, config: dict = None):
@@ -333,6 +334,27 @@ class ConfigCenter(_PluginBase):
}
]
},
{
'component': 'VRow',
'content': [
{
"component": "VCol",
"props": {
"cols": 12,
},
"content": [
{
"component": "VTextField",
"props": {
"model": "GITHUB_PROXY",
"label": "Github加速服务器",
"placeholder": "https://mirror.ghproxy.com/"
}
}
]
}
]
},
{
'component': 'VRow',
'content': [

View File

@@ -59,7 +59,7 @@ class DirMonitor(_PluginBase):
# 插件图标
plugin_icon = "directory.png"
# 插件版本
plugin_version = "2.2"
plugin_version = "2.3"
# 插件作者
plugin_author = "jxxghp"
# 作者主页
@@ -174,7 +174,8 @@ class DirMonitor(_PluginBase):
try:
if target_path and target_path.is_relative_to(Path(mon_path)):
logger.warn(f"{target_path} 是监控目录 {mon_path} 的子目录,无法监控")
self.systemmessage.put(f"{target_path} 是下载目录 {mon_path} 的子目录,无法监控", title="目录监控")
self.systemmessage.put(f"{target_path} 是下载目录 {mon_path} 的子目录,无法监控",
title="目录监控")
continue
except Exception as e:
logger.debug(str(e))
@@ -429,6 +430,20 @@ class DirMonitor(_PluginBase):
return
if not transferinfo.success:
# 判断是否转移后文件已存在,补充转移成功历史记录
if transferinfo.target_path and transferinfo.target_path.exists():
logger.info(f"{file_path.name} 目标文件已存在,补充转移成功历史记录")
# 补充转移成功历史记录
self.transferhis.add_success(
src_path=file_path,
mode=transfer_type,
download_hash=download_hash,
meta=file_meta,
mediainfo=mediainfo,
transferinfo=transferinfo
)
return
# 转移失败
logger.warn(f"{file_path.name} 入库失败:{transferinfo.message}")
# 新增转移失败历史记录

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

View File

@@ -29,7 +29,7 @@ class MediaSyncDel(_PluginBase):
# 插件图标
plugin_icon = "mediasyncdel.png"
# 插件版本
plugin_version = "1.6"
plugin_version = "1.7"
# 插件作者
plugin_author = "thsrite"
# 作者主页
@@ -754,6 +754,20 @@ class MediaSyncDel(_PluginBase):
logger.error(f"{media_name} 同步删除失败,未获取到媒体类型,请检查媒体是否刮削")
return
# 处理路径映射 (处理同一媒体多分辨率的情况)
if self._library_path:
paths = self._library_path.split("\n")
for path in paths:
sub_paths = path.split(":")
if len(sub_paths) < 2:
continue
media_path = media_path.replace(sub_paths[0], sub_paths[1]).replace('\\', '/')
# 兼容重新整理的场景
if Path(media_path).exists():
logger.warn(f"转移路径 {media_path} 未被删除或重新生成,跳过处理")
return
# 查询转移记录
msg, transfer_history = self.__get_transfer_his(media_type=media_type,
media_name=media_name,
@@ -890,15 +904,6 @@ class MediaSyncDel(_PluginBase):
# 类型
mtype = MediaType.MOVIE if media_type in ["Movie", "MOV"] else MediaType.TV
# 处理路径映射 (处理同一媒体多分辨率的情况)
if self._library_path:
paths = self._library_path.split("\n")
for path in paths:
sub_paths = path.split(":")
if len(sub_paths) < 2:
continue
media_path = media_path.replace(sub_paths[0], sub_paths[1]).replace('\\', '/')
# 删除电影
if mtype == MediaType.MOVIE:
msg = f'电影 {media_name} {tmdb_id}'

View File

@@ -43,7 +43,7 @@ class SiteStatistic(_PluginBase):
# 插件图标
plugin_icon = "statistic.png"
# 插件版本
plugin_version = "3.5"
plugin_version = "3.6"
# 插件作者
plugin_author = "lightolly"
# 作者主页
@@ -1440,6 +1440,11 @@ class SiteStatistic(_PluginBase):
# 更新时间
self.save_data("last_update_time", today_date)
self.eventmanager.send_event(etype=EventType.PluginAction, data={
"action": "sitestatistic_refresh_complete"
})
logger.info("站点数据刷新完成")
def __custom_sites(self) -> List[Any]: