From 786de1d8b0e898b0fa1524a1fdd888fa475e500c Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:54:24 +0800 Subject: [PATCH] =?UTF-8?q?fix=20brushflow=20=E5=A2=9E=E5=8A=A0H&R?= =?UTF-8?q?=E5=81=9A=E7=A7=8D=E6=97=B6=E9=97=B4=E3=80=81=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=99=A8=E7=9B=91=E6=8E=A7=E9=85=8D=E7=BD=AE=E9=A1=B9=EF=BC=8C?= =?UTF-8?q?=E5=88=B7=E6=B5=81=E5=89=8D=E7=BD=AE=E6=9D=A1=E4=BB=B6=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E8=B0=83=E6=95=B4=EF=BC=8C=E4=BB=A3=E7=90=86=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E7=A7=8D=E5=AD=90=E9=BB=98=E8=AE=A4=E4=B8=BA=E5=85=B3?= =?UTF-8?q?=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- plugins/brushflow/__init__.py | 209 ++++++++++++++++++++++++++-------- 2 files changed, 164 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 70ebff0..a379a70 100644 --- a/package.json +++ b/package.json @@ -226,7 +226,7 @@ "BrushFlow": { "name": "站点刷流", "description": "自动托管刷流,将会提高对应站点的访问频率。", - "version": "2.4", + "version": "2.5", "icon": "brush.jpg", "author": "jxxghp,InfinityPacer", "level": 2 diff --git a/plugins/brushflow/__init__.py b/plugins/brushflow/__init__.py index f6faa63..36f9196 100644 --- a/plugins/brushflow/__init__.py +++ b/plugins/brushflow/__init__.py @@ -51,6 +51,7 @@ class BrushConfig: self.seeder = config.get("seeder") self.pubtime = config.get("pubtime") self.seed_time = self.__parse_number(config.get("seed_time")) + self.hr_seed_time = self.__parse_number(config.get("hr_seed_time")) self.seed_ratio = self.__parse_number(config.get("seed_ratio")) self.seed_size = self.__parse_number(config.get("seed_size")) self.download_time = self.__parse_number(config.get("download_time")) @@ -65,12 +66,15 @@ class BrushConfig: self.except_tags = config.get("except_tags", True) self.except_subscribe = config.get("except_subscribe", True) self.brush_sequential = config.get("brush_sequential", False) - self.proxy_download = config.get("proxy_download", True) + self.proxy_download = config.get("proxy_download", False) self.proxy_delete = config.get("proxy_delete", False) self.log_more = config.get("log_more", False) self.active_time_range = config.get("active_time_range") - self.enable_site_config = config.get("enable_site_config", False) + self.downloader_monitor = config.get("downloader_monitor") + self.brush_tag = "刷流" + # 站点独立配置 + self.enable_site_config = config.get("enable_site_config", False) self.site_config = config.get("site_config", "[]") self.group_site_configs = {} @@ -91,6 +95,7 @@ class BrushConfig: "seeder", "pubtime", "seed_time", + "hr_seed_time", "seed_ratio", "seed_size", "download_time", @@ -185,7 +190,7 @@ class BrushFlow(_PluginBase): # 插件图标 plugin_icon = "brush.jpg" # 插件版本 - plugin_version = "2.4" + plugin_version = "2.5" # 插件作者 plugin_author = "jxxghp,InfinityPacer" # 作者主页 @@ -701,6 +706,23 @@ class BrushFlow(_PluginBase): } ] }, + { + 'component': 'VCol', + 'props': { + "cols": 12, + "md": 4 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'hr_seed_time', + 'label': 'H&R做种时间(小时)', + 'placeholder': '达到后删除任务' + } + } + ] + }, { 'component': 'VCol', 'props': { @@ -1042,6 +1064,22 @@ class BrushFlow(_PluginBase): { 'component': 'VRow', "content": [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'downloader_monitor', + 'label': '下载器监控', + } + } + ] + }, { 'component': 'VCol', 'props': { @@ -1182,12 +1220,13 @@ class BrushFlow(_PluginBase): "except_tags": True, "except_subscribe": True, "brush_sequential": False, - "proxy_download": True, + "proxy_download": False, "proxy_delete": False, "freeleech": "free", "hr": "yes", "enable_site_config": False, - "log_more": False + "log_more": False, + "downloader_monitor": False, } def get_page(self) -> List[dict]: @@ -1781,7 +1820,7 @@ class BrushFlow(_PluginBase): # 判断能否通过保种体积刷流条件 size_condition_passed, reason = self.__evaluate_size_condition_for_brush(torrents_size=torrents_size, - brush_torrent_size=torrent.size) + add_torrent_size=torrent.size) self.__log_brush_conditions(passed=size_condition_passed, reason=reason, torrent=torrent) if not size_condition_passed: continue @@ -1844,26 +1883,42 @@ class BrushFlow(_PluginBase): return True def __evaluate_size_condition_for_brush(self, torrents_size: float, - brush_torrent_size: float = 0.0) -> Tuple[bool, Optional[str]]: + add_torrent_size: float = 0.0) -> Tuple[bool, Optional[str]]: """ 过滤体积不符合条件的种子 """ - total_size = self.__bytes_to_gb(torrents_size + brush_torrent_size) # 预计总做种体积 + brush_config = self.__get_brush_config() + + # 如果没有明确指定增加的种子大小,则检查配置中是否有种子大小下限,如果有,使用这个大小作为增加的种子大小 + preset_condition = False + if not add_torrent_size and brush_config.size: + size_limits = [float(size) * 1024 ** 3 for size in brush_config.size.split("-")] + add_torrent_size = size_limits[0] # 使用配置的种子大小下限 + preset_condition = True + + total_size = self.__bytes_to_gb(torrents_size + add_torrent_size) # 预计总做种体积 def generate_message(config): - if brush_torrent_size > 0: - return (f"当前做种体积 {self.__bytes_to_gb(torrents_size):.1f} GB," - f"刷流种子 {self.__bytes_to_gb(brush_torrent_size):.1f} GB," - f"预计做种体积 {total_size:.1f} GB,已超过做种体积 {config} GB") + if add_torrent_size: + if preset_condition: + return (f"当前做种体积 {self.__bytes_to_gb(torrents_size):.1f} GB," + f"刷流种子下限 {self.__bytes_to_gb(add_torrent_size):.1f} GB," + f"预计做种体积 {total_size:.1f} GB," + f"超过设定的保种体积 {config} GB,暂时停止新增任务") + else: + return (f"当前做种体积 {self.__bytes_to_gb(torrents_size):.1f} GB," + f"刷流种子大小 {self.__bytes_to_gb(add_torrent_size):.1f} GB," + f"预计做种体积 {total_size:.1f} GB," + f"超过设定的保种体积 {config} GB") else: - return f"当前做种体积 {self.__bytes_to_gb(torrents_size):.1f} GB,已超过做种体积 {config} GB,暂时停止新增任务" + return (f"当前做种体积 {self.__bytes_to_gb(torrents_size):.1f} GB," + f"超过设定的保种体积 {config} GB,暂时停止新增任务") reasons = [ ("disksize", - lambda config: torrents_size + brush_torrent_size > float(config) * 1024 ** 3, generate_message) + lambda config: torrents_size + add_torrent_size > float(config) * 1024 ** 3, generate_message) ] - brush_config = self.__get_brush_config() for condition, check, message in reasons: config_value = getattr(brush_config, condition, None) if config_value and check(config_value): @@ -2021,6 +2076,7 @@ class BrushFlow(_PluginBase): seeding_torrents_dict = {self.__get_hash(torrent): torrent for torrent in seeding_torrents} + # 检查种子刷流标签变更情况 self.__update_seeding_tasks_based_on_tags(torrent_tasks=torrent_tasks, unmanaged_tasks=unmanaged_tasks, seeding_torrents_dict=seeding_torrents_dict) @@ -2096,7 +2152,14 @@ class BrushFlow(_PluginBase): seeding_torrents_dict: Dict[str, Any]): brush_config = self.__get_brush_config() + if brush_config.downloader_monitor: + logger.info("已开启下载器监控,开始同步种子刷流标签记录") + else: + logger.info("没有开启下载器监控,取消同步种子刷流标签记录") + return + if not brush_config.downloader == "qbittorrent": + logger.info("同步种子刷流标签记录目前仅支持qbittorrent") return # 初始化汇总信息 @@ -2116,15 +2179,15 @@ class BrushFlow(_PluginBase): torrent_task = unmanaged_tasks.pop(torrent_hash) torrent_tasks[torrent_hash] = torrent_task added_tasks.append(torrent_task) - logger.info( - f"站点 {torrent_task.get('site_name')},刷流任务种子再次加入:{torrent_task.get('title')}|{torrent_task.get('description')}") + logger.info(f"站点 {torrent_task.get('site_name')}," + f"刷流任务种子再次加入:{torrent_task.get('title')}|{torrent_task.get('description')}") else: # 否则,创建一个新的任务 torrent_task = self.__convert_torrent_info_to_task(torrent) torrent_tasks[torrent_hash] = torrent_task added_tasks.append(torrent_task) - logger.info( - f"站点 {torrent_task.get('site_name')},刷流任务种子加入:{torrent_task.get('title')}|{torrent_task.get('description')}") + logger.info(f"站点 {torrent_task.get('site_name')}," + f"刷流任务种子加入:{torrent_task.get('title')}|{torrent_task.get('description')}") # 包含刷流标签又在刷流任务中,这里额外处理一个特殊逻辑,就是种子在刷流任务中可能被标记删除但实际上又还在下载器中,这里进行重置 else: torrent_task = torrent_tasks[torrent_hash] @@ -2132,7 +2195,8 @@ class BrushFlow(_PluginBase): torrent_task["deleted"] = False reset_tasks.append(torrent_task) logger.info( - f"站点 {torrent_task.get('site_name')},在下载器中找到已标记删除的刷流任务对应的种子信息,更新刷流任务状态为正常:{torrent_task.get('title')}|{torrent_task.get('description')}") + f"站点 {torrent_task.get('site_name')},在下载器中找到已标记删除的刷流任务对应的种子信息," + f"更新刷流任务状态为正常:{torrent_task.get('title')}|{torrent_task.get('description')}") else: # 不包含刷流标签但又在刷流任务中,则移除管理 if torrent_hash in torrent_tasks: @@ -2140,8 +2204,8 @@ class BrushFlow(_PluginBase): torrent_task = torrent_tasks.pop(torrent_hash) unmanaged_tasks[torrent_hash] = torrent_task removed_tasks.append(torrent_task) - logger.info( - f"站点 {torrent_task.get('site_name')},刷流任务种子移除:{torrent_task.get('title')}|{torrent_task.get('description')}") + logger.info(f"站点 {torrent_task.get('site_name')}," + f"刷流任务种子移除:{torrent_task.get('title')}|{torrent_task.get('description')}") self.save_data("torrents", torrent_tasks) self.save_data("unmanaged", unmanaged_tasks) @@ -2183,12 +2247,30 @@ class BrushFlow(_PluginBase): return proxy_delete_torrents, not_proxy_delete_torrents - def __evaluate_conditions_for_delete(self, site_name: str, torrent_info: dict) -> Tuple[bool, str]: + def __evaluate_conditions_for_delete(self, site_name: str, torrent_info: dict, torrent_task: dict) \ + -> Tuple[bool, str]: """ 评估删除条件并返回是否应删除种子及其原因 """ brush_config = self.__get_brush_config(sitename=site_name) + reason = "未能满足设置的删除条件" + + # 当配置了H&R做种时间/分享率时,则H&R种子只有达到预期行为时,才会进行删除,如果没有配置H&R做种时间/分享率,则普通种子的删除规则也适用于H&R种子 + # 判断是否为H&R种子并且是否配置了特定的H&R条件 + hit_and_run = torrent_task.get("hit_and_run", False) + hr_specific_conditions_configured = hit_and_run and (brush_config.hr_seed_time or brush_config.seed_ratio) + if hr_specific_conditions_configured: + if (brush_config.hr_seed_time and torrent_info.get("seeding_time") + >= float(brush_config.hr_seed_time) * 3600): + return True, (f"H&R种子,做种时间 {torrent_info.get('seeding_time') / 3600:.1f} 小时," + f"大于 {brush_config.hr_seed_time} 小时") + if brush_config.seed_ratio and torrent_info.get("ratio") >= float(brush_config.seed_ratio): + return True, f"H&R种子,分享率 {torrent_info.get('ratio'):.2f},大于 {brush_config.seed_ratio}" + return False, "H&R种子,未能满足设置的H&R删除条件" + + # 处理其他场景,1. 不是H&R种子;2. 是H&R种子但没有特定条件配置 + reason = reason if not hit_and_run else "H&R种子(未设置H&R条件),未能满足设置的删除条件" if brush_config.seed_time and torrent_info.get("seeding_time") >= float(brush_config.seed_time) * 3600: reason = f"做种时间 {torrent_info.get('seeding_time') / 3600:.1f} 小时,大于 {brush_config.seed_time} 小时" elif brush_config.seed_ratio and torrent_info.get("ratio") >= float(brush_config.seed_ratio): @@ -2205,15 +2287,16 @@ class BrushFlow(_PluginBase): brush_config.seed_inactivetime) * 60: reason = f"未活动时间 {torrent_info.get('iatime') / 60:.0f} 分钟,大于 {brush_config.seed_inactivetime} 分钟" else: - return False, "" + return False, reason - return True, reason + return True, reason if not hit_and_run else "H&R种子(未设置H&R条件)," + reason def __delete_torrent_for_evaluate_conditions(self, torrents: List[Any], torrent_tasks: Dict[str, dict], proxy_delete: bool = False) -> List: """ 根据条件删除种子并获取已删除列表 """ + brush_config = self.__get_brush_config() delete_hashs = [] for torrent in torrents: @@ -2230,13 +2313,17 @@ class BrushFlow(_PluginBase): # 删除种子的具体实现可能会根据实际情况略有不同 should_delete, reason = self.__evaluate_conditions_for_delete(site_name=site_name, - torrent_info=torrent_info) + torrent_info=torrent_info, + torrent_task=torrent_task) if should_delete: delete_hashs.append(torrent_hash) reason = "触发动态删除," + reason if proxy_delete else reason self.__send_delete_message(site_name=site_name, torrent_title=torrent_title, torrent_desc=torrent_desc, reason=reason) logger.info(f"站点:{site_name},{reason},删除种子:{torrent_title}|{torrent_desc}") + else: + if brush_config.log_more: + logger.info(f"站点:{site_name},{reason},不删除种子:{torrent_title}|{torrent_desc}") return delete_hashs @@ -2266,11 +2353,13 @@ class BrushFlow(_PluginBase): # 当总体积未超过最大阈值时,不需要执行删除操作 if total_torrent_size < max_size: logger.info( - f"当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB,上限 {self.__bytes_to_gb(max_size):.1f} GB,下限 {self.__bytes_to_gb(min_size):.1f} GB,未触发动态删除") + f"当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB,上限 {self.__bytes_to_gb(max_size):.1f} GB," + f"下限 {self.__bytes_to_gb(min_size):.1f} GB,未触发动态删除") return [] else: logger.info( - f"当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB,上限 {self.__bytes_to_gb(max_size):.1f} GB,下限 {self.__bytes_to_gb(min_size):.1f} GB,触发动态删除") + f"当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB,上限 {self.__bytes_to_gb(max_size):.1f} GB," + f"下限 {self.__bytes_to_gb(min_size):.1f} GB,触发动态删除") need_delete_hashes = [] @@ -2337,7 +2426,8 @@ class BrushFlow(_PluginBase): reason=reason) logger.info(f"站点:{site_name},{reason},删除种子:{torrent_title}|{torrent_desc}") - msg = f"站点:{','.join(sites_names)}\n内容:已完成 {len(need_delete_hashes)} 个种子删除,当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB\n原因:触发动态删除" + msg = (f"站点:{','.join(sites_names)}\n内容:已完成 {len(need_delete_hashes)} 个种子删除," + f"当前做种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB\n原因:触发动态删除") logger.info(msg) self.__send_message(title="【刷流任务状态更新】", text=msg) @@ -2348,6 +2438,14 @@ class BrushFlow(_PluginBase): """ 处理已经被删除,但是任务记录中还没有被标记删除的种子 """ + brush_config = self.__get_brush_config() + + if brush_config.downloader_monitor: + logger.info("已开启下载器监控,开始同步刷流任务删除记录") + else: + logger.info("没有开启下载器监控,取消同步刷流任务删除记录") + return + # 先通过获取的全量种子,判断已经被删除,但是任务记录中还没有被标记删除的种子 torrent_all_hashes = self.__get_all_hashes(torrents) missing_hashes = [hash_value for hash_value in torrent_check_hashes if hash_value not in torrent_all_hashes] @@ -2481,6 +2579,7 @@ class BrushFlow(_PluginBase): "maxdlspeed": "总下载带宽", "maxdlcount": "同时下载任务数", "seed_time": "做种时间", + "hr_seed_time": "H&R做种时间", "seed_ratio": "分享率", "seed_size": "上传量", "download_time": "下载超时时间", @@ -2550,6 +2649,7 @@ class BrushFlow(_PluginBase): "seeder": brush_config.seeder, "pubtime": brush_config.pubtime, "seed_time": brush_config.seed_time, + "hr_seed_time": brush_config.hr_seed_time, "seed_ratio": brush_config.seed_ratio, "seed_size": brush_config.seed_size, "download_time": brush_config.download_time, @@ -2568,6 +2668,7 @@ class BrushFlow(_PluginBase): "proxy_delete": brush_config.proxy_delete, "log_more": brush_config.log_more, "active_time_range": brush_config.active_time_range, + "downloader_monitor": brush_config.downloader_monitor, "enable_site_config": brush_config.enable_site_config, "site_config": brush_config.site_config } @@ -3301,23 +3402,39 @@ class BrushFlow(_PluginBase): "https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/README.md " "进行配置,请注意,只需要保留实际配置内容(删除这段)\n") config = """[{ - "sitename": "测试站点", - "freeleech": "free", - "hr": "no", - "include": "", - "exclude": "", - "size": "10-500", - "seeder": "1", - "pubtime": "5-120", - "seed_time": 120, - "seed_ratio": "", - "seed_size": "", - "download_time": "", - "seed_avgspeed": "", - "seed_inactivetime": "", - "save_path": "/downloads/site1", - "proxy_download": false -}]""" + "sitename": "站点1", + "seed_time": 96, + "hr_seed_time": 144 + }, { + "sitename": "站点2", + "hr": "yes", + "size": "10-500", + "seeder": "5-10", + "pubtime": "5-120", + "seed_time": 96, + "save_path": "/downloads/site2", + "proxy_download": true, + "hr_seed_time": 144 + }, { + "sitename": "站点3", + "freeleech": "free", + "hr": "yes", + "include": "", + "exclude": "", + "size": "10-500", + "seeder": "1", + "pubtime": "5-120", + "seed_time": 120, + "hr_seed_time": 144, + "seed_ratio": "", + "seed_size": "", + "download_time": "", + "seed_avgspeed": "", + "seed_inactivetime": "", + "save_path": "/downloads/site1", + "proxy_download": false, + "proxy_delete": false, + }]""" return desc + config @staticmethod