diff --git a/package.json b/package.json index 348f06a..a76e7c3 100644 --- a/package.json +++ b/package.json @@ -355,11 +355,12 @@ "name": "站点刷流", "description": "自动托管刷流,将会提高对应站点的访问频率。", "labels": "刷流,仪表板", - "version": "3.3", + "version": "3.4", "icon": "brush.jpg", "author": "jxxghp,InfinityPacer", "level": 2, "history": { + "v3.4": "移除「记录更多日志」配置项并调整为DEBUG日志,支持「删除排除标签」配置项,增加刷流任务时支持触发插件事件", "v3.3": "支持QB删除种子时强制汇报Tracker,站点独立配置增加「站点全局H&R」配置项", "v3.2": "支持推送QB种子时启用「先下载首尾文件块」选项", "v3.1": "支持仪表板显示站点刷流数据,需要主程序升级v1.8.7+版本", diff --git a/plugins/brushflow/__init__.py b/plugins/brushflow/__init__.py index 0189777..bdc0b9a 100644 --- a/plugins/brushflow/__init__.py +++ b/plugins/brushflow/__init__.py @@ -25,6 +25,7 @@ from app.modules.qbittorrent import Qbittorrent from app.modules.transmission import Transmission from app.plugins import _PluginBase from app.schemas import NotificationType, TorrentInfo, MediaType +from app.schemas.types import EventType from app.utils.http import RequestUtils from app.utils.string import StringUtils @@ -67,11 +68,11 @@ class BrushConfig: self.clear_task = config.get("clear_task", False) self.archive_task = config.get("archive_task", False) self.except_tags = config.get("except_tags", True) + self.delete_except_tags = config.get("delete_except_tags") self.except_subscribe = config.get("except_subscribe", True) self.brush_sequential = config.get("brush_sequential", False) 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.downloader_monitor = config.get("downloader_monitor") self.qb_category = config.get("qb_category") @@ -257,7 +258,7 @@ class BrushFlow(_PluginBase): # 插件图标 plugin_icon = "brush.jpg" # 插件版本 - plugin_version = "3.3" + plugin_version = "3.4" # 插件作者 plugin_author = "jxxghp,InfinityPacer" # 作者主页 @@ -295,7 +296,6 @@ class BrushFlow(_PluginBase): # endregion def init_plugin(self, config: dict = None): - logger.info(f"站点刷流服务初始化") self.siteshelper = SitesHelper() self.siteoper = SiteOper() self.torrents = TorrentsChain() @@ -340,11 +340,10 @@ class BrushFlow(_PluginBase): brush_config.archive_task = False self.__update_config() - if brush_config.log_more: - if brush_config.enable_site_config: - logger.info(f"已开启站点独立配置,配置信息:{brush_config}") - else: - logger.info(f"没有开启站点独立配置,配置信息:{brush_config}") + if brush_config.enable_site_config: + logger.debug(f"已开启站点独立配置,配置信息:{brush_config}") + else: + logger.debug(f"没有开启站点独立配置,配置信息:{brush_config}") # 停止现有任务 self.stop_service() @@ -366,8 +365,6 @@ 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) @@ -974,11 +971,6 @@ class BrushFlow(_PluginBase): 'component': 'VWindow', 'props': { 'model': '_tabs' - # VWindow设置paddnig会导致切换Tab时页面高度变动,调整为修改VRow的方案 - # 'style': { - # 'padding-top': '24px', - # 'padding-bottom': '24px', - # }, }, 'content': [ { @@ -1426,11 +1418,28 @@ class BrushFlow(_PluginBase): 'component': 'VTextField', 'props': { 'model': 'seed_inactivetime', - 'label': '未活动时间(分钟) ', + 'label': '未活动时间(分钟)', 'placeholder': '超过时删除任务' } } ] + }, + { + 'component': 'VCol', + 'props': { + "cols": 12, + "md": 4 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'delete_except_tags', + 'label': '删除排除标签', + 'placeholder': '如:MOVIEPILOT,H&R' + } + } + ] } ] } @@ -1658,27 +1667,6 @@ class BrushFlow(_PluginBase): ] } ] - }, - { - 'component': 'VRow', - "content": [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 4 - }, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'log_more', - 'label': '记录更多日志', - } - } - ] - } - ] } ] } @@ -1742,7 +1730,7 @@ class BrushFlow(_PluginBase): 'props': { 'type': 'error', 'variant': 'tonal', - 'text': '注意:排除H&R并不保证能完全适配所有站点(部分站点在列表页不显示H&R标志,但实际上是有H&R的),请注意核对使用!' + 'text': '注意:排除H&R并不保证能完全适配所有站点(部分站点在列表页不显示H&R标志,但实际上是有H&R的),请注意核对使用' } } ] @@ -1850,6 +1838,7 @@ class BrushFlow(_PluginBase): "clear_task": False, "archive_task": False, "except_tags": True, + "delete_except_tags": f"{settings.TORRENT_TAG},H&R" if settings.TORRENT_TAG else "H&R", "except_subscribe": True, "brush_sequential": False, "proxy_download": False, @@ -1857,7 +1846,6 @@ class BrushFlow(_PluginBase): "freeleech": "free", "hr": "yes", "enable_site_config": False, - "log_more": False, "downloader_monitor": False, "auto_qb_category": False, "qb_first_last_piece": False, @@ -2055,9 +2043,6 @@ class BrushFlow(_PluginBase): if brush_config.site_hr_active: logger.info(f"站点 {siteinfo.name} 已开启全站H&R选项,所有种子设置为H&R种子") - # 由于缓存原因,这里不能直接改torrents,在后续加入任务中调整 - # for torrent in torrents: - # torrent.hit_and_run = True # 排除包含订阅的种子 if brush_config.except_subscribe: @@ -2068,7 +2053,7 @@ class BrushFlow(_PluginBase): torrents_size = self.__calculate_seeding_torrents_size(torrent_tasks=torrent_tasks) - logger.info(f"正在准备种子刷流,数量:{len(torrents)}") + logger.info(f"正在准备种子刷流,数量 {len(torrents)}") # 过滤种子 for torrent in torrents: @@ -2078,6 +2063,8 @@ class BrushFlow(_PluginBase): if not pre_condition_passed: return False + logger.debug(f"种子详情:{torrent}") + # 判断能否通过保种体积刷流条件 size_condition_passed, reason = self.__evaluate_size_condition_for_brush(torrents_size=torrents_size, add_torrent_size=torrent.size) @@ -2098,8 +2085,8 @@ class BrushFlow(_PluginBase): logger.warn(f"{torrent.title} 添加刷流任务失败!") continue - # 保存任务信息 - torrent_tasks[hash_string] = { + # 触发刷流下载时间并保存任务信息 + torrent_task = { "site": siteinfo.id, "site_name": siteinfo.name, "title": torrent.title, @@ -2134,6 +2121,12 @@ class BrushFlow(_PluginBase): "time": time.time() } + self.eventmanager.send_event(etype=EventType.PluginAction, data={ + "action": "brushflow_download_added", + "data": torrent_task + }) + torrent_tasks[hash_string] = torrent_task + # 统计数据 torrents_size += torrent.size statistic_info["count"] += 1 @@ -2306,7 +2299,8 @@ class BrushFlow(_PluginBase): return True, None - def __log_brush_conditions(self, passed: bool, reason: str, torrent: Any = None): + @staticmethod + def __log_brush_conditions(passed: bool, reason: str, torrent: Any = None): """ 记录刷流日志 """ @@ -2314,9 +2308,7 @@ class BrushFlow(_PluginBase): if not torrent: logger.warn(f"没有通过前置刷流条件校验,原因:{reason}") else: - brush_config = self.__get_brush_config() - if brush_config.log_more: - logger.warn(f"种子没有通过刷流条件校验,原因:{reason} 种子:{torrent.title}|{torrent.description}") + logger.debug(f"种子没有通过刷流条件校验,原因:{reason} 种子:{torrent.title}|{torrent.description}") # endregion @@ -2372,34 +2364,57 @@ class BrushFlow(_PluginBase): # 更新刷流任务列表中在下载器中删除的种子为删除状态 self.__update_undeleted_torrents_missing_in_downloader(torrent_tasks, torrent_check_hashes, check_torrents) - # 排除MoviePilot种子 - if check_torrents and brush_config.except_tags: - check_torrents = self.__filter_torrents_by_tag(torrents=check_torrents, - exclude_tag=settings.TORRENT_TAG) + # 根据配置的标签进行种子排除 + if check_torrents: + logger.info(f"当前刷流任务共 {len(check_torrents)} 个有效种子,正在准备按设定的种子标签进行排除") + # 初始化一个空的列表来存储需要排除的标签 + tags_to_exclude = set() + # 如果 except_tags 配置为 True,将 settings.TORRENT_TAG 添加到排除列表中(前提是它不为空且不是纯空白) + if brush_config.except_tags and settings.TORRENT_TAG.strip(): + tags_to_exclude.add(settings.TORRENT_TAG.strip()) + # 如果 delete_except_tags 非空且不是纯空白,则添加到排除列表中 + if brush_config.delete_except_tags and brush_config.delete_except_tags.strip(): + tags_to_exclude.update(tag.strip() for tag in brush_config.delete_except_tags.split(',')) + # 将所有需要排除的标签组合成一个字符串,每个标签之间用逗号分隔 + combined_tags = ",".join(tags_to_exclude) + if combined_tags: # 确保有标签需要排除 + pre_filter_count = len(check_torrents) # 获取过滤前的任务数量 + check_torrents = self.__filter_torrents_by_tag(torrents=check_torrents, exclude_tag=combined_tags) + post_filter_count = len(check_torrents) # 获取过滤后的任务数量 + excluded_count = pre_filter_count - post_filter_count # 计算被排除的任务数量 + logger.info( + f"有效种子数 {pre_filter_count},排除标签 '{combined_tags}' 后," + f"剩余种子数 {post_filter_count},排除种子数 {excluded_count}") + else: + logger.info("没有配置有效的排除标签,所有种子均参与后续处理") - need_delete_hashes = [] - - # 如果配置了动态删除以及删种阈值,则根据动态删种进行分组处理 - if brush_config.proxy_delete and brush_config.delete_size_range: - logger.info("已开启动态删种,按系统默认动态删种条件开始检查任务") - proxy_delete_hashes = self.__delete_torrent_for_proxy(torrents=check_torrents, - torrent_tasks=torrent_tasks) or [] - need_delete_hashes.extend(proxy_delete_hashes) - # 否则均认为是没有开启动态删种 + # 种子删除检查 + if not check_torrents: + logger.info("没有需要检查的任务,跳过") else: - logger.info("没有开启动态删种,按用户设置删种条件开始检查任务") - not_proxy_delete_hashes = self.__delete_torrent_for_evaluate_conditions(torrents=check_torrents, - torrent_tasks=torrent_tasks) or [] - need_delete_hashes.extend(not_proxy_delete_hashes) + need_delete_hashes = [] - if need_delete_hashes: - # 如果是QB,则重新汇报Tracker - if brush_config.downloader == "qbittorrent": - self.__qb_torrents_reannounce(torrent_hashes=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 + # 如果配置了动态删除以及删种阈值,则根据动态删种进行分组处理 + if brush_config.proxy_delete and brush_config.delete_size_range: + logger.info("已开启动态删种,按系统默认动态删种条件开始检查任务") + proxy_delete_hashes = self.__delete_torrent_for_proxy(torrents=check_torrents, + torrent_tasks=torrent_tasks) or [] + need_delete_hashes.extend(proxy_delete_hashes) + # 否则均认为是没有开启动态删种 + else: + logger.info("没有开启动态删种,按用户设置删种条件开始检查任务") + not_proxy_delete_hashes = self.__delete_torrent_for_evaluate_conditions(torrents=check_torrents, + torrent_tasks=torrent_tasks) or [] + need_delete_hashes.extend(not_proxy_delete_hashes) + + if need_delete_hashes: + # 如果是QB,则重新汇报Tracker + if brush_config.downloader == "qbittorrent": + self.__qb_torrents_reannounce(torrent_hashes=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) @@ -2618,8 +2633,7 @@ class BrushFlow(_PluginBase): 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}") + logger.debug(f"站点:{site_name},{reason},不删除种子:{torrent_title}|{torrent_desc}") return delete_hashes @@ -2657,8 +2671,7 @@ class BrushFlow(_PluginBase): 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}") + logger.debug(f"站点:{site_name},{reason},不删除种子:{torrent_title}|{torrent_desc}") return delete_hashes @@ -2914,7 +2927,7 @@ class BrushFlow(_PluginBase): "active_downloaded": active_downloaded }) - logger.info(f"刷流任务统计数据:总任务数:{total_count},活跃任务数:{active_count},已删除:{total_deleted}," + logger.info(f"刷流任务统计数据,总任务数:{total_count},活跃任务数:{active_count},已删除:{total_deleted}," f"待归档:{total_unarchived}," f"活跃上传量:{StringUtils.str_filesize(active_uploaded)}," f"活跃下载量:{StringUtils.str_filesize(active_downloaded)}," @@ -3030,11 +3043,11 @@ class BrushFlow(_PluginBase): "clear_task": brush_config.clear_task, "archive_task": brush_config.archive_task, "except_tags": brush_config.except_tags, + "delete_except_tags": brush_config.delete_except_tags, "except_subscribe": brush_config.except_subscribe, "brush_sequential": brush_config.brush_sequential, "proxy_download": brush_config.proxy_download, "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, "qb_category": brush_config.qb_category, @@ -3131,7 +3144,7 @@ class BrushFlow(_PluginBase): data = data.get(key) if not data: return None - logger.info(f"获取到下载地址:{data}") + logger.debug(f"获取到下载地址:{data}") return data return None @@ -3201,8 +3214,7 @@ class BrushFlow(_PluginBase): # 获取种子Hash torrent_hash = self.qb.get_torrent_id_by_tag(tags=tag) if not torrent_hash: - logger.error(f"{brush_config.downloader} 获取种子Hash失败" - f"{',请尝试启用「代理下载种子」配置项' if not brush_config.proxy_download else ''}") + logger.error(f"{brush_config.downloader} 获取种子Hash失败,详细信息请查看 README") return None return torrent_hash return None @@ -3705,14 +3717,21 @@ class BrushFlow(_PluginBase): def __filter_torrents_by_tag(self, torrents: List[Any], exclude_tag: str) -> List[Any]: """ - 根据标签过滤torrents + 根据标签过滤torrents,排除标签格式为逗号分隔的字符串,例如 "MOVIEPILOT, H&R" """ + # 如果排除标签字符串为空,则返回原始列表 + if not exclude_tag: + return torrents + + # 将 exclude_tag 字符串分割成一个集合,并去除每个标签两端的空白,忽略空白标签并自动去重 + exclude_tags = set(tag.strip() for tag in exclude_tag.split(',') if tag.strip()) + filter_torrents = [] for torrent in torrents: # 使用 __get_label 方法获取每个 torrent 的标签列表 labels = self.__get_label(torrent) - # 如果排除的标签不在这个列表中,则添加到过滤后的列表 - if exclude_tag not in labels: + # 检查是否有任何一个排除标签存在于标签列表中 + if not any(exclude in labels for exclude in exclude_tags): filter_torrents.append(torrent) return filter_torrents @@ -3766,7 +3785,8 @@ class BrushFlow(_PluginBase): for key in set(self._subscribe_infos) - current_keys: del self._subscribe_infos[key] - logger.info(f"订阅标题匹配完成,当前订阅的标题集合为:{self._subscribe_infos}") + logger.info("已订阅标题匹配完成") + logger.debug(f"当前订阅的标题集合为:{self._subscribe_infos}") unique_titles = {title for titles in self._subscribe_infos.values() for title in titles} return unique_titles