From f0c742d6d26268efa2932ce03b44000d23b14f79 Mon Sep 17 00:00:00 2001 From: Pixel-LH Date: Fri, 29 Mar 2024 17:31:58 +0800 Subject: [PATCH 01/24] Update package.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更正整理VCB动漫压制组作品插件图标 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e5329e7..3170326 100644 --- a/package.json +++ b/package.json @@ -195,7 +195,7 @@ "name": "整理VCB动漫压制组作品", "description": "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理", "version": "1.6.6", - "icon": "qingwa.png", + "icon": "vcbmonitor.png", "author": "pixel@qingwa", "level": 2 }, From 00ff5b6f10efb788a3d36934a54267736137a430 Mon Sep 17 00:00:00 2001 From: Allen Date: Fri, 29 Mar 2024 13:13:58 +0000 Subject: [PATCH 02/24] fix issues/143 --- plugins/downloaderhelper/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 09beb40..ff6df52 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -872,7 +872,7 @@ class DownloaderHelper(_PluginBase): else: site_tag = self.__generate_site_tag(self.__get_domain_keyword(tracker_domain)) - if site_tag: + if site_tag and site_tag in delete_suggest: delete_suggest.remove(site_tag) return site_tag, delete_suggest @@ -1444,7 +1444,8 @@ class DownloaderHelper(_PluginBase): # 移除建议删除的标签 if delete_suggest and len(delete_suggest) > 0: for to_delete in delete_suggest: - torrent_tags_copy.remove(to_delete) + 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) From fbdc393e17b40525a6a445af413e244cb59c674c Mon Sep 17 00:00:00 2001 From: Allen Date: Fri, 29 Mar 2024 13:52:34 +0000 Subject: [PATCH 03/24] fix issues/142 --- plugins/downloaderhelper/__init__.py | 35 +++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index ff6df52..43b07cd 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -77,6 +77,8 @@ class DownloaderHelper(_PluginBase): __tracker_mappings: Dict[str, str] = {} # 排除种子标签 __exclude_tags: Set[str] = set() + # 多级根域名,用于在打标时做特殊处理 + __multi_level_root_domain: List[str] = ['edu.cn', 'com.cn', 'net.cn', 'org.cn'] def init_plugin(self, config: dict = None): """ @@ -516,8 +518,7 @@ class DownloaderHelper(_PluginBase): finally: self.__exit_event.clear() - @staticmethod - def __parse_tracker_mappings(tracker_mappings: str) -> Dict[str, str]: + def __parse_tracker_mappings(self, tracker_mappings: str) -> Dict[str, str]: """ 解析配置的tracker映射 :param tracker_mappings: 配置的tracker映射 @@ -540,7 +541,7 @@ class DownloaderHelper(_PluginBase): key, value = key.strip(), value.strip() if not key or not value: continue - if len(key.split('.')) >= 2 and len(value.split('.')) == 2: + if self.__is_valid_domain(key) and self.__is_valid_domain(value): mappings[key] = value return mappings @@ -796,6 +797,34 @@ class DownloaderHelper(_PluginBase): else: return None + def __match_multi_level_root_domain(self, domain: str) -> Tuple[str, int]: + """ + 匹配多级根域名 + :param domain: 被匹配的域名 + :return: 匹配的根域名, 匹配的根域名长度 + """ + if not domain or not self.__multi_level_root_domain: + return None, 0 + for root_domain in self.__multi_level_root_domain: + if domain.endswith('.' + root_domain): + root_domain_len = len(root_domain.split('.')) + return root_domain, root_domain_len + return None, 0 + + def __is_valid_domain(self, domain: str) -> bool: + """ + 判断域名是否有效 + :param domain: 被判断的域名 + :return: 是否有效 + """ + if not domain: + return False + domain_len = len(domain.split('.')) + root_domain, root_domain_len = self.__match_multi_level_root_domain(domain) + if root_domain: + return domain_len > root_domain_len + return domain_len > 1 + def __generate_site_tag(self, site: str) -> Optional[str]: """ 生成站点标签 From 7264ce06d381fd920156ee164efab741805bdda1 Mon Sep 17 00:00:00 2001 From: Allen Date: Fri, 29 Mar 2024 13:56:30 +0000 Subject: [PATCH 04/24] issues/142 --- plugins/downloaderhelper/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 43b07cd..aef83ce 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -797,7 +797,7 @@ class DownloaderHelper(_PluginBase): else: return None - def __match_multi_level_root_domain(self, domain: str) -> Tuple[str, int]: + def __match_multi_level_root_domain(self, domain: str) -> Tuple[Optional[str], int]: """ 匹配多级根域名 :param domain: 被匹配的域名 From 1c5f9cf3a8b921a6f9b990705c7735bef19df690 Mon Sep 17 00:00:00 2001 From: Allen Date: Fri, 29 Mar 2024 14:41:17 +0000 Subject: [PATCH 05/24] =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/downloaderhelper/__init__.py | 48 +++++++++++++--------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index aef83ce..4a0181d 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -768,34 +768,32 @@ class DownloaderHelper(_PluginBase): scheme, netloc = StringUtils.get_url_netloc(url) return netloc - @staticmethod - def __get_domain_level2(domain: str) -> Optional[str]: + def __get_main_domain(self, domain: str) -> Optional[str]: """ - 获取域名的二级域名 + 获取域名的主域名 + :param domain: 原域名 + :return: 主域名 """ if not domain: return None domain_arr = domain.split('.') - domain_arr_len = len(domain_arr) - if domain_arr_len == 2: - return domain - elif domain_arr_len > 2: - return f'{domain_arr[-2]}.{domain_arr[-1]}' - else: + domain_len = len(domain_arr) + if domain_len < 2: return None + root_domain, root_domain_len = self.__match_multi_level_root_domain(domain=domain) + if root_domain: + return f'{domain_arr[-root_domain_len - 1]}.{root_domain}' + else: + return f'{domain_arr[-2]}.{domain_arr[-1]}' - @staticmethod - def __get_domain_keyword(domain: str) -> Optional[str]: + def __get_domain_keyword(self, domain: str) -> Optional[str]: """ 获取域名关键字 """ - if not domain: - return None - domain_arr = domain.split('.') - if len(domain_arr) >= 2: - return domain_arr[-2] - else: + main_domain = self.__get_main_domain(domain=domain) + if not main_domain: return None + return main_domain.split('.')[0] def __match_multi_level_root_domain(self, domain: str) -> Tuple[Optional[str], int]: """ @@ -845,7 +843,7 @@ class DownloaderHelper(_PluginBase): return None, None # tracker的完整域名 - tracker_domain = self.__get_url_domain(tracker_url) + tracker_domain = self.__get_url_domain(url=tracker_url) if not tracker_domain: return None, None @@ -853,20 +851,20 @@ class DownloaderHelper(_PluginBase): delete_suggest = set() # tracker域名关键字 - tracker_domain_keyword = self.__get_domain_keyword(tracker_domain) + tracker_domain_keyword = self.__get_domain_keyword(domain=tracker_domain) if tracker_domain_keyword: # 建议移除 delete_suggest.add(tracker_domain_keyword) - delete_suggest.add(self.__generate_site_tag(tracker_domain_keyword)) + delete_suggest.add(self.__generate_site_tag(site=tracker_domain_keyword)) # 首先根据tracker的完整域名去匹配站点信息 - site_info = self.__get_site_info_by_domain(tracker_domain) + site_info = self.__get_site_info_by_domain(site_domain=tracker_domain) - # 如果没有匹配到,再根据二级域名去匹配 + # 如果没有匹配到,再根据主域名去匹配 if not site_info: - tracker_domain_level2 = self.__get_domain_level2(tracker_domain) - if tracker_domain_level2: - site_info = self.__get_site_info_by_domain(tracker_domain_level2) + tracker_main_domain = self.__get_main_domain(domain=tracker_domain) + if tracker_main_domain and tracker_main_domain != tracker_domain: + site_info = self.__get_site_info_by_domain(tracker_main_domain) # 如果还是没有匹配到,就根据tracker映射的域名匹配 matched_site_domain = None From 8045966d6f4a7fc038f796070dc00a05cc9adabd Mon Sep 17 00:00:00 2001 From: Allen Date: Fri, 29 Mar 2024 14:43:42 +0000 Subject: [PATCH 06/24] =?UTF-8?q?=E6=96=87=E6=A1=88=E5=8F=8A=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=9B=B4=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/downloaderhelper/README.md | 2 +- plugins/downloaderhelper/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/downloaderhelper/README.md b/plugins/downloaderhelper/README.md index 7aca8b6..c0a88d8 100644 --- a/plugins/downloaderhelper/README.md +++ b/plugins/downloaderhelper/README.md @@ -43,7 +43,7 @@ |定时执行周期|插件定时服务的cron表达式,仅支持5位的,缺省时不注册定时服务。| |排除种子标签|多个标签通过英文逗号分割,具备配置的任意标签的种子不会进行自动做种、站点标签、自动删种操作。| |站点标签前缀|站点标签的前缀,缺省时不添加前缀。| -|Tracker映射|站点标签的原理是根据tracker的域名去匹配站点,但是有的PT站的tracker域名和站点域名不一致,导致匹配不到站点,因此需要对这些特殊站点的tracker做映射;每行一个映射,格式是 `tracker域名:站点域名`,tracker域名至少需要配置二级,站点域名只能配置二级。| +|Tracker映射|站点标签的原理是根据tracker的域名去匹配站点,但是有的PT站的tracker域名和站点域名不一致,导致匹配不到站点,因此需要对这些特殊站点的tracker做映射;每行一个映射,格式是 `tracker域名:站点域名`,tracker域名可以是完整域名或者主域名。| ##### 2.1.2、下载器子任务配置项 diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 4a0181d..1f2f392 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -314,7 +314,7 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'title': 'Tracker映射。用于在站点打标签时,指定tracker和站点域名不同的种子的域名对应关系;前面为tracker域名(二级或多级),中间是英文冒号,后面是站点域名(只能是二级)。' + 'title': 'Tracker映射。用于在站点打标签时,指定tracker和站点域名不同的种子的域名对应关系;前面为tracker域名(完整域名或者主域名皆可),中间是英文冒号,后面是站点域名。' }, 'content': [{ 'component': 'VTextarea', From a138e2965832b75de062a279d66405ddf6208acc Mon Sep 17 00:00:00 2001 From: Allen Date: Fri, 29 Mar 2024 14:44:43 +0000 Subject: [PATCH 07/24] =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=99=A8=E5=8A=A9?= =?UTF-8?q?=E6=89=8B=E7=89=88=E6=9C=AC=E5=8D=87=E7=BA=A7v1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- plugins/downloaderhelper/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3170326..79ef317 100644 --- a/package.json +++ b/package.json @@ -458,7 +458,7 @@ "DownloaderHelper": { "name": "下载器助手", "description": "自动做种、站点标签、自动删种。", - "version": "1.1", + "version": "1.2", "icon": "DownloaderHelper.png", "author": "hotlcc", "level": 2 diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 1f2f392..49463f4 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -32,7 +32,7 @@ class DownloaderHelper(_PluginBase): # 插件图标 plugin_icon = "DownloaderHelper.png" # 插件版本 - plugin_version = "1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "hotlcc" # 作者主页 From 20a01666e4d5acfae7640e555e9def66e3427ada Mon Sep 17 00:00:00 2001 From: xuzhi Date: Fri, 29 Mar 2024 15:36:18 +0000 Subject: [PATCH 08/24] Fix the data page turns to empty sometimes. --- package.json | 2 +- plugins/contractcheck/__init__.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 79ef317..07eb18f 100644 --- a/package.json +++ b/package.json @@ -450,7 +450,7 @@ "ContractCheck": { "name": "契约检查", "description": "定时检查保种契约达成情况。", - "version": "1.0", + "version": "1.1", "icon": "contract.png", "author": "DzAvril", "level": 1 diff --git a/plugins/contractcheck/__init__.py b/plugins/contractcheck/__init__.py index a768ff5..b651ac0 100644 --- a/plugins/contractcheck/__init__.py +++ b/plugins/contractcheck/__init__.py @@ -39,7 +39,7 @@ class ContractCheck(_PluginBase): # 插件图标 plugin_icon = "contract.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "DzAvril" # 作者主页 @@ -108,11 +108,11 @@ class ContractCheck(_PluginBase): ) self._site_schema.sort(key=lambda x: x.order) - # 站点数据 - self._sites_data = {} # 立即运行一次 if self._onlyonce: + # 站点数据 + self._sites_data = {} # 定时服务 self._scheduler = BackgroundScheduler(timezone=settings.TZ) logger.info(f"保种契约检查服务启动,立即运行一次") From b01053d567b0a279bcfa6529042df5dde42d67fb Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sat, 30 Mar 2024 02:28:42 +0800 Subject: [PATCH 09/24] =?UTF-8?q?fix=20brushflow=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=81=9A=E7=A7=8D=E4=BD=93=E7=A7=AF=E5=88=A4=E6=96=AD=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=AE=B0=E5=BD=95=E6=9B=B4=E5=A4=9A=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- plugins/brushflow/__init__.py | 165 +++++++++++++++++++++++++--------- 2 files changed, 122 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 79ef317..9d31ea8 100644 --- a/package.json +++ b/package.json @@ -226,7 +226,7 @@ "BrushFlow": { "name": "站点刷流", "description": "自动托管刷流,将会提高对应站点的访问频率。", - "version": "2.0", + "version": "2.1", "icon": "brush.jpg", "author": "jxxghp", "level": 2 diff --git a/plugins/brushflow/__init__.py b/plugins/brushflow/__init__.py index 6b1cc12..7842b5b 100644 --- a/plugins/brushflow/__init__.py +++ b/plugins/brushflow/__init__.py @@ -1,16 +1,14 @@ +import base64 +import json +import random import re import threading import time -import random -import json -import base64 from datetime import datetime, timedelta from threading import Event from typing import Any, List, Dict, Tuple, Optional, Union, Set import pytz -from apscheduler.schedulers.background import BackgroundScheduler - from app import schemas from app.chain.torrents import TorrentsChain from app.core.config import settings @@ -24,6 +22,7 @@ from app.plugins import _PluginBase from app.schemas import NotificationType, TorrentInfo from app.utils.http import RequestUtils from app.utils.string import StringUtils +from apscheduler.schedulers.background import BackgroundScheduler lock = threading.Lock() @@ -67,6 +66,7 @@ class BrushConfig: self.brush_sequential = config.get("brush_sequential", False) self.proxy_download = config.get("proxy_download", True) 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.brush_tag = "刷流" @@ -182,7 +182,7 @@ class BrushFlow(_PluginBase): # 插件图标 plugin_icon = "brush.jpg" # 插件版本 - plugin_version = "2.0" + plugin_version = "2.1" # 插件作者 plugin_author = "jxxghp,InfinityPacer" # 作者主页 @@ -1030,6 +1030,27 @@ class BrushFlow(_PluginBase): } ] }, + { + 'component': 'VRow', + "content": [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'log_more', + 'label': '记录更多日志', + } + } + ] + } + ] + }, { 'component': 'VRow', 'content': [ @@ -1156,7 +1177,8 @@ class BrushFlow(_PluginBase): "proxy_delete": False, "freeleech": "free", "hr": "yes", - "enable_site_config": False + "enable_site_config": False, + "log_more": False } def get_page(self) -> List[dict]: @@ -1658,8 +1680,15 @@ class BrushFlow(_PluginBase): torrent_tasks: Dict[str, dict] = self.get_data("torrents") or {} torrents_size = self.__calculate_seeding_torrents_size(torrent_tasks=torrent_tasks) + # 判断能否通过保种体积前置条件 + size_condition_passed, reason = self.__evaluate_size_condition_for_brush(torrents_size=torrents_size) + self.__log_brush_conditions(passed=size_condition_passed, reason=reason) + if not size_condition_passed: + return + # 判断能否通过刷流前置条件 - pre_condition_passed, reason = self.__evaluate_pre_conditions_for_brush(torrents_size=torrents_size) + pre_condition_passed, reason = self.__evaluate_pre_conditions_for_brush() + self.__log_brush_conditions(passed=pre_condition_passed, reason=reason) if not pre_condition_passed: return @@ -1695,7 +1724,9 @@ class BrushFlow(_PluginBase): 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}") @@ -1723,23 +1754,24 @@ class BrushFlow(_PluginBase): # 过滤种子 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(include_network_conditions=False) + self.__log_brush_conditions(passed=pre_condition_passed, reason=reason, torrent=torrent) if not pre_condition_passed: - # logger.info(f"种子没有通过刷流前置条件校验,原因:{reason} 种子:{torrent.title}|{torrent.description}") return False - # else: - # logger.info(f"种子已通过刷流前置校验,种子:{torrent.title}|{torrent.description}") + + # 判断能否通过保种体积刷流条件 + size_condition_passed, reason = self.__evaluate_size_condition_for_brush(torrents_size=torrents_size, + brush_torrent_size=torrent.size) + self.__log_brush_conditions(passed=size_condition_passed, reason=reason, torrent=torrent) + if not size_condition_passed: + continue # 判断能否通过刷流条件 condition_passed, reason = self.__evaluate_conditions_for_brush(torrent=torrent, torrent_tasks=torrent_tasks) + self.__log_brush_conditions(passed=condition_passed, reason=reason, torrent=torrent) if not condition_passed: - # logger.info(f"种子没有通过刷流条件校验,原因:{reason} 种子:{torrent.title}|{torrent.description}") continue - # else: - # logger.info(f"种子已通过刷流条件校验,种子:{torrent.title}|{torrent.description}") # 添加下载任务 hash_string = self.__download(torrent=torrent) @@ -1790,14 +1822,43 @@ class BrushFlow(_PluginBase): return True - def __evaluate_pre_conditions_for_brush(self, torrents_size: float, - include_network_conditions: bool = True) -> Tuple[bool, Optional[str]]: + def __evaluate_size_condition_for_brush(self, torrents_size: float, + brush_torrent_size: float = 0.0) -> Tuple[bool, Optional[str]]: + """ + 过滤体积不符合条件的种子 + """ + total_size = self.__bytes_to_gb(torrents_size + brush_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") + else: + return f"当前做种体积 {self.__bytes_to_gb(torrents_size):.1f} GB,已超过做种体积 {config} GB,暂时停止新增任务" + + reasons = [ + ("disksize", + lambda config: torrents_size + brush_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): + reason = message(config_value) + return False, reason + + return True, None + + def __evaluate_pre_conditions_for_brush(self, 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," - f"已超过保种体积 {config} GB,暂时停止新增任务"), + lambda config: f"当前同时下载任务数已达到最大值 {config},暂时停止新增任务") ] if include_network_conditions: @@ -1819,7 +1880,6 @@ class BrushFlow(_PluginBase): config_value = getattr(brush_config, condition, None) if config_value and check(config_value): reason = message(config_value) - logger.warn(reason) return False, reason return True, None @@ -1894,6 +1954,18 @@ class BrushFlow(_PluginBase): return True, None + def __log_brush_conditions(self, passed: bool, reason: str, torrent: Any = None): + """ + 记录刷流日志 + """ + if not passed: + 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}") + # endregion # region Check @@ -1944,25 +2016,27 @@ class BrushFlow(_PluginBase): # 先更新刷流任务的最新状态,上下传,分享率 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 [] - # 排除MoviePilot种子 if check_torrents and brush_config.except_tags: check_torrents = self.__filter_torrents_by_tag(torrents=check_torrents, exclude_tag=settings.TORRENT_TAG) - need_delete_hashes = [] - need_delete_hashes.extend(undeleted_hashes) + # 先通过获取的全量种子,判断已经被删除,但是任务记录中还没有被标记删除的种子 + undeleted_hashes = self.__get_undeleted_torrents_missing_in_downloader(torrent_tasks, torrent_check_hashes, + check_torrents) or [] + # 这里提前把已经被删除的种子进行标记,避免开启动态删除种子统计体积有时差 + if undeleted_hashes: + for torrent_hash in undeleted_hashes: + torrent_tasks[torrent_hash]["deleted"] = True + need_delete_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 [] need_delete_hashes.extend(proxy_delete_hashs) - # 否则均认为是没有开启动态删种 + # 否则均认为是没有开启动态删种 else: logger.info("没有开启动态删种,按用户设置删种条件开始检查任务") not_proxy_delete_hashs = self.__delete_torrent_for_evaluate_conditions(torrents=check_torrents, @@ -2138,9 +2212,9 @@ class BrushFlow(_PluginBase): def __delete_torrent_for_proxy(self, torrents: List[Any], torrent_tasks: Dict[str, dict]) -> List: """ - 支持动态删除种子,当设置了动态删种(全局)和删除阈值时,当保种体积达到删除阈值时,优先按设置规则进行删除,若还没有达到阈值,则排除HR种子后按加入时间倒序进行删除 - 删除阈值:100,当保种体积 > 100G 时,则开始删除种子,直至降低至 100G - 删除阈值:50-100,当保种体积 > 100G 时,则开始删除种子,直至降至为 50G + 支持动态删除种子,当设置了动态删种(全局)和删除阈值时,当做种体积达到删除阈值时,优先按设置规则进行删除,若还没有达到阈值,则排除HR种子后按加入时间倒序进行删除 + 删除阈值:100,当做种体积 > 100G 时,则开始删除种子,直至降低至 100G + 删除阈值:50-100,当做种体积 > 100G 时,则开始删除种子,直至降至为 50G """ brush_config = self.__get_brush_config() @@ -2150,26 +2224,27 @@ class BrushFlow(_PluginBase): # 解析删除阈值范围 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] # 触发删除操作的保种体积上限 + 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()) + # 计算当前总做种体积 + # total_torrent_size = sum(info.get("total_size", 0) for info in torrent_info_map.values()) + total_torrent_size = self.__calculate_seeding_torrents_size(torrent_tasks=torrent_tasks) # 当总体积未超过最大阈值时,不需要执行删除操作 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,下限 {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,下限 {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) logger.info(f"托管种子数 {len(proxy_delete_torrents)},未托管种子数 {len(not_proxy_delete_torrents)}") @@ -2227,7 +2302,7 @@ class BrushFlow(_PluginBase): 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" + 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) @@ -2453,6 +2528,7 @@ class BrushFlow(_PluginBase): "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, "enable_site_config": brush_config.enable_site_config, "site_config": brush_config.site_config @@ -2604,7 +2680,8 @@ 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失败") + logger.error(f"{brush_config.downloader} 获取种子Hash失败" + f"{',请尝试开启代理下载种子' if not brush_config.proxy_download else ''}") return None return torrent_hash return None From f587969a5708f5c5ef0f6a82e479500ae421be3f Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sat, 30 Mar 2024 08:06:18 +0800 Subject: [PATCH 10/24] fix README --- README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 58e1dc2..d206df9 100644 --- a/README.md +++ b/README.md @@ -48,34 +48,38 @@ MoviePilot官方插件市场:https://github.com/jxxghp/MoviePilot-Plugins PS:MoviePilot中的其它事件也是同样方法实现响应: ```python class EventType(Enum): - # 插件重载 + # 插件需要重载 PluginReload = "plugin.reload" # 插件动作 PluginAction = "plugin.action" # 执行命令 CommandExcute = "command.excute" - # 站点删除 + # 站点已删除 SiteDeleted = "site.deleted" - # Webhook消息 - WebhookMessage = "webhook.message" + # 站点已更新 + SiteUpdated = "site.updated" # 转移完成 TransferComplete = "transfer.complete" - # 添加下载 + # 下载已添加 DownloadAdded = "download.added" # 删除历史记录 HistoryDeleted = "history.deleted" # 删除下载源文件 DownloadFileDeleted = "downloadfile.deleted" - # 用户外来消息 + # 收到用户外来消息 UserMessage = "user.message" - # 通知消息 + # 收到Webhook消息 + WebhookMessage = "webhook.message" + # 发送消息通知 NoticeMessage = "notice.message" # 名称识别请求 NameRecognize = "name.recognize" # 名称识别结果 NameRecognizeResult = "name.recognize.result" - # 站点信息更新 - SiteUpdated = "site.updated" + # 订阅已添加 + SubscribeAdded = "subscribe.added" + # 订阅已完成 + SubscribeComplete = "subscribe.complete" ``` ### 2. 如何在插件中实现远程命令响应? From 50ff3fb90bfd88b21f192fe86cf1184428475d4c Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sat, 30 Mar 2024 08:07:52 +0800 Subject: [PATCH 11/24] fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d206df9..889a95f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ MoviePilot官方插件市场:https://github.com/jxxghp/MoviePilot-Plugins - 可在插件目录中放置`requirement.txt`文件,用于指定插件依赖的第三方库,MoviePilot会在插件安装时自动安装依赖库。 ### 5. 界面开发 -- 插件支持`插件配置`及`详情展示`两个展示页面,通过配置化的方式组装,使用[Vuetify](https://vuetifyjs.com/)组件库,所有该组件库有的组件都可以通过Json配置使用。 +- 插件支持`插件配置`及`详情展示`两个展示页面,通过配置化的方式组装,使用 [Vuetify](https://vuetifyjs.com/) 组件库,所有该组件库有的组件都可以通过Json配置使用。 ## 常见问题 From 52e26a7b75c66c96a3991930dcde5e2cce642d20 Mon Sep 17 00:00:00 2001 From: ljmeng Date: Sat, 30 Mar 2024 11:06:49 +0800 Subject: [PATCH 12/24] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E7=AB=99=E7=82=B9?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +- plugins/crossseed/__init__.py | 118 +++++++++++++++++++++++----------- 2 files changed, 83 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 07eb18f..0f8000f 100644 --- a/package.json +++ b/package.json @@ -185,8 +185,8 @@ }, "CrossSeed": { "name": "青蛙辅种助手", - "description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音等。", - "version": "1.8", + "description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。", + "version": "1.9", "icon": "qingwa.png", "author": "233@qingwa", "level": 2 diff --git a/plugins/crossseed/__init__.py b/plugins/crossseed/__init__.py index a05945f..3d79420 100644 --- a/plugins/crossseed/__init__.py +++ b/plugins/crossseed/__init__.py @@ -33,18 +33,31 @@ class CSSiteConfig(object): 站点辅种配置类 """ - def __init__(self, site_name: str, site_url: str, site_passkey: str) -> None: - self.name = site_name - self.url = site_url.removesuffix("/") - self.passkey = site_passkey + def __init__( + self, + name: str = None, + url: str = None, + passkey: str = None, + id: int = None, + cookie: str = None, + ua: str = None, + proxy: bool = None, + ) -> None: + self.name = name + self.url = url + self.passkey = passkey + self.id = id + self.cookie = cookie + self.ua = ua + self.proxy = proxy def get_api_url(self): if self.name == "憨憨": - return f"{self.url}/npapi/pieces-hash" - return f"{self.url}/api/pieces-hash" + return f"{self.url}npapi/pieces-hash" + return f"{self.url}api/pieces-hash" def get_torrent_url(self, torrent_id: str): - return f"{self.url}/download.php?id={torrent_id}&passkey={self.passkey}" + return f"{self.url}download.php?id={torrent_id}&passkey={self.passkey}" class TorInfo: @@ -135,21 +148,20 @@ class CrossSeedHelper(object): "User-Agent": "CrossSeedHelper", } data = {"passkey": site.passkey, "pieces_hash": pieces_hash_set} + remote_torrent_infos = [] try: response = requests.post( site.get_api_url(), headers=headers, json=data, timeout=10 ) response.raise_for_status() + rsp_body = response.json() + if isinstance(rsp_body["data"], dict): + for pieces_hash, torrent_id in rsp_body["data"].items(): + remote_torrent_infos.append( + TorInfo.remote(site.name, pieces_hash, torrent_id) + ) except requests.exceptions.RequestException as e: return None, f"站点{site.name}请求失败:{e}" - rsp_body = response.json() - - remote_torrent_infos = [] - if isinstance(rsp_body["data"], dict): - for pieces_hash, torrent_id in rsp_body["data"].items(): - remote_torrent_infos.append( - TorInfo.remote(site.name, pieces_hash, torrent_id) - ) return remote_torrent_infos, None @@ -157,11 +169,11 @@ class CrossSeed(_PluginBase): # 插件名称 plugin_name = "青蛙辅种助手" # 插件描述 - plugin_desc = "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音等。" + plugin_desc = "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。" # 插件图标 plugin_icon = "qingwa.png" # 插件版本 - plugin_version = "1.8" + plugin_version = "1.9" # 插件作者 plugin_author = "233@qingwa" # 作者主页 @@ -206,7 +218,6 @@ class CrossSeed(_PluginBase): # 辅种缓存,出错的种子不再重复辅种,且无法清除。种子被删除404等情况 _permanent_error_caches = [] _torrentpaths = [] - _name_site_map = {} _site_cs_infos = [] # 辅种计数 total = 0 @@ -247,18 +258,49 @@ class CrossSeed(_PluginBase): self._sites = [site_id for site_id, site_name in all_sites if site_id in self._sites] # 拆分出选中的站点 site_names = [site_name for site_id, site_name in all_sites if site_id in self._sites] - # 拆分为映射关系 - self._name_site_map = {} - for site in self.siteoper.list_order_by_pri(): - self._name_site_map[site.name] = site - # 只给选中的站点构造站点配置 - self._site_cs_infos: List[CSSiteConfig] = [] + + # 整理所有可用内部站点信息 + all_site_cs_info_map : dict[str, CSSiteConfig] = dict() + for site in inner_site_list: + if site.is_active: + all_site_cs_info_map[site.name] = CSSiteConfig( + name=site.name, + url=site.url, + id=site.id, + cookie=site.cookie, + ua=site.ua, + proxy=site.proxy, + ) + for site in self.__custom_sites(): + all_site_cs_info_map[site.get("name")] = CSSiteConfig( + name=site.get("name"), + url=site.get("url"), + id=site.get("id"), + cookie=site.get("cookie"), + ua=site.get("ua"), + proxy=site.get("proxy"), + ) + self._sites = [site.id for site in all_site_cs_info_map.values() if site.id in self._sites] + site_names = [site.name for site in all_site_cs_info_map.values() if site.id in self._sites] + + # 整理passkey映射关系 + site_name_key_map = dict() for site_key in self._token.strip().split("\n"): site_key_arr = re.split("[\s::]+",site_key.strip()) site_name = site_key_arr[0] - db_site = self._name_site_map[site_name] - if site_name in site_names and db_site: - self._site_cs_infos.append(CSSiteConfig(site_name, db_site.url, site_key_arr[1])) + site_name_key_map[site_name] = site_key_arr[1] + + # 只给选中的站点补全站点配置 + self._site_cs_infos: List[CSSiteConfig] = [] + # 根据配置来补充passkey + for site_name in site_names: + site_key = site_name_key_map.get(site_name) + if not site_key: + logger.warning(f"未找到站点{site_name}的passkey, 请检查passkey配置是否有误,站点{site_name}将跳过辅种") + continue + site_cs_info = all_site_cs_info_map.get(site_name) + site_cs_info.passkey = site_key + self._site_cs_infos.append(site_cs_info) self.__update_config() @@ -709,14 +751,14 @@ class CrossSeed(_PluginBase): if not torrent_path.exists(): if downloader == "qbittorrent": # FIXME qb从4.4.0开始,种子文件以标题+序号的方式保存,目前只能尝试导出后再解析 - # logger.info(f"正在导出种子 {torrent.get('name')}({hash_str})") + logger.warn(f"QB种子文件不存在:{torrent_path} 尝试远程导出种子") try: torrent_data = torrent.export() torrent_info, err = TorInfo.from_data(torrent_data) except Exception as e: err = str(e) if not torrent_info: - logger.error(f"尝试导出种子 {hash_str} 出错 {err}") + logger.error(f"尝试远程导出种子 {hash_str} 出错 {err}") continue else: logger.error(f"种子文件不存在:{torrent_path}") @@ -873,12 +915,12 @@ class CrossSeed(_PluginBase): # 逐个站点查询可辅种数据 chunk_size = 100 for site_config in self._site_cs_infos: - db_site_info = self._name_site_map[site_config.name] - if not db_site_info: - logger.info(f"未在支持站点中找到{site_config.name}") remote_tors: List[TorInfo] = [] total_size = len(pieces_hashes) for i in range(0, len(pieces_hashes), chunk_size): + if self._event.is_set(): + logger.info(f"辅种服务停止") + return # 切片操作 chunk = pieces_hashes[i:i + chunk_size] # 处理分组 @@ -911,6 +953,9 @@ class CrossSeed(_PluginBase): logger.info(f"站点{site_config.name}正在做种或已经辅种过的种子数为{local_cnt}") for tor_info in not_local_tors: + if self._event.is_set(): + logger.info(f"辅种服务停止") + return if not tor_info: continue if not tor_info.torrent_id or not tor_info.pieces_hash: @@ -922,7 +967,7 @@ class CrossSeed(_PluginBase): logger.info(f"种子 {tor_info.get_name_id_tag()} 辅种失败且已缓存,跳过 ...") continue # 添加任务 - self.__download_torrent(tor=tor_info, site_config=site_config, site_info=db_site_info, + self.__download_torrent(tor=tor_info, site_config=site_config, downloader=downloader, save_path=save_paths.get(tor_info.pieces_hash)) @@ -967,7 +1012,6 @@ class CrossSeed(_PluginBase): self, tor: TorInfo, site_config: CSSiteConfig, - site_info: Site, downloader: str, save_path: str, ): @@ -984,9 +1028,9 @@ class CrossSeed(_PluginBase): # 下载种子文件 _, content, _, _, error_msg = self.torrent.download_torrent( url=torrent_url, - cookie=site_info.cookie, - ua=site_info.ua or settings.USER_AGENT, - proxy=True if site_info.proxy else False) + cookie=site_config.cookie, + ua=site_config.ua or settings.USER_AGENT, + proxy=True if site_config.proxy else False) # 兼容种子无法访问的情况 if not content or (isinstance(content, bytes) and "你没有该权限".encode(encoding="utf-8") in content): From eb05e91c578beb2b87a8814f4f97e874f948f56b Mon Sep 17 00:00:00 2001 From: Allen Date: Sat, 30 Mar 2024 05:27:38 +0000 Subject: [PATCH 13/24] fix bug --- package.json | 2 +- plugins/downloaderhelper/__init__.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0f8000f..621f4c7 100644 --- a/package.json +++ b/package.json @@ -458,7 +458,7 @@ "DownloaderHelper": { "name": "下载器助手", "description": "自动做种、站点标签、自动删种。", - "version": "1.2", + "version": "1.3", "icon": "DownloaderHelper.png", "author": "hotlcc", "level": 2 diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 49463f4..072e3a6 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -32,7 +32,7 @@ class DownloaderHelper(_PluginBase): # 插件图标 plugin_icon = "DownloaderHelper.png" # 插件版本 - plugin_version = "1.2" + plugin_version = "1.3" # 插件作者 plugin_author = "hotlcc" # 作者主页 @@ -1541,12 +1541,12 @@ class DownloaderHelper(_PluginBase): # 执行 logger.info('下载添加事件监听任务执行开始...') context = TaskContext().enable_seeding(False).enable_tagging(True).enable_delete(False) - hash_str = event.event_data.get('hash') + hash = event.event_data.get('hash') if hash: - context.select_torrent(hash_str) + context.select_torrent(torrent=hash) username = event.event_data.get('username') if username: - context.select_username(username) + context.set_username(username=username) self.__run_for_all(context=context) logger.info('下载添加事件监听任务执行结束') From 7878d4ad1b3a73ce74b468b4936d1f43b829f577 Mon Sep 17 00:00:00 2001 From: Allen Date: Sat, 30 Mar 2024 05:32:35 +0000 Subject: [PATCH 14/24] fix bug --- plugins/downloaderhelper/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 072e3a6..52ebeb6 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -1541,9 +1541,9 @@ class DownloaderHelper(_PluginBase): # 执行 logger.info('下载添加事件监听任务执行开始...') context = TaskContext().enable_seeding(False).enable_tagging(True).enable_delete(False) - hash = event.event_data.get('hash') - if hash: - context.select_torrent(torrent=hash) + _hash = event.event_data.get('hash') + if _hash: + context.select_torrent(torrent=_hash) username = event.event_data.get('username') if username: context.set_username(username=username) From 9bcad7e825d08470a7ec3be781d3029875a64aa8 Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sat, 30 Mar 2024 16:16:02 +0800 Subject: [PATCH 15/24] fix brushflow log_more --- plugins/brushflow/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/brushflow/__init__.py b/plugins/brushflow/__init__.py index 7842b5b..064dc83 100644 --- a/plugins/brushflow/__init__.py +++ b/plugins/brushflow/__init__.py @@ -262,6 +262,12 @@ 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}") + # 停止现有任务 self.stop_service() @@ -282,7 +288,7 @@ class BrushFlow(_PluginBase): # 如果开启&存在站点时,才需要启用后台任务 self._task_brush_enable = brush_config.enabled and brush_config.brushsites - # brush_config.onlyonce = True + # brush_config.onlyonce = True # 检查是否启用了一次性任务 if brush_config.onlyonce: From 89ec00e2fb87b086c6c80ad004735e0f9c7ca585 Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sat, 30 Mar 2024 16:38:04 +0800 Subject: [PATCH 16/24] =?UTF-8?q?feature:=20#125=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=A3=9E=E4=B9=A6=E6=9C=BA=E5=99=A8=E4=BA=BA=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- icons/FeiShu_A.png | Bin 0 -> 13812 bytes package.json | 8 ++ plugins/feishumsg/__init__.py | 263 ++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 icons/FeiShu_A.png create mode 100644 plugins/feishumsg/__init__.py diff --git a/icons/FeiShu_A.png b/icons/FeiShu_A.png new file mode 100644 index 0000000000000000000000000000000000000000..c0b0446c8396fbb6cb63bbfd2eab46efac8626cc GIT binary patch literal 13812 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 p`1K$I0{j2?KYaclvi30GP&4 bool: + return self._enabled and (True if self._webhookurl else False) + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + pass + + def get_api(self) -> List[Dict[str, Any]]: + pass + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + """ + 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 + """ + # 遍历 NotificationType 枚举,生成消息类型选项 + msg_type_options = [] + default_msg_type_values = [] + for item in NotificationType: + msg_type_options.append({ + "title": item.value, + "value": item.name + }) + default_msg_type_values.append(item.name) + return [ + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enabled', + 'label': '启用插件', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'webhookurl', + 'label': 'WebHook地址', + 'placeholder': 'https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxxxx', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'secret', + 'label': '密钥', + 'placeholder': '如设置了签名校验,请输入密钥', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'multiple': True, + 'chips': True, + 'model': 'msgtypes', + 'label': '消息类型', + 'items': msg_type_options + } + } + ] + } + ] + }, + ] + } + ], { + "enabled": False, + 'webhookurl': '', + 'msgtypes': default_msg_type_values, + 'secret': '', + } + + def get_page(self) -> List[dict]: + pass + + @eventmanager.register(EventType.NoticeMessage) + def send(self, event: Event): + """ + 消息发送事件 + """ + if not self.get_state(): + return + + if not event.event_data: + return + + msg_body = event.event_data + # 渠道 + channel = msg_body.get("channel") + if channel: + logger.info(f"channel: {channel} 不进行消息推送") + return + # 类型 + msg_type: NotificationType = msg_body.get("type") + # 标题 + title = msg_body.get("title") + # 文本 + text = msg_body.get("text") + # 图像 + image = msg_body.get("image") + + if not title and not text: + logger.warn("标题和内容不能同时为空") + return + + if (msg_type and self._msgtypes + and msg_type.name not in self._msgtypes): + logger.info(f"消息类型 {msg_type.value} 未开启消息发送") + return + + try: + payload = { + "msg_type": "post", + "content": { + "post": { + "zh_cn": { + "title": title, + "content": [ + [{ + "tag": "text", + "text": text + }] + ] + } + } + } + } + + # 如果存在密钥时,还需要进行签名处理 + if self._secret: + timestamp = str(int(time.time())) + sign = self.gen_sign(timestamp, self._secret) + payload.update({ + "timestamp": timestamp, + "sign": sign + }) + + res = RequestUtils(content_type="application/json").post_res(url=self._webhookurl, json=payload) + if res and res.status_code == 200: + ret_json = res.json() + errno = ret_json.get('code') + error = ret_json.get('msg') + if errno == 0: + logger.info("飞书机器人消息发送成功") + else: + logger.warn(f"飞书机器人消息发送失败,错误码:{errno},错误原因:{error}") + elif res is not None: + logger.warn(f"飞书机器人消息发送失败,错误码:{res.status_code},错误原因:{res.reason}") + else: + logger.warn("飞书机器人消息发送失败,未获取到返回信息") + except Exception as msg_e: + logger.error(f"飞书机器人消息发送失败,{str(msg_e)}") + + def stop_service(self): + """ + 退出插件 + """ + pass + + @staticmethod + def gen_sign(timestamp, secret): + # 拼接timestamp和secret + string_to_sign = '{}\n{}'.format(timestamp, secret) + hmac_code = hmac.new(string_to_sign.encode("utf-8"), digestmod=hashlib.sha256).digest() + # 对结果进行base64处理 + sign = base64.b64encode(hmac_code).decode('utf-8') + return sign From bab104230aff325edb2699b0415555c612aadaad Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sat, 30 Mar 2024 22:55:07 +0800 Subject: [PATCH 17/24] =?UTF-8?q?fix=20burshflow=20=E7=AB=99=E7=82=B9?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E9=85=8D=E7=BD=AE=E5=BC=80=E5=90=AF=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/brushflow/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/brushflow/__init__.py b/plugins/brushflow/__init__.py index 064dc83..148927d 100644 --- a/plugins/brushflow/__init__.py +++ b/plugins/brushflow/__init__.py @@ -117,8 +117,10 @@ class BrushConfig: self.group_site_configs[sitename] = BrushConfig(config=full_config, process_site_config=False) except Exception as e: - logger.error(f"解析站点配置失败,请检查配置项。错误详情: {e}") + logger.error(f"解析站点配置失败,已停用插件并关闭站点独立配置,请检查配置项,错误详情: {e}") self.group_site_configs = {} + self.enable_site_config = False + self.enabled = False def get_site_config(self, sitename): """ From 1eac7363ec0cf17d3f3f1c796bd42b479aaa78b6 Mon Sep 17 00:00:00 2001 From: Pixel-LH Date: Sun, 31 Mar 2024 20:18:19 +0800 Subject: [PATCH 18/24] Rename requirements.txt to requirement.txt --- plugins/vcbanimemonitor/requirement.txt | 1 + plugins/vcbanimemonitor/requirements.txt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 plugins/vcbanimemonitor/requirement.txt delete mode 100644 plugins/vcbanimemonitor/requirements.txt diff --git a/plugins/vcbanimemonitor/requirement.txt b/plugins/vcbanimemonitor/requirement.txt new file mode 100644 index 0000000..0eed2d7 --- /dev/null +++ b/plugins/vcbanimemonitor/requirement.txt @@ -0,0 +1 @@ +roman~=4.1 diff --git a/plugins/vcbanimemonitor/requirements.txt b/plugins/vcbanimemonitor/requirements.txt deleted file mode 100644 index 7001735..0000000 --- a/plugins/vcbanimemonitor/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -roman~=4.1 \ No newline at end of file From fa1670bda74dedac9231718ed9892d8cae2162ba Mon Sep 17 00:00:00 2001 From: Pixel-LH Date: Sun, 31 Mar 2024 20:29:44 +0800 Subject: [PATCH 19/24] Rename requirement.txt to requirements.txt --- plugins/vcbanimemonitor/{requirement.txt => requirements.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/vcbanimemonitor/{requirement.txt => requirements.txt} (100%) diff --git a/plugins/vcbanimemonitor/requirement.txt b/plugins/vcbanimemonitor/requirements.txt similarity index 100% rename from plugins/vcbanimemonitor/requirement.txt rename to plugins/vcbanimemonitor/requirements.txt From e649ff9dec4cd43426bce9643759ce3ac42f2e43 Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sun, 31 Mar 2024 21:18:01 +0800 Subject: [PATCH 20/24] fix brushflow tranmission --- package.json | 2 +- plugins/brushflow/__init__.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index dab3491..9a83da8 100644 --- a/package.json +++ b/package.json @@ -226,7 +226,7 @@ "BrushFlow": { "name": "站点刷流", "description": "自动托管刷流,将会提高对应站点的访问频率。", - "version": "2.1", + "version": "2.2", "icon": "brush.jpg", "author": "jxxghp", "level": 2 diff --git a/plugins/brushflow/__init__.py b/plugins/brushflow/__init__.py index 148927d..c9d3d24 100644 --- a/plugins/brushflow/__init__.py +++ b/plugins/brushflow/__init__.py @@ -184,7 +184,7 @@ class BrushFlow(_PluginBase): # 插件图标 plugin_icon = "brush.jpg" # 插件版本 - plugin_version = "2.1" + plugin_version = "2.2" # 插件作者 plugin_author = "jxxghp,InfinityPacer" # 作者主页 @@ -2252,7 +2252,7 @@ class BrushFlow(_PluginBase): need_delete_hashes = [] - # 即使开了动态删除,但是也有可能部分站点单独设置了关闭,这里根据种子动态进行分组,先处理不需要动态的种子,按设置的规则进行删除 + # 即使开了动态删除,但是也有可能部分站点单独设置了关闭,这里根据种子托管进行分组,先处理不需要托管的种子,按设置的规则进行删除 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)}") @@ -2264,7 +2264,7 @@ class BrushFlow(_PluginBase): 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, @@ -2274,7 +2274,7 @@ class BrushFlow(_PluginBase): 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种子后按加入时间倒序进行删除 + # 在完成初始删除步骤后,如果总体积仍然超过最小阈值,则进一步找到已完成种子并排除HR种子后按加入时间正序进行删除 if total_torrent_size > min_size: # 重新计算当前的种子列表,排除已删除的种子 remaining_hashes = list( @@ -2707,7 +2707,7 @@ class BrushFlow(_PluginBase): else: logger.error('代理下载种子失败,继续尝试传递种子地址到下载器进行下载') if torrent_content: - torrent = self.tr.add_torrent(content=torrent.enclosure, + torrent = self.tr.add_torrent(content=torrent_content, download_dir=download_dir, cookie=torrent.site_cookie, labels=["已整理", brush_config.brush_tag]) From 0871cf8df6a0517a5dfd421f4890cf153042ce94 Mon Sep 17 00:00:00 2001 From: ljmeng Date: Mon, 1 Apr 2024 00:52:06 +0800 Subject: [PATCH 21/24] =?UTF-8?q?=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- plugins/crossseed/__init__.py | 47 ++++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9a83da8..c818c60 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "CrossSeed": { "name": "青蛙辅种助手", "description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。", - "version": "1.9", + "version": "2.0", "icon": "qingwa.png", "author": "233@qingwa", "level": 2 diff --git a/plugins/crossseed/__init__.py b/plugins/crossseed/__init__.py index 3d79420..af5363c 100644 --- a/plugins/crossseed/__init__.py +++ b/plugins/crossseed/__init__.py @@ -1,6 +1,7 @@ import hashlib import os import re +import time from datetime import datetime, timedelta from pathlib import Path from threading import Event @@ -42,6 +43,7 @@ class CSSiteConfig(object): cookie: str = None, ua: str = None, proxy: bool = None, + query_gap: int = 1, ) -> None: self.name = name self.url = url @@ -50,6 +52,7 @@ class CSSiteConfig(object): self.cookie = cookie self.ua = ua self.proxy = proxy + self.query_gap = query_gap def get_api_url(self): if self.name == "憨憨": @@ -160,6 +163,7 @@ class CrossSeedHelper(object): remote_torrent_infos.append( TorInfo.remote(site.name, pieces_hash, torrent_id) ) + time.sleep(site.query_gap) except requests.exceptions.RequestException as e: return None, f"站点{site.name}请求失败:{e}" return remote_torrent_infos, None @@ -173,7 +177,7 @@ class CrossSeed(_PluginBase): # 插件图标 plugin_icon = "qingwa.png" # 插件版本 - plugin_version = "1.9" + plugin_version = "2.0" # 插件作者 plugin_author = "233@qingwa" # 作者主页 @@ -240,7 +244,7 @@ class CrossSeed(_PluginBase): self._downloaders = config.get("downloaders") self._torrentpath = config.get("torrentpath") # 种子路径和下载器对应 /qb,/tr - self._torrentpaths = self._torrentpath.split(",") + self._torrentpaths = self._torrentpath.strip().split(",") self._sites = config.get("sites") or [] self._notify = config.get("notify") self._nolabels = config.get("nolabels") @@ -282,13 +286,21 @@ class CrossSeed(_PluginBase): ) self._sites = [site.id for site in all_site_cs_info_map.values() if site.id in self._sites] site_names = [site.name for site in all_site_cs_info_map.values() if site.id in self._sites] - + # 整理passkey映射关系 site_name_key_map = dict() + site_name_gap_map = dict() for site_key in self._token.strip().split("\n"): site_key_arr = re.split("[\s::]+",site_key.strip()) site_name = site_key_arr[0] site_name_key_map[site_name] = site_key_arr[1] + if len(site_key_arr) >= 2: + if str.isdigit(site_key_arr[2]): + site_name_gap_map[site_name] = int(site_key_arr[2]) + else: + logger.warn( + f"站点{site_name}配置的查询请求间隔时间不为整数,不能生效, 请修改 {site_key_arr[2]}" + ) # 只给选中的站点补全站点配置 self._site_cs_infos: List[CSSiteConfig] = [] @@ -300,6 +312,10 @@ class CrossSeed(_PluginBase): continue site_cs_info = all_site_cs_info_map.get(site_name) site_cs_info.passkey = site_key + # 追加设置的请求间隔时间 + site_query_gap = site_name_gap_map.get(site_name) + if site_query_gap: + site_cs_info.query_gap = site_query_gap self._site_cs_infos.append(site_cs_info) self.__update_config() @@ -653,7 +669,7 @@ class CrossSeed(_PluginBase): 'type': 'info', 'variant': 'tonal', 'text': '1. 定时任务周期建议每次辅种间隔时间大于1天,不填写每天上午2点到7点随机辅种一次; ' - '2. 支持辅种站点列表:青蛙【已验证】,AGSVPT,麒麟,UBits,聆音 等,配置passkey时,站点名称需严格和上面选项一致,只有选中的站点会辅种,passkey可保存多个; ' + '2. 支持辅种站点列表:青蛙、AGSVPT、红豆饭、麒麟、UBits、聆音等,配置passkey时,站点名称需严格和上面选项一致,只有选中的站点会辅种,passkey可保存多个; ' '3. 请勿与IYUU辅种插件同时添加相同站点,可能会有冲突,且意义不大;' '4. 测试站点是否支持的方法:【站点域名/api/pieces-hash】接口访问返回405则大概率支持 ' } @@ -661,6 +677,29 @@ class CrossSeed(_PluginBase): ] } ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': '【进阶设置】如果辅种过程中访问/api/pieces-hash接口偶尔会失败,可以设置请求间隔时间。 ' + '可以在passkey后增加 :3 来将某个站点的请求间隔调整为3秒,3可以改为其他数字,只能为整数,默认请求间隔为1秒。 ' + '示例配置 站点名称:Passkey:3' + } + } + ] + } + ] } ] } From 5b0c6663c2856552e21dedda15aaa75d8fb83474 Mon Sep 17 00:00:00 2001 From: ljmeng Date: Mon, 1 Apr 2024 01:02:20 +0800 Subject: [PATCH 22/24] fix --- plugins/crossseed/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/crossseed/__init__.py b/plugins/crossseed/__init__.py index af5363c..fc75269 100644 --- a/plugins/crossseed/__init__.py +++ b/plugins/crossseed/__init__.py @@ -294,7 +294,7 @@ class CrossSeed(_PluginBase): site_key_arr = re.split("[\s::]+",site_key.strip()) site_name = site_key_arr[0] site_name_key_map[site_name] = site_key_arr[1] - if len(site_key_arr) >= 2: + if len(site_key_arr) > 2: if str.isdigit(site_key_arr[2]): site_name_gap_map[site_name] = int(site_key_arr[2]) else: From e1c59c93f21ebe0697c7719e0ecba9f95d56c134 Mon Sep 17 00:00:00 2001 From: Allen Date: Mon, 1 Apr 2024 02:18:04 +0000 Subject: [PATCH 23/24] =?UTF-8?q?=E8=A1=A8=E5=8D=95=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- plugins/downloaderhelper/__init__.py | 74 +++++++++++++++------------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index c818c60..b65b458 100644 --- a/package.json +++ b/package.json @@ -458,7 +458,7 @@ "DownloaderHelper": { "name": "下载器助手", "description": "自动做种、站点标签、自动删种。", - "version": "1.3", + "version": "1.4", "icon": "DownloaderHelper.png", "author": "hotlcc", "level": 2 diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 52ebeb6..82c0efd 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -32,7 +32,7 @@ class DownloaderHelper(_PluginBase): # 插件图标 plugin_icon = "DownloaderHelper.png" # 插件版本 - plugin_version = "1.3" + plugin_version = "1.4" # 插件作者 plugin_author = "hotlcc" # 作者主页 @@ -177,42 +177,42 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12, - 'title': '插件总开关' + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 }, 'content': [{ 'component': 'VSwitch', 'props': { 'model': 'enable', - 'label': '启用插件' + 'label': '启用插件', + 'hint': '插件总开关' } }] }, { 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12, - 'title': '执行插件任务后是否发送通知' + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 }, 'content': [{ 'component': 'VSwitch', 'props': { 'model': 'enable_notify', - 'label': '发送通知' + 'label': '发送通知', + 'hint': '执行插件任务后是否发送通知' } }] }, { 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12, - 'title': '保存插件配置后是否立即触发一次插件任务运行' + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 }, 'content': [{ 'component': 'VSwitch', 'props': { 'model': 'run_once', - 'label': '立即运行一次' + 'label': '立即运行一次', + 'hint': '保存插件配置后是否立即触发一次插件任务运行' } }] }] @@ -222,42 +222,42 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12, - 'title': '监听下载添加事件。当MoviePilot添加下载任务时,会触发执行本插件进行自动做种和添加站点标签。' + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 }, 'content': [{ 'component': 'VSwitch', 'props': { 'model': 'listen_download_event', - 'label': '监听下载事件' + 'label': '监听下载事件', + 'hint': '监听下载添加事件。当MoviePilot添加下载任务时,会触发执行本插件进行自动做种和添加站点标签。' } }] }, { 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12, - 'title': '监听源文件删除事件。当在【历史记录】中删除源文件时,会自动触发运行本插件任务进行自动删种。' + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 }, 'content': [{ 'component': 'VSwitch', 'props': { 'model': 'listen_source_file_event', - 'label': '监听源文件事件' + 'label': '监听源文件事件', + 'hint': '监听源文件删除事件。当在【历史记录】中删除源文件时,会自动触发运行本插件任务进行自动删种。' } }] }, { 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12, - 'title': '给种子添加站点标签时,是否优先以站点名称作为标签内容(否则将使用域名关键字)?' + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 }, 'content': [{ 'component': 'VSwitch', 'props': { 'model': 'site_name_priority', - 'label': '站点名称优先' + 'label': '站点名称优先', + 'hint': '给种子添加站点标签时,是否优先以站点名称作为标签内容(否则将使用域名关键字)?' } }] }] @@ -267,44 +267,44 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12, - 'title': '设置插件任务执行周期。支持5位cron表达式,应避免任务执行过于频繁,例如:0/30 * * * *。缺省时不执行定时任务,但不影响监听任务的执行。' + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 }, 'content': [{ 'component': 'VTextField', 'props': { 'model': 'cron', 'label': '定时执行周期', - 'placeholder': '0/30 * * * *' + 'placeholder': '0/30 * * * *', + 'hint': '设置插件任务执行周期。支持5位cron表达式,应避免任务执行过于频繁,例如:0/30 * * * *。缺省时不执行定时任务,但不影响监听任务的执行。' } }] }, { 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12, - 'title': '下载器中的种子有这些标签时不进行任何操作,多个标签使用英文“,”分割' + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 }, 'content': [{ 'component': 'VTextField', 'props': { 'model': 'exclude_tags', - 'label': '排除种子标签' + 'label': '排除种子标签', + 'hint': '下载器中的种子有这些标签时不进行任何操作,多个标签使用英文“,”分割' } }] }, { 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12, - 'title': '给种子添加站点标签时的标签前缀,默认值为“站点/”' + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 }, 'content': [{ 'component': 'VTextField', 'props': { 'model': 'tag_prefix', 'label': '站点标签前缀', - 'placeholder': '站点/' + 'placeholder': '站点/', + 'hint': '给种子添加站点标签时的标签前缀,默认值为“站点/”' } }] }] @@ -313,8 +313,7 @@ class DownloaderHelper(_PluginBase): 'content': [{ 'component': 'VCol', 'props': { - 'cols': 12, - 'title': 'Tracker映射。用于在站点打标签时,指定tracker和站点域名不同的种子的域名对应关系;前面为tracker域名(完整域名或者主域名皆可),中间是英文冒号,后面是站点域名。' + 'cols': 12 }, 'content': [{ 'component': 'VTextarea', @@ -324,7 +323,8 @@ class DownloaderHelper(_PluginBase): 'placeholder': '格式:\n' ':\n' '例如:\n' - 'chdbits.xyz:ptchdbits.co' + 'chdbits.xyz:ptchdbits.co', + 'hint': 'Tracker映射。用于在站点打标签时,指定tracker和站点域名不同的种子的域名对应关系;前面为tracker域名(完整域名或者主域名皆可),中间是英文冒号,后面是站点域名。' } }] }] @@ -373,7 +373,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VSwitch', 'props': { 'model': 'qb_enable', - 'label': '任务开关' + 'label': '任务开关', + 'hint': '该下载器子任务的开关' } }] }, { @@ -386,7 +387,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VSwitch', 'props': { 'model': 'qb_enable_seeding', - 'label': '自动做种' + 'label': '自动做种', + 'hint': '是否开启自动做种功能' } }] }, { @@ -399,7 +401,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VSwitch', 'props': { 'model': 'qb_enable_tagging', - 'label': '站点标签' + 'label': '站点标签', + 'hint': '是否开启站点标签功能' } }] }, { @@ -412,7 +415,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VSwitch', 'props': { 'model': 'qb_enable_delete', - 'label': '自动删种' + 'label': '自动删种', + 'hint': '是否开启自动删种功能' } }] }] From 58863433ce57e842af171038c1504d52cb8d9a8e Mon Sep 17 00:00:00 2001 From: jxxghp Date: Mon, 1 Apr 2024 11:05:43 +0800 Subject: [PATCH 24/24] fix DownloaderHelper ui --- plugins/downloaderhelper/__init__.py | 29 +++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 82c0efd..817c264 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -177,7 +177,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 + 'md': 4, + 'xl': 3 }, 'content': [{ 'component': 'VSwitch', @@ -191,7 +192,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 + 'md': 4, + 'xl': 3 }, 'content': [{ 'component': 'VSwitch', @@ -205,7 +207,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 + 'md': 4, + 'xl': 3 }, 'content': [{ 'component': 'VSwitch', @@ -215,14 +218,12 @@ class DownloaderHelper(_PluginBase): 'hint': '保存插件配置后是否立即触发一次插件任务运行' } }] - }] - }, { - 'component': 'VRow', - 'content': [{ + }, { 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 + 'md': 4, + 'xl': 3 }, 'content': [{ 'component': 'VSwitch', @@ -236,7 +237,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 + 'md': 4, + 'xl': 3 }, 'content': [{ 'component': 'VSwitch', @@ -250,7 +252,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 + 'md': 4, + 'xl': 3 }, 'content': [{ 'component': 'VSwitch', @@ -267,7 +270,7 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 + 'md': 4 }, 'content': [{ 'component': 'VTextField', @@ -282,7 +285,7 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 + 'md': 4 }, 'content': [{ 'component': 'VTextField', @@ -296,7 +299,7 @@ class DownloaderHelper(_PluginBase): 'component': 'VCol', 'props': { 'cols': 12, - 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 6, 'xs': 12 + 'md': 4 }, 'content': [{ 'component': 'VTextField',