From 0b2fb381df523fddb1a028b13a14a28427383211 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 27 Mar 2024 11:53:09 +0800 Subject: [PATCH] fix #129 ide warning --- package.json | 2 +- plugins/brushflow/__init__.py | 656 +++++++++++++++++++--------------- 2 files changed, 361 insertions(+), 297 deletions(-) diff --git a/package.json b/package.json index 0d8bcaa..8072281 100644 --- a/package.json +++ b/package.json @@ -218,7 +218,7 @@ "BrushFlow": { "name": "站点刷流", "description": "自动托管刷流,将会提高对应站点的访问频率。", - "version": "1.7", + "version": "2.0", "icon": "brush.jpg", "author": "jxxghp", "level": 2 diff --git a/plugins/brushflow/__init__.py b/plugins/brushflow/__init__.py index 1a29396..6b1cc12 100644 --- a/plugins/brushflow/__init__.py +++ b/plugins/brushflow/__init__.py @@ -27,10 +27,12 @@ from app.utils.string import StringUtils lock = threading.Lock() + class BrushConfig: """ 刷流配置 """ + def __init__(self, config: dict, process_site_config=True): self.enabled = config.get("enabled", False) self.notify = config.get("notify", True) @@ -70,53 +72,54 @@ class BrushConfig: self.brush_tag = "刷流" self.site_config = config.get("site_config", "[]") self.group_site_configs = {} - + if self.enable_site_config and process_site_config: self.__initialize_site_config() - + def __initialize_site_config(self): - if not self.site_config: - self.group_site_configs = {} - - # 定义允许覆盖的字段列表 - allowed_fields = { - "freeleech", - "hr", - "include", - "exclude", - "size", - "seeder", - "pubtime", - "seed_time", - "seed_ratio", - "seed_size", - "download_time", - "seed_avgspeed", - "seed_inactivetime", - "save_path", - "proxy_download", - "proxy_delete" - # 当新增支持字段时,仅在此处添加字段名 - } - try: - site_configs = json.loads(self.site_config) - self.group_site_configs = {} - for config in site_configs: - sitename = config.get("sitename") - if not sitename: - continue + if not self.site_config: + self.group_site_configs = {} - # 只从站点特定配置中获取允许的字段 - site_specific_config = {key: config[key] for key in allowed_fields & set(config.keys())} + # 定义允许覆盖的字段列表 + allowed_fields = { + "freeleech", + "hr", + "include", + "exclude", + "size", + "seeder", + "pubtime", + "seed_time", + "seed_ratio", + "seed_size", + "download_time", + "seed_avgspeed", + "seed_inactivetime", + "save_path", + "proxy_download", + "proxy_delete" + # 当新增支持字段时,仅在此处添加字段名 + } + try: + site_configs = json.loads(self.site_config) + self.group_site_configs = {} + for config in site_configs: + sitename = config.get("sitename") + if not sitename: + continue + + # 只从站点特定配置中获取允许的字段 + site_specific_config = {key: config[key] for key in allowed_fields & set(config.keys())} + + full_config = {key: getattr(self, key) for key in vars(self) if + key not in ['group_site_configs', 'site_config']} + full_config.update(site_specific_config) + + self.group_site_configs[sitename] = BrushConfig(config=full_config, process_site_config=False) + except Exception as e: + logger.error(f"解析站点配置失败,请检查配置项。错误详情: {e}") + self.group_site_configs = {} - full_config = {key: getattr(self, key) for key in vars(self) if key not in ['group_site_configs', 'site_config']} - full_config.update(site_specific_config) - - self.group_site_configs[sitename] = BrushConfig(config=full_config, process_site_config=False) - except Exception as e: - logger.error(f"解析站点配置失败,请检查配置项。错误详情: {e}") - self.group_site_configs = {} - def get_site_config(self, sitename): """ 根据站点名称获取特定的BrushConfig实例。如果没有找到站点特定的配置,则返回全局的BrushConfig实例。 @@ -124,7 +127,7 @@ class BrushConfig: if not self.enable_site_config: return self return self if not sitename else self.group_site_configs.get(sitename, self) - + @staticmethod def __parse_number(value): if value is None or value == '': # 更精确地检查None或空字符串 @@ -142,8 +145,8 @@ class BrushConfig: else: return number except (ValueError, TypeError): - return 0 - + return 0 + def __format_value(self, v): """ Format the value to mimic JSON serialization. This is now an instance method. @@ -164,14 +167,14 @@ class BrushConfig: # Note the use of self.format_value(v) here to call the instance method attrs_str = ', '.join(f'"{k}": {self.__format_value(v)}' for k, v in attrs.items()) return f'{{ {attrs_str} }}' - + def __repr__(self): return self.__str__() + class BrushFlow(_PluginBase): - # region 全局定义 - + # 插件名称 plugin_name = "站点刷流" # 插件描述 @@ -179,7 +182,7 @@ class BrushFlow(_PluginBase): # 插件图标 plugin_icon = "brush.jpg" # 插件版本 - plugin_version = "1.7" + plugin_version = "2.0" # 插件作者 plugin_author = "jxxghp,InfinityPacer" # 作者主页 @@ -195,6 +198,7 @@ class BrushFlow(_PluginBase): siteshelper = None siteoper = None torrents = None + subscribeoper = None qb = None tr = None # 刷流配置 @@ -208,36 +212,36 @@ class BrushFlow(_PluginBase): # 退出事件 _event = Event() _scheduler = None - + # endregion - def init_plugin(self, config: dict = None): + def init_plugin(self, config: dict = None): logger.info(f"站点刷流服务初始化") self.siteshelper = SitesHelper() self.siteoper = SiteOper() self.torrents = TorrentsChain() self.subscribeoper = SubscribeOper() self._task_brush_enable = False - + if not config: logger.info("站点刷流任务出错,无法获取插件配置") return False # 如果没有站点配置时,增加默认的配置项 if not config.get("site_config"): - config["site_config"] = self.__get_demo_site_config() - + config["site_config"] = self.__get_demo_site_config() + # 如果配置校验没有通过,那么这里修改配置文件后退出 if not self.__validate_and_fix_config(config=config): self._brush_config = BrushConfig(config=config) self._brush_config.enabled = False self.__update_config() return - + self._brush_config = BrushConfig(config=config) 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 = [ @@ -252,7 +256,7 @@ class BrushFlow(_PluginBase): brush_config.clear_task = False brush_config.archive_task = False self.__update_config() - + elif brush_config.archive_task: self.__archive_tasks() brush_config.archive_task = False @@ -260,7 +264,7 @@ class BrushFlow(_PluginBase): # 停止现有任务 self.stop_service() - + if not self.__setup_downloader(): return @@ -277,27 +281,27 @@ class BrushFlow(_PluginBase): # 如果开启&存在站点时,才需要启用后台任务 self._task_brush_enable = brush_config.enabled and brush_config.brushsites - + # brush_config.onlyonce = True # 检查是否启用了一次性任务 if brush_config.onlyonce: self._scheduler = BackgroundScheduler(timezone=settings.TZ) - + logger.info(f"站点刷流Brush服务启动,立即运行一次") self._scheduler.add_job(self.brush, 'date', run_date=datetime.now( tz=pytz.timezone(settings.TZ) ) + timedelta(seconds=3), name="站点刷流Brush服务") - + logger.info(f"站点刷流Check服务启动,立即运行一次") self._scheduler.add_job(self.check, 'date', run_date=datetime.now( tz=pytz.timezone(settings.TZ) ) + timedelta(seconds=3), name="站点刷流Check服务") - + # 关闭一次性开关 brush_config.onlyonce = False self.__update_config() @@ -307,7 +311,7 @@ class BrushFlow(_PluginBase): # 启动服务 self._scheduler.print_jobs() self._scheduler.start() - + def get_state(self) -> bool: brush_config = self.__get_brush_config() return True if brush_config and brush_config.enabled else False @@ -331,11 +335,11 @@ class BrushFlow(_PluginBase): }] """ services = [] - + brush_config = self.__get_brush_config() if not brush_config: return services - + if self._task_brush_enable: logger.info(f"站点刷流Brush定时服务启动,时间间隔 {self._brush_interval} 分钟") services.append({ @@ -345,7 +349,7 @@ class BrushFlow(_PluginBase): "func": self.brush, "kwargs": {"minutes": self._brush_interval} }) - + if brush_config.enabled: logger.info(f"站点刷流Check定时服务启动,时间间隔 {self._check_interval} 分钟") services.append({ @@ -365,7 +369,7 @@ class BrushFlow(_PluginBase): """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 """ - + # 站点的可选项 site_options = [{"title": site.get("name"), "value": site.get("id")} for site in self.siteshelper.get_indexers()] @@ -863,7 +867,7 @@ class BrushFlow(_PluginBase): { 'component': 'VRow', 'content': [ - + ] }, { @@ -975,7 +979,7 @@ class BrushFlow(_PluginBase): { 'component': 'VRow', "content": [ - { + { 'component': 'VCol', 'props': { 'cols': 12, @@ -1106,7 +1110,7 @@ class BrushFlow(_PluginBase): "rows": 16 } } - ] + ] } ] }, @@ -1160,7 +1164,7 @@ class BrushFlow(_PluginBase): torrents = self.get_data("torrents") or {} # 统计数据 statistic_info = self.__get_statistic_info() - + if not torrents: return [ { @@ -1209,7 +1213,8 @@ class BrushFlow(_PluginBase): { 'component': 'td', 'html': f'{data.get("title")}' + - (f'
{data.get("description")}' if data.get("description") else "") + (f'
{data.get("description")}' if data.get( + "description") else "") }, { @@ -1242,7 +1247,7 @@ class BrushFlow(_PluginBase): ] } for data in data_list ] - + # 拼装页面 return [ { @@ -1632,32 +1637,32 @@ class BrushFlow(_PluginBase): except Exception as e: print(str(e)) - #region Brush + # region Brush def brush(self): """ 定时刷流,添加下载任务 """ brush_config = self.__get_brush_config() - + if not brush_config.brushsites or not brush_config.downloader: return - + if not self.__is_current_time_in_range(): logger.info(f"当前不在指定的刷流时间区间内,刷流操作将暂时暂停") return - + with lock: logger.info(f"开始执行刷流任务 ...") - + torrent_tasks: Dict[str, dict] = self.get_data("torrents") or {} torrents_size = self.__calculate_seeding_torrents_size(torrent_tasks=torrent_tasks) - + # 判断能否通过刷流前置条件 pre_condition_passed, reason = self.__evaluate_pre_conditions_for_brush(torrents_size=torrents_size) if not pre_condition_passed: return - + statistic_info = self.__get_statistic_info() # 获取所有站点的信息,并过滤掉不存在的站点 @@ -1672,61 +1677,64 @@ class BrushFlow(_PluginBase): random.shuffle(site_infos) logger.info(f"即将针对站点 {', '.join(site.name for site in site_infos)} 开始刷流") - + # 处理所有站点 for site in site_infos: # 如果站点刷流没有正确响应,说明没有通过前置条件,其他站点也不需要继续刷流了 - if not self.__brush_site_torrents(siteid=site.id, torrent_tasks=torrent_tasks, statistic_info=statistic_info): + if not self.__brush_site_torrents(siteid=site.id, torrent_tasks=torrent_tasks, + statistic_info=statistic_info): logger.info(f"站点 {site.name} 刷流中途结束,停止后续刷流") break else: logger.info(f"站点 {site.name} 刷流完成") - + # 保存数据 self.save_data("torrents", torrent_tasks) # 保存统计数据 self.save_data("statistic", statistic_info) logger.info(f"刷流任务执行完成") - + def __brush_site_torrents(self, siteid, torrent_tasks, statistic_info) -> bool: - + siteinfo = self.siteoper.get(siteid) if not siteinfo: logger.warn(f"站点不存在:{siteid}") return True - + logger.info(f"开始获取站点 {siteinfo.name} 的新种子 ...") torrents = self.torrents.browse(domain=siteinfo.domain) if not torrents: logger.info(f"站点 {siteinfo.name} 没有获取到种子") return True - + brush_config = self.__get_brush_config(sitename=siteinfo.name) - + # 排除包含订阅的种子 if brush_config.except_subscribe: torrents = self.__filter_torrents_contains_subscribe(torrents=torrents) - + # 按发布日期降序排列 torrents.sort(key=lambda x: x.pubdate or '', reverse=True) - + torrents_size = self.__calculate_seeding_torrents_size(torrent_tasks=torrent_tasks) - + logger.info(f"正在准备种子刷流,数量:{len(torrents)}") - + # 过滤种子 for torrent in torrents: # 判断能否通过刷流前置条件 seeding_size = torrents_size + torrent.size - pre_condition_passed, reason = self.__evaluate_pre_conditions_for_brush(torrents_size=seeding_size, include_network_conditions=False) + pre_condition_passed, reason = self.__evaluate_pre_conditions_for_brush(torrents_size=seeding_size, + include_network_conditions=False) if not pre_condition_passed: # logger.info(f"种子没有通过刷流前置条件校验,原因:{reason} 种子:{torrent.title}|{torrent.description}") return False # else: # logger.info(f"种子已通过刷流前置校验,种子:{torrent.title}|{torrent.description}") - + # 判断能否通过刷流条件 - condition_passed, reason = self.__evaluate_conditions_for_brush(torrent=torrent, torrent_tasks=torrent_tasks) + condition_passed, reason = self.__evaluate_conditions_for_brush(torrent=torrent, + torrent_tasks=torrent_tasks) if not condition_passed: # logger.info(f"种子没有通过刷流条件校验,原因:{reason} 种子:{torrent.title}|{torrent.description}") continue @@ -1738,7 +1746,7 @@ class BrushFlow(_PluginBase): if not hash_string: logger.warn(f"{torrent.title} 添加刷流任务失败!") continue - + # 保存任务信息 torrent_tasks[hash_string] = { "site": siteinfo.id, @@ -1773,23 +1781,25 @@ class BrushFlow(_PluginBase): "deleted": False, "time": time.time() } - + # 统计数据 torrents_size += torrent.size statistic_info["count"] += 1 logger.info(f"站点 {siteinfo.name},新增刷流种子下载:{torrent.title}|{torrent.description}") self.__send_add_message(torrent) - + return True - def __evaluate_pre_conditions_for_brush(self, torrents_size: int, include_network_conditions: bool = True) -> Tuple[bool, str]: + def __evaluate_pre_conditions_for_brush(self, torrents_size: float, + include_network_conditions: bool = True) -> Tuple[bool, Optional[str]]: reasons = [ ("maxdlcount", lambda config: self.__get_downloading_count() >= int(config), lambda config: f"当前同时下载任务数已达到最大值 {config},暂时停止新增任务"), ("disksize", lambda config: torrents_size > float(config) * 1024 ** 3, - lambda config: f"当前做种体积 {self.__bytes_to_gb(torrents_size):.1f} GB,已超过保种体积 {config} GB,暂时停止新增任务"), + lambda config: f"当前做种体积 {self.__bytes_to_gb(torrents_size):.1f} GB," + f"已超过保种体积 {config} GB,暂时停止新增任务"), ] - + if include_network_conditions: downloader_info = self.__get_downloader_info() if downloader_info: @@ -1797,11 +1807,13 @@ class BrushFlow(_PluginBase): current_download_speed = downloader_info.download_speed or 0 reasons.extend([ ("maxupspeed", lambda config: current_upload_speed >= float(config) * 1024, - lambda config: f"当前总上传带宽 {StringUtils.str_filesize(current_upload_speed)},已达到最大值 {config} KB/s,暂时停止新增任务"), + lambda config: f"当前总上传带宽 {StringUtils.str_filesize(current_upload_speed)}," + f"已达到最大值 {config} KB/s,暂时停止新增任务"), ("maxdlspeed", lambda config: current_download_speed >= float(config) * 1024, - lambda config: f"当前总下载带宽 {StringUtils.str_filesize(current_download_speed)},已达到最大值 {config} KB/s,暂时停止新增任务"), + lambda config: f"当前总下载带宽 {StringUtils.str_filesize(current_download_speed)}," + f"已达到最大值 {config} KB/s,暂时停止新增任务"), ]) - + brush_config = self.__get_brush_config() for condition, check, message in reasons: config_value = getattr(brush_config, condition, None) @@ -1812,12 +1824,12 @@ class BrushFlow(_PluginBase): return True, None - def __evaluate_conditions_for_brush(self, torrent, torrent_tasks) -> Tuple[bool, str]: + def __evaluate_conditions_for_brush(self, torrent, torrent_tasks) -> Tuple[bool, Optional[str]]: """ 过滤不符合条件的种子 """ brush_config = self.__get_brush_config(torrent.site_name) - + task_key = f"{torrent.site_name}{torrent.title}" if any(task_key == f"{task.get('site_name')}{task.get('title')}" for task in torrent_tasks.values()): return False, "重复种子" @@ -1833,21 +1845,25 @@ class BrushFlow(_PluginBase): return False, "存在H&R" # 包含规则 - if brush_config.include and not (re.search(brush_config.include, torrent.title, re.I) or re.search(brush_config.include, torrent.description, re.I)): + if brush_config.include and not ( + re.search(brush_config.include, torrent.title, re.I) or re.search(brush_config.include, + torrent.description, re.I)): return False, "不符合包含规则" - + # 排除规则 - if brush_config.exclude and (re.search(brush_config.exclude, torrent.title, re.I) or re.search(brush_config.exclude, torrent.description, re.I)): + if brush_config.exclude and ( + re.search(brush_config.exclude, torrent.title, re.I) or re.search(brush_config.exclude, + torrent.description, re.I)): return False, "符合排除规则" - + # 种子大小(GB) if brush_config.size: - sizes = [float(size) * 1024**3 for size in brush_config.size.split("-")] + sizes = [float(size) * 1024 ** 3 for size in brush_config.size.split("-")] if len(sizes) == 1 and torrent.size < sizes[0]: return False, "种子大小不符合条件" elif len(sizes) > 1 and not sizes[0] <= torrent.size <= sizes[1]: return False, "种子大小不在指定范围内" - + # 做种人数 if brush_config.seeder: seeders_range = [int(n) for n in brush_config.seeder.split("-")] @@ -1878,10 +1894,10 @@ class BrushFlow(_PluginBase): return True, None - #endregion - - #region Check - + # endregion + + # region Check + def check(self): """ 定时检查,删除下载任务 @@ -1890,10 +1906,10 @@ class BrushFlow(_PluginBase): if not brush_config.downloader: return - + if not self.__is_current_time_in_range(): - logger.info(f"当前不在指定的刷流时间区间内,检查操作将暂时暂停") - return + logger.info(f"当前不在指定的刷流时间区间内,检查操作将暂时暂停") + return with lock: logger.info("开始检查刷流下载任务 ...") @@ -1904,15 +1920,16 @@ class BrushFlow(_PluginBase): if not downloader: logger.warn("无法获取下载器实例,将在下个时间周期重试") return - + seeding_torrents, error = downloader.get_torrents() if error: logger.warn("连接下载器出错,将在下个时间周期重试") return 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) + + self.__update_seeding_tasks_based_on_tags(torrent_tasks=torrent_tasks, unmanaged_tasks=unmanaged_tasks, + seeding_torrents_dict=seeding_torrents_dict) torrent_check_hashes = list(torrent_tasks.keys()) if not torrent_tasks or not torrent_check_hashes: @@ -1923,42 +1940,46 @@ class BrushFlow(_PluginBase): # 获取到当前所有做种数据中需要被检查的种子数据 check_torrents = [seeding_torrents_dict[th] for th in torrent_check_hashes if th in seeding_torrents_dict] - + # 先更新刷流任务的最新状态,上下传,分享率 self.__update_torrent_tasks_state(torrents=check_torrents, torrent_tasks=torrent_tasks) # 先通过获取的全量种子,判断已经被删除,但是任务记录中还没有被标记删除的种子 - undeleted_hashes = self.__get_undeleted_torrents_missing_in_downloader(torrent_tasks, torrent_check_hashes, check_torrents) or [] - + undeleted_hashes = self.__get_undeleted_torrents_missing_in_downloader(torrent_tasks, torrent_check_hashes, + check_torrents) or [] + # 排除MoviePilot种子 if check_torrents and brush_config.except_tags: - check_torrents = self.__filter_torrents_by_tag(torrents=check_torrents, exclude_tag=settings.TORRENT_TAG) + check_torrents = self.__filter_torrents_by_tag(torrents=check_torrents, + exclude_tag=settings.TORRENT_TAG) need_delete_hashes = [] need_delete_hashes.extend(undeleted_hashes) - + # 如果配置了删种阈值,则根据动态删种进行分组处理 if brush_config.proxy_delete and brush_config.delete_size_range: logger.info("已开启动态删种,按系统默认动态删种条件开始检查任务") - proxy_delete_hashs = self.__delete_torrent_for_proxy(torrents=check_torrents, torrent_tasks=torrent_tasks) or [] + proxy_delete_hashs = self.__delete_torrent_for_proxy(torrents=check_torrents, + torrent_tasks=torrent_tasks) or [] need_delete_hashes.extend(proxy_delete_hashs) # 否则均认为是没有开启动态删种 else: logger.info("没有开启动态删种,按用户设置删种条件开始检查任务") - not_proxy_delete_hashs = self.__delete_torrent_for_evaluate_conditions(torrents=check_torrents, torrent_tasks=torrent_tasks) or [] + not_proxy_delete_hashs = self.__delete_torrent_for_evaluate_conditions(torrents=check_torrents, + torrent_tasks=torrent_tasks) or [] need_delete_hashes.extend(not_proxy_delete_hashs) - + if need_delete_hashes: if downloader.delete_torrents(ids=need_delete_hashes, delete_file=True): for torrent_hash in need_delete_hashes: torrent_tasks[torrent_hash]["deleted"] = True - + self.__update_and_save_statistic_info(torrent_tasks) - + self.save_data("torrents", torrent_tasks) logger.info("刷流下载任务检查完成") - + def __update_torrent_tasks_state(self, torrents: List[Any], torrent_tasks: Dict[str, dict]): """ 更新刷流任务的最新状态,上下传,分享率 @@ -1971,20 +1992,21 @@ class BrushFlow(_PluginBase): continue torrent_info = self.__get_torrent_info(torrent) - + # 更新上传量、下载量 torrent_task.update({ "downloaded": torrent_info.get("downloaded"), "uploaded": torrent_info.get("uploaded"), "ratio": torrent_info.get("ratio"), - }) - - def __update_seeding_tasks_based_on_tags(self, torrent_tasks: Dict[str, dict], unmanaged_tasks: Dict[str, dict], seeding_torrents_dict: Dict[str, Any]): + }) + + def __update_seeding_tasks_based_on_tags(self, torrent_tasks: Dict[str, dict], unmanaged_tasks: Dict[str, dict], + seeding_torrents_dict: Dict[str, Any]): brush_config = self.__get_brush_config() - + if not brush_config.downloader == "qbittorrent": return - + # 基于 seeding_torrents_dict 的信息更新或添加到 torrent_tasks for torrent_hash, torrent in seeding_torrents_dict.items(): tags = self.__get_label(torrent=torrent) @@ -1997,20 +2019,23 @@ class BrushFlow(_PluginBase): # 如果在 unmanaged_tasks 中,移除并转移到 torrent_tasks torrent_task = unmanaged_tasks.pop(torrent_hash) torrent_tasks[torrent_hash] = 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')},种子再次加入刷流任务:{torrent_task.get('title')}|{torrent_task.get('description')}") self.__send_add_message(torrent=torrent_task, title="【刷流任务种子再次加入】") else: # 否则,创建一个新的任务 torrent_task = self.__convert_torrent_info_to_task(torrent) torrent_tasks[torrent_hash] = 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')},种子加入刷流任务:{torrent_task.get('title')}|{torrent_task.get('description')}") self.__send_add_message(torrent=torrent_task, title="【刷流任务种子加入】") # 包含刷流标签又在刷流任务中,这里额外处理一个特殊逻辑,就是种子在刷流任务中可能被标记删除但实际上又还在下载器中,这里进行重置 else: torrent_task = torrent_tasks[torrent_hash] if torrent_task.get("deleted"): torrent_task["deleted"] = False - logger.info(f"站点 {torrent_task.get('site_name')},种子再次加入刷流任务:{torrent_task.get('title')}|{torrent_task.get('description')}") + logger.info( + f"站点 {torrent_task.get('site_name')},种子再次加入刷流任务:{torrent_task.get('title')}|{torrent_task.get('description')}") self.__send_add_message(torrent=torrent_task, title="【刷流任务种子再次加入】") else: # 不包含刷流标签但又在刷流任务中,则移除管理 @@ -2018,16 +2043,17 @@ class BrushFlow(_PluginBase): # 如果种子不符合刷流条件但在 torrent_tasks 中,移除并加入 unmanaged_tasks torrent_task = torrent_tasks.pop(torrent_hash) unmanaged_tasks[torrent_hash] = 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')},种子移除刷流任务:{torrent_task.get('title')}|{torrent_task.get('description')}") self.__send_delete_message(site_name=torrent_task.get("site_name"), - torrent_title=torrent_task.get("title"), - torrent_desc=torrent_task.get("description"), - reason="刷流标签移除", - title="【刷流任务种子移除】") + torrent_title=torrent_task.get("title"), + torrent_desc=torrent_task.get("description"), + reason="刷流标签移除", + title="【刷流任务种子移除】") self.save_data("torrents", torrent_tasks) self.save_data("unmanaged", unmanaged_tasks) - + def __group_torrents_by_proxy_delete(self, torrents: List[Any], torrent_tasks: Dict[str, dict]): """ 根据是否启用动态删种进行分组 @@ -2038,52 +2064,54 @@ class BrushFlow(_PluginBase): for torrent in torrents: torrent_hash = self.__get_hash(torrent) torrent_task = torrent_tasks.get(torrent_hash, None) - + # 如果找不到种子任务,说明不在管理的种子范围内,直接跳过 if not torrent_task: continue - + site_name = torrent_task.get("site_name", "") - torrent_title = torrent_task.get("title", "") - torrent_desc = torrent_task.get("description", "") - + brush_config = self.__get_brush_config(site_name) if brush_config.proxy_delete: proxy_delete_torrents.append(torrent) else: not_proxy_delete_torrents.append(torrent) - + return proxy_delete_torrents, not_proxy_delete_torrents - - def __evaluate_conditions_for_delete(self, site_name: str, torrent_info: str) -> Tuple[bool, str]: + + def __evaluate_conditions_for_delete(self, site_name: str, torrent_info: dict) -> Tuple[bool, str]: """ 评估删除条件并返回是否应删除种子及其原因 - """ + """ brush_config = self.__get_brush_config(sitename=site_name) - + 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): reason = f"分享率 {torrent_info.get('ratio'):.2f},大于 {brush_config.seed_ratio}" - elif brush_config.seed_size and torrent_info.get("uploaded") >= float(brush_config.seed_size) * 1024**3: - reason = f"上传量 {torrent_info.get('uploaded') / 1024**3:.1f} GB,大于 {brush_config.seed_size} GB" - elif brush_config.download_time and torrent_info.get("downloaded") < torrent_info.get("total_size") and torrent_info.get("dltime") >= float(brush_config.download_time) * 3600: + elif brush_config.seed_size and torrent_info.get("uploaded") >= float(brush_config.seed_size) * 1024 ** 3: + reason = f"上传量 {torrent_info.get('uploaded') / 1024 ** 3:.1f} GB,大于 {brush_config.seed_size} GB" + elif brush_config.download_time and torrent_info.get("downloaded") < torrent_info.get( + "total_size") and torrent_info.get("dltime") >= float(brush_config.download_time) * 3600: reason = f"下载耗时 {torrent_info.get('dltime') / 3600:.1f} 小时,大于 {brush_config.download_time} 小时" - elif brush_config.seed_avgspeed and torrent_info.get("avg_upspeed") <= float(brush_config.seed_avgspeed) * 1024 and torrent_info.get("seeding_time") >= 30 * 60: + elif brush_config.seed_avgspeed and torrent_info.get("avg_upspeed") <= float( + brush_config.seed_avgspeed) * 1024 and torrent_info.get("seeding_time") >= 30 * 60: reason = f"平均上传速度 {torrent_info.get('avg_upspeed') / 1024:.1f} KB/s,低于 {brush_config.seed_avgspeed} KB/s" - elif brush_config.seed_inactivetime and torrent_info.get("iatime") >= float(brush_config.seed_inactivetime) * 60: + elif brush_config.seed_inactivetime and torrent_info.get("iatime") >= float( + brush_config.seed_inactivetime) * 60: reason = f"未活动时间 {torrent_info.get('iatime') / 60:.0f} 分钟,大于 {brush_config.seed_inactivetime} 分钟" else: return False, "" return True, reason - - def __delete_torrent_for_evaluate_conditions(self, torrents: List[Any], torrent_tasks: Dict[str, dict], proxy_delete: bool = False) -> List: + + def __delete_torrent_for_evaluate_conditions(self, torrents: List[Any], torrent_tasks: Dict[str, dict], + proxy_delete: bool = False) -> List: """ 根据条件删除种子并获取已删除列表 - """ + """ delete_hashs = [] - + for torrent in torrents: torrent_hash = self.__get_hash(torrent) torrent_task = torrent_tasks.get(torrent_hash, None) @@ -2093,19 +2121,21 @@ class BrushFlow(_PluginBase): site_name = torrent_task.get("site_name", "") torrent_title = torrent_task.get("title", "") torrent_desc = torrent_task.get("description", "") - + torrent_info = self.__get_torrent_info(torrent) # 删除种子的具体实现可能会根据实际情况略有不同 - should_delete, reason = self.__evaluate_conditions_for_delete(site_name=site_name, torrent_info=torrent_info) + should_delete, reason = self.__evaluate_conditions_for_delete(site_name=site_name, + torrent_info=torrent_info) 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) + 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}") return delete_hashs - + def __delete_torrent_for_proxy(self, torrents: List[Any], torrent_tasks: Dict[str, dict]) -> List: """ 支持动态删除种子,当设置了动态删种(全局)和删除阈值时,当保种体积达到删除阈值时,优先按设置规则进行删除,若还没有达到阈值,则排除HR种子后按加入时间倒序进行删除 @@ -2113,56 +2143,68 @@ class BrushFlow(_PluginBase): 删除阈值:50-100,当保种体积 > 100G 时,则开始删除种子,直至降至为 50G """ brush_config = self.__get_brush_config() - + # 如果没有启用动态删除或没有设置删除阈值,则不执行删除操作 if not (brush_config.proxy_delete and brush_config.delete_size_range): return [] - + # 解析删除阈值范围 - sizes = [float(size) * 1024**3 for size in brush_config.delete_size_range.split("-")] + sizes = [float(size) * 1024 ** 3 for size in brush_config.delete_size_range.split("-")] min_size = sizes[0] # 至少需要达到的保种体积 max_size = sizes[1] if len(sizes) > 1 else sizes[0] # 触发删除操作的保种体积上限 - + torrent_info_map = {self.__get_hash(torrent): self.__get_torrent_info(torrent=torrent) for torrent in torrents} - + # 计算当前总保种体积 total_torrent_size = sum(info.get("total_size", 0) for info in torrent_info_map.values()) # 当总体积未超过最大阈值时,不需要执行删除操作 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,未触发动态删除") + 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,未触发动态删除") 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,触发动态删除") - + 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,触发动态删除") + need_delete_hashes = [] - + # 即使开了动态删除,但是也有可能部分站点单独设置了关闭,这里根据种子动态进行分组,先处理不需要动态的种子,按设置的规则进行删除 - proxy_delete_torrents, not_proxy_delete_torrents = self.__group_torrents_by_proxy_delete(torrents=torrents, torrent_tasks=torrent_tasks) + proxy_delete_torrents, not_proxy_delete_torrents = self.__group_torrents_by_proxy_delete(torrents=torrents, + torrent_tasks=torrent_tasks) logger.info(f"托管种子数 {len(proxy_delete_torrents)},未托管种子数 {len(not_proxy_delete_torrents)}") if not_proxy_delete_torrents: - not_proxy_delete_hashes = self.__delete_torrent_for_evaluate_conditions(torrents=not_proxy_delete_torrents, torrent_tasks=torrent_tasks) or [] + not_proxy_delete_hashes = self.__delete_torrent_for_evaluate_conditions(torrents=not_proxy_delete_torrents, + torrent_tasks=torrent_tasks) or [] need_delete_hashes.extend(not_proxy_delete_hashes) - total_torrent_size -= sum(torrent_info_map[self.__get_hash(torrent)].get("total_size", 0) for torrent in not_proxy_delete_torrents if self.__get_hash(torrent) in not_proxy_delete_hashes) - + total_torrent_size -= sum( + torrent_info_map[self.__get_hash(torrent)].get("total_size", 0) for torrent in not_proxy_delete_torrents + if self.__get_hash(torrent) in not_proxy_delete_hashes) + # 如果删除非动态种子后仍未达到最小体积要求,则处理动态种子 if total_torrent_size > min_size and proxy_delete_torrents: - proxy_delete_hashes = self.__delete_torrent_for_evaluate_conditions(torrents=proxy_delete_torrents, torrent_tasks=torrent_tasks, proxy_delete=True) or [] - need_delete_hashes.extend(proxy_delete_hashes) - total_torrent_size -= sum(torrent_info_map[self.__get_hash(torrent)].get("total_size", 0) for torrent in proxy_delete_torrents if self.__get_hash(torrent) in proxy_delete_hashes) + proxy_delete_hashes = self.__delete_torrent_for_evaluate_conditions(torrents=proxy_delete_torrents, + torrent_tasks=torrent_tasks, + proxy_delete=True) or [] + need_delete_hashes.extend(proxy_delete_hashes) + total_torrent_size -= sum( + torrent_info_map[self.__get_hash(torrent)].get("total_size", 0) for torrent in proxy_delete_torrents if + self.__get_hash(torrent) in proxy_delete_hashes) # 在完成初始删除步骤后,如果总体积仍然超过最小阈值,则进一步找到已完成种子并排除HR种子后按加入时间倒序进行删除 if total_torrent_size > min_size: # 重新计算当前的种子列表,排除已删除的种子 - remaining_hashes = {self.__get_hash(torrent) for torrent in proxy_delete_torrents} - set(need_delete_hashes) + remaining_hashes = list( + {self.__get_hash(torrent) for torrent in proxy_delete_torrents} - set(need_delete_hashes)) # 这里根据排除后的种子列表,再次从下载器中找到已完成的任务 downloader = self.__get_downloader(brush_config.downloader) completed_torrents = downloader.get_completed_torrents(ids=remaining_hashes) remaining_hashes = {self.__get_hash(torrent) for torrent in completed_torrents} - remaining_torrents = [(hash, torrent_info_map[hash]) for hash in remaining_hashes] - + remaining_torrents = [(_hash, torrent_info_map[_hash]) for _hash in remaining_hashes] + # 准备一个列表,用于存放满足条件的种子,即非HR种子且有明确加入时间 - filtered_torrents = [(hash, info['add_on']) for hash, info in remaining_torrents if not torrent_tasks[hash].get("hit_and_run", False)] + filtered_torrents = [(_hash, info['add_on']) for _hash, info in remaining_torrents if + not torrent_tasks[_hash].get("hit_and_run", False)] sorted_torrents = sorted(filtered_torrents, key=lambda x: x[1]) # 进行额外的删除操作,直到满足最小阈值或没有更多种子可删除 @@ -2173,24 +2215,25 @@ class BrushFlow(_PluginBase): torrent_info = torrent_info_map.get(torrent_hash, None) if not torrent_task or not torrent_info: continue - + need_delete_hashes.append(torrent_hash) total_torrent_size -= torrent_info.get("total_size", 0) - + site_name = torrent_task.get("site_name", "") torrent_title = torrent_task.get("title", "") torrent_desc = torrent_task.get("description", "") reason = "触发动态删除,系统自动删除" - self.__send_delete_message(site_name=site_name, torrent_title=torrent_title, torrent_desc=torrent_desc, reason=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}") - + msg = f"已完成 {len(need_delete_hashes)} 个种子删除,当前保种体积 {self.__bytes_to_gb(total_torrent_size):.1f} GB" self.post_message(mtype=NotificationType.SiteMessage, title="【刷流任务种子删除】", text=msg) logger.info(msg) - + # 返回所有需要删除的种子的哈希列表 return need_delete_hashes - + def __get_undeleted_torrents_missing_in_downloader(self, torrent_tasks, torrent_check_hashes, torrents) -> List: """ 处理已经被删除,但是任务记录中还没有被标记删除的种子 @@ -2201,7 +2244,7 @@ class BrushFlow(_PluginBase): if not undeleted_hashes: return [] - + # 处理每个符合条件的任务 for hash_value in undeleted_hashes: # 获取对应的任务信息 @@ -2209,8 +2252,9 @@ class BrushFlow(_PluginBase): # 获取site_name和torrent_title site_name = torrent_info.get("site_name", "") torrent_title = torrent_info.get("title", "") - torrent_desc = torrent_info.get("description", "") - self.__send_delete_message(site_name=site_name, torrent_title=torrent_title, torrent_desc=torrent_desc, reason="下载器中找不到种子") + torrent_desc = torrent_info.get("description", "") + self.__send_delete_message(site_name=site_name, torrent_title=torrent_title, torrent_desc=torrent_desc, + reason="下载器中找不到种子") logger.info(f"站点:{site_name},下载器中找不到种子,删除种子:{torrent_title}|{torrent_desc}") return undeleted_hashes @@ -2219,13 +2263,13 @@ class BrushFlow(_PluginBase): 根据torrent_info转换成torrent_task """ torrent_info = self.__get_torrent_info(torrent=torrent) - + trackers = [tracker.get("url") for tracker in (torrent.trackers or []) if - tracker.get("tier", -1) >= 0 and tracker.get("url")] + tracker.get("tier", -1) >= 0 and tracker.get("url")] trackers.append(torrent_info.get("tracker")) - + site_id, site_name = self.__get_site_by_tracker(trackers=trackers) - + torrent_task = { "site": site_id, "site_name": site_name, @@ -2250,8 +2294,8 @@ class BrushFlow(_PluginBase): } return torrent_task - #endregion - + # endregion + def __update_and_save_statistic_info(self, torrent_tasks): """ 更新并保存统计信息 @@ -2262,7 +2306,7 @@ class BrushFlow(_PluginBase): statistic_info = self.__get_statistic_info() archived_tasks = self.get_data("archived") or {} combined_tasks = {**torrent_tasks, **archived_tasks} - + for task in combined_tasks.values(): if task.get("deleted", False): total_deleted += 1 @@ -2292,11 +2336,11 @@ class BrushFlow(_PluginBase): }) logger.info(f"刷流任务统计数据:总任务数:{total_count},活跃任务数:{active_count},已删除:{total_deleted}," - f"待归档:{total_unarchived}," - f"活跃上传量:{StringUtils.str_filesize(active_uploaded)}," - f"活跃下载量:{StringUtils.str_filesize(active_downloaded)}," - f"总上传量:{StringUtils.str_filesize(total_uploaded)}," - f"总下载量:{StringUtils.str_filesize(total_downloaded)}") + f"待归档:{total_unarchived}," + f"活跃上传量:{StringUtils.str_filesize(active_uploaded)}," + f"活跃下载量:{StringUtils.str_filesize(active_downloaded)}," + f"总上传量:{StringUtils.str_filesize(total_uploaded)}," + f"总下载量:{StringUtils.str_filesize(total_downloaded)}") self.save_data("statistic", statistic_info) self.save_data("torrents", torrent_tasks) @@ -2310,7 +2354,7 @@ class BrushFlow(_PluginBase): def __validate_and_fix_config(self, config: dict = None) -> bool: """ 检查并修正配置值 - """ + """ if config is None: logger.error("配置为None,无法验证和修正") return False @@ -2332,14 +2376,14 @@ class BrushFlow(_PluginBase): "up_speed": "单任务上传限速", "dl_speed": "单任务下载限速" } - + config_range_number_attr_to_desc = { "pubtime": "发布时间", "size": "种子大小", "seeder": "做种人数", "delete_size_range": "动态删种阈值" } - + for attr, desc in config_number_attr_to_desc.items(): value = config.get(attr) if value and not self.__is_number(value): @@ -2354,13 +2398,13 @@ class BrushFlow(_PluginBase): self.__log_and_notify_error(f"站点刷流任务出错,{desc}设置错误:{value}") config[attr] = None found_error = True # 更新错误标志 - - active_time_range = config.get("active_time_range") - if active_time_range and not self.__is_valid_time_range(time_range=active_time_range): + + active_time_range = config.get("active_time_range") + if active_time_range and not self.__is_valid_time_range(time_range=active_time_range): self.__log_and_notify_error(f"站点刷流任务出错,开启时间段设置错误:{active_time_range}") config["active_time_range"] = None - found_error = True # 更新错误标志 - + found_error = True # 更新错误标志 + # 如果发现任何错误,返回False;否则返回True return not found_error @@ -2370,10 +2414,10 @@ class BrushFlow(_PluginBase): """ if brush_config is None: brush_config = self._brush_config - + if brush_config is None: return - + # 创建一个将配置属性名称映射到BrushConfig属性值的字典 config_mapping = { "onlyonce": brush_config.onlyonce, @@ -2413,7 +2457,7 @@ class BrushFlow(_PluginBase): "enable_site_config": brush_config.enable_site_config, "site_config": brush_config.site_config } - + # 使用update_config方法或其等效方法更新配置 self.update_config(config_mapping) @@ -2422,19 +2466,19 @@ class BrushFlow(_PluginBase): 根据下载器类型初始化下载器实例 """ brush_config = self.__get_brush_config() - + if brush_config.downloader == "qbittorrent": self.qb = Qbittorrent() if self.qb.is_inactive(): self.__log_and_notify_error("站点刷流任务出错:Qbittorrent未连接") return False - + elif brush_config.downloader == "transmission": self.tr = Transmission() if self.tr.is_inactive(): self.__log_and_notify_error("站点刷流任务出错:Transmission未连接") return False - + return True def __get_downloader(self, dtype: str) -> Optional[Union[Transmission, Qbittorrent]]: @@ -2510,7 +2554,7 @@ class BrushFlow(_PluginBase): if not torrent.enclosure: logger.error(f"获取下载链接失败:{torrent.title}") return None - + brush_config = self.__get_brush_config(torrent.site_name) # 上传限速 @@ -2529,7 +2573,7 @@ class BrushFlow(_PluginBase): if not torrent_content: logger.error(f"获取下载链接失败:{torrent.title}") return None - + if brush_config.downloader == "qbittorrent": if not self.qb: return None @@ -2548,29 +2592,29 @@ class BrushFlow(_PluginBase): else: logger.error('代理下载种子失败,继续尝试传递种子地址到下载器进行下载') if torrent_content: - state = True state = self.qb.add_torrent(content=torrent_content, download_dir=download_dir, cookie=torrent.site_cookie, tag=["已整理", brush_config.brush_tag, tag], upload_limit=up_speed, download_limit=down_speed) - if not state: - return None - else: - # 获取种子Hash - torrent_hash = self.qb.get_torrent_id_by_tag(tags=tag) - if not torrent_hash: - logger.error(f"{brush_config.downloader} 获取种子Hash失败") + if not state: return None - return torrent_hash - + else: + # 获取种子Hash + torrent_hash = self.qb.get_torrent_id_by_tag(tags=tag) + if not torrent_hash: + logger.error(f"{brush_config.downloader} 获取种子Hash失败") + return None + return torrent_hash + return None + elif brush_config.downloader == "transmission": if not self.tr: return None # 如果开启代理下载以及种子地址不是磁力地址,则请求种子到内存再传入下载器 if brush_config.proxy_download and not torrent_content.startswith("magnet"): - response = RequestUtils(cookies=torrent.site_cookie, + response = RequestUtils(cookies=torrent.site_cookie, proxies=settings.PROXY if torrent.site_proxy else None, ua=torrent.site_ua).get_res(url=torrent_content) if response and response.ok: @@ -2602,7 +2646,7 @@ class BrushFlow(_PluginBase): except Exception as e: print(str(e)) return "" - + def __get_all_hashes(self, torrents): """ 获取torrents列表中所有种子的Hash值 @@ -2782,7 +2826,7 @@ class BrushFlow(_PluginBase): tags = torrent.get("tags") # tracker tracker = torrent.get("tracker") - + return { "hash": torrent_id, "title": torrent_title, @@ -2807,7 +2851,8 @@ class BrushFlow(_PluginBase): logger.error(message) self.systemmessage.put(message) - def __send_delete_message(self, site_name: str, torrent_title: str, torrent_desc: str, reason: str, title: str = "【刷流任务种子删除】"): + def __send_delete_message(self, site_name: str, torrent_title: str, torrent_desc: str, reason: str, + title: str = "【刷流任务种子删除】"): """ 发送删除种子的消息 """ @@ -2823,19 +2868,21 @@ class BrushFlow(_PluginBase): msg_text = f"{msg_text}\n内容:{torrent_desc}" if reason: msg_text = f"{msg_text}\n原因:{reason}" - + self.post_message(mtype=NotificationType.SiteMessage, title=title, text=msg_text) - - def __build_add_message_text(self, torrent): + + @staticmethod + def __build_add_message_text(torrent): """ 构建消息文本,兼容TorrentInfo对象和torrent_task字典 """ + # 定义一个辅助函数来统一获取数据的方式 - def get_data(key, default=None): + def get_data(_key, default=None): if isinstance(torrent, dict): - return torrent.get(key, default) + return torrent.get(_key, default) else: - return getattr(torrent, key, default) + return getattr(torrent, _key, default) # 构造消息文本,确保使用中文标签 msg_parts = [] @@ -2857,7 +2904,7 @@ class BrushFlow(_PluginBase): msg_parts.append(f"{label_mapping[key]}:{'是' if key == 'hit_and_run' and value else value}") return "\n".join(msg_parts) - + def __send_add_message(self, torrent, title: str = "【刷流任务种子下载】"): """ 发送添加下载的消息 @@ -2918,7 +2965,8 @@ class BrushFlow(_PluginBase): torrents = downlader.get_downloading_torrents() return len(torrents) or 0 - def __get_pubminutes(self, pubdate: str) -> float: + @staticmethod + def __get_pubminutes(pubdate: str) -> float: """ 将字符串转换为时间,并计算与当前时间差)(分钟) """ @@ -2932,27 +2980,28 @@ class BrushFlow(_PluginBase): except Exception as e: print(str(e)) return 0 - - def __adjust_site_pubminutes(self, pub_minutes: float, torrent: TorrentInfo) -> float: + + @staticmethod + def __adjust_site_pubminutes(pub_minutes: float, torrent: TorrentInfo) -> float: """ 处理部分站点的时区逻辑 """ try: if not torrent: return pub_minutes - + if torrent.site_name == "我堡": # 获取当前时区的UTC偏移量(以秒为单位) utc_offset_seconds = time.timezone # 将UTC偏移量转换为分钟 utc_offset_minutes = utc_offset_seconds / 60 - + # 增加UTC偏移量到pub_minutes adjusted_pub_minutes = pub_minutes + utc_offset_minutes - + return adjusted_pub_minutes - + return pub_minutes except Exception as e: logger.error(str(e)) @@ -2985,11 +3034,11 @@ class BrushFlow(_PluginBase): # 返回不包含 None 的名称集合 return subscribe_titles - - def __filter_torrents_contains_subscribe(self, torrents : Any): + + def __filter_torrents_contains_subscribe(self, torrents: Any): subscribe_titles = self.__get_subscribe_titles() logger.info(f"当前订阅的名称集合为:{subscribe_titles}") - + # 初始化两个列表,一个用于收集未被排除的种子,一个用于记录被排除的种子 included_torrents = [] excluded_torrents = [] @@ -2999,7 +3048,7 @@ class BrushFlow(_PluginBase): # 确保title和description至少是空字符串 title = torrent.title or '' description = torrent.description or '' - + if any(subscribe_title in title or subscribe_title in description for subscribe_title in subscribe_titles): # 如果种子的标题或描述包含订阅标题中的任一项,则记录为被排除 excluded_torrents.append(torrent) @@ -3007,14 +3056,15 @@ class BrushFlow(_PluginBase): else: # 否则,收集为未被排除的种子 included_torrents.append(torrent) - + if not excluded_torrents: logger.info(f"没有命中订阅内容,不需要排除种子") - + # 返回未被排除的种子列表 return included_torrents - - def __bytes_to_gb(self, size_in_bytes: int) -> float: + + @staticmethod + def __bytes_to_gb(size_in_bytes: float) -> float: """ 将字节单位的大小转换为千兆字节(GB)。 @@ -3022,14 +3072,16 @@ class BrushFlow(_PluginBase): :return: 文件大小,单位为千兆字节(GB)。 """ return size_in_bytes / (1024 ** 3) - - def __is_number_or_range(self, value): + + @staticmethod + def __is_number_or_range(value): """ 检查字符串是否表示单个数字或数字范围(如'5'或'5-10') """ return bool(re.match(r"^\d+(-\d+)?$", value)) - - def __is_number(self, value): + + @staticmethod + def __is_number(value): """ 检查给定的值是否可以被转换为数字(整数或浮点数) """ @@ -3039,12 +3091,13 @@ class BrushFlow(_PluginBase): except ValueError: return False - def __calculate_seeding_torrents_size(self, torrent_tasks: Dict[str, dict]) -> int: + @staticmethod + def __calculate_seeding_torrents_size(torrent_tasks: Dict[str, dict]) -> float: """ 计算保种种子体积 """ return sum(task.get("size", 0) for task in torrent_tasks.values() if not task.get("deleted", False)) - + def __archive_tasks(self): """ 归档已经删除的种子数据 @@ -3053,10 +3106,10 @@ class BrushFlow(_PluginBase): # 用于存储已删除的数据 archived_tasks: Dict[str, dict] = self.get_data("archived") or {} - + # 准备一个列表,记录所有需要从原始数据中删除的键 keys_to_delete = [] - + # 遍历所有 torrent 条目 for key, value in torrent_tasks.items(): # 检查是否标记为已删除 @@ -3065,40 +3118,50 @@ class BrushFlow(_PluginBase): archived_tasks[key] = value # 记录键,稍后删除 keys_to_delete.append(key) - + # 从原始字典中移除已删除的条目 for key in keys_to_delete: del torrent_tasks[key] - + self.save_data("archived", archived_tasks) self.save_data("torrents", torrent_tasks) # 归档需要更新一下统计数据 self.__update_and_save_statistic_info(torrent_tasks=torrent_tasks) - + def __clear_tasks(self): """ 清除统计数据(清理已删除和已归档的数据,保留活跃种子数据) """ torrent_tasks: Dict[str, dict] = self.get_data("torrents") or {} - + to_delete = [key for key, value in torrent_tasks.items() if value.get("deleted")] for key in to_delete: del torrent_tasks[key] - + self.save_data("torrents", torrent_tasks) self.save_data("archived", {}) self.save_data("unmanaged", {}) # 需要更新一下统计数据 self.__update_and_save_statistic_info(torrent_tasks=torrent_tasks) - - def __get_statistic_info(self) -> Dict[str, dict]: + + def __get_statistic_info(self) -> Dict[str, int]: """ 获取统计数据 """ - statistic_info = self.get_data("statistic") or {"count": 0, "deleted": 0, "uploaded": 0, "downloaded": 0, "unarchived": 0, "active": 0, "active_uploaded": 0, "active_downloaded": 0} + statistic_info = self.get_data("statistic") or { + "count": 0, + "deleted": 0, + "uploaded": 0, + "downloaded": 0, + "unarchived": 0, + "active": 0, + "active_uploaded": 0, + "active_downloaded": 0 + } return statistic_info - def __get_demo_site_config(self) -> str: + @staticmethod + def __get_demo_site_config() -> str: desc = ("以下为配置示例,请参考 " "https://github.com/InfinityPacer/MoviePilot-Plugins/blob/main/README.md " "进行配置,请注意,只需要保留实际配置内容(删除这段)\n") @@ -3121,36 +3184,38 @@ class BrushFlow(_PluginBase): "proxy_download": false }]""" return desc + config - - def __is_valid_time_range(self, time_range: str) -> bool: + + @staticmethod + def __is_valid_time_range(time_range: str) -> bool: """检查时间范围字符串是否有效:格式为"HH:MM-HH:MM",且时间有效""" if not time_range: return False - + # 使用正则表达式匹配格式 pattern = re.compile(r'^\d{2}:\d{2}-\d{2}:\d{2}$') if not pattern.match(time_range): return False - + try: start_str, end_str = time_range.split('-') datetime.strptime(start_str, '%H:%M').time() datetime.strptime(end_str, '%H:%M').time() - except Exception: + except Exception as e: + print(str(e)) return False return True - + def __is_current_time_in_range(self) -> bool: """判断当前时间是否在开启时间区间内""" - + brush_config = self.__get_brush_config() active_time_range = brush_config.active_time_range - + if not self.__is_valid_time_range(active_time_range): # 如果时间范围格式不正确或不存在,说明当前没有开启时间段,返回True return True - + start_str, end_str = active_time_range.split('-') start_time = datetime.strptime(start_str, '%H:%M').time() end_time = datetime.strptime(end_str, '%H:%M').time() @@ -3167,9 +3232,10 @@ class BrushFlow(_PluginBase): """ 根据tracker获取站点信息 """ + domain = "未知" if not trackers: - return (0, "未知") - + return 0, domain + # 特定tracker到域名的映射 tracker_mappings = { "chdbits.xyz": "ptchdbits.co", @@ -3189,9 +3255,7 @@ class BrushFlow(_PluginBase): site_info = self.siteshelper.get_indexer(domain) if site_info: - return (site_info.get("id"), site_info.get("name")) + return site_info.get("id"), site_info.get("name") # 当找不到对应的站点信息时,返回一个默认值 - return (0, domain) - - \ No newline at end of file + return 0, domain