From 59d2d936a0d343db80df6dfbdf3a50ea14722a52 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 26 Apr 2024 11:30:30 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=A6=92=E5=A4=B4?= =?UTF-8?q?=E6=96=B0=E6=9E=B6=E6=9E=84=E8=BE=85=E7=A7=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 7 ++++-- plugins/iyuuautoseed/__init__.py | 37 +++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5d14ef7..d3ecb75 100644 --- a/package.json +++ b/package.json @@ -208,10 +208,13 @@ "IYUUAutoSeed": { "name": "IYUU自动辅种", "description": "基于IYUU官方Api实现自动辅种。", - "version": "1.3", + "version": "1.4", "icon": "IYUU.png", "author": "jxxghp", - "level": 2 + "level": 2, + "history": { + "v1.4": "支持馒头新架构辅种" + } }, "CrossSeed": { "name": "青蛙辅种助手", diff --git a/plugins/iyuuautoseed/__init__.py b/plugins/iyuuautoseed/__init__.py index 7ce742b..1e3ba3d 100644 --- a/plugins/iyuuautoseed/__init__.py +++ b/plugins/iyuuautoseed/__init__.py @@ -34,7 +34,7 @@ class IYUUAutoSeed(_PluginBase): # 插件图标 plugin_icon = "IYUU.png" # 插件版本 - plugin_version = "1.3" + plugin_version = "1.4" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -892,7 +892,35 @@ class IYUUAutoSeed(_PluginBase): 拼装种子下载链接 """ - def __is_special_site(url): + def __is_mteam(url: str): + """ + 判断是否为mteam站点 + """ + return True if "m-team." in url else False + + def __get_mteam_enclosure(tid: str): + """ + 获取mteam种子下载链接 + """ + _apikey = self.systemconfig.get(f"site.m-team.apikey") + if not _apikey: + logger.error("m-team站点的apikey未配置") + return None + with RequestUtils( + headers={ + 'Content-Type': 'application/json', + 'User-Agent': f'{site.get("ua")}', + 'Accept': 'application/json, text/plain, */*', + 'x-api-key': _apikey + } + ).post_res(f"{site.get('url')}api/torrent/genDlToken", params={ + 'id': tid + }) as res: + if not res: + logger.warn(f"m-team 获取种子下载链接失败:{tid}") + return None + + def __is_special_site(url: str): """ 判断是否为特殊站点 """ @@ -910,7 +938,10 @@ class IYUUAutoSeed(_PluginBase): return False try: - if __is_special_site(site.get('url')): + if __is_mteam(site.get('url')): + # 调用mteam接口获取下载链接 + return __get_mteam_enclosure(seed.get("torrent_id")) + elif __is_special_site(site.get('url')): # 从详情页面获取下载链接 return self.__get_torrent_url_from_page(seed=seed, site=site) else: From 08a899f1868f0016a8c1c60cd3e81378e7ec61d2 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 26 Apr 2024 11:37:41 +0800 Subject: [PATCH 02/11] fix log --- plugins/iyuuautoseed/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/iyuuautoseed/__init__.py b/plugins/iyuuautoseed/__init__.py index 1e3ba3d..2d847e6 100644 --- a/plugins/iyuuautoseed/__init__.py +++ b/plugins/iyuuautoseed/__init__.py @@ -971,7 +971,7 @@ class IYUUAutoSeed(_PluginBase): flags=re.IGNORECASE) return f"{site.get('url')}{download_url}" except Exception as e: - logger.warn(f"站点 {site.get('name')} Url转换失败:{str(e)},尝试通过详情页面获取种子下载链接 ...") + logger.warn(f"{site.get('name')} {base_url} Url转换失败:{str(e)},尝试通过详情页面获取种子下载链接 ...") return self.__get_torrent_url_from_page(seed=seed, site=site) def __get_torrent_url_from_page(self, seed: dict, site: dict): From 5ef61974fbe00873df9b820373e6f0a7f5241a01 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 26 Apr 2024 11:54:46 +0800 Subject: [PATCH 03/11] fix log --- plugins/iyuuautoseed/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/iyuuautoseed/__init__.py b/plugins/iyuuautoseed/__init__.py index 2d847e6..ec2e5da 100644 --- a/plugins/iyuuautoseed/__init__.py +++ b/plugins/iyuuautoseed/__init__.py @@ -971,7 +971,7 @@ class IYUUAutoSeed(_PluginBase): flags=re.IGNORECASE) return f"{site.get('url')}{download_url}" except Exception as e: - logger.warn(f"{site.get('name')} {base_url} Url转换失败:{str(e)},尝试通过详情页面获取种子下载链接 ...") + logger.warn(f"{site.get('name')} Url转换失败,{str(e)}:site_url={site.get('url')},base_url={base_url}, seed={seed}") return self.__get_torrent_url_from_page(seed=seed, site=site) def __get_torrent_url_from_page(self, seed: dict, site: dict): From f3c5c9fd29dd03a58bdc1efe637ccc8a2ece3c63 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 26 Apr 2024 12:05:28 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20=5F=5Finit=5F=5F.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/iyuuautoseed/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/iyuuautoseed/__init__.py b/plugins/iyuuautoseed/__init__.py index ec2e5da..5eb8788 100644 --- a/plugins/iyuuautoseed/__init__.py +++ b/plugins/iyuuautoseed/__init__.py @@ -760,7 +760,7 @@ class IYUUAutoSeed(_PluginBase): site_domain = StringUtils.get_url_domain(site_url) # 站点信息 site_info = self.sites.get_indexer(site_domain) - if not site_info: + if not site_info or not site_info.get('url'): logger.debug(f"没有维护种子对应的站点:{site_url}") return False if self._sites and site_info.get('id') not in self._sites: From d3c96051ca10c93d0a1d9c6652218f473b41185a Mon Sep 17 00:00:00 2001 From: Allen Date: Fri, 26 Apr 2024 16:11:26 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=90=E8=81=9A?= =?UTF-8?q?=E5=90=88=E7=AB=99=E7=82=B9=E5=BC=80=E5=85=B3=E3=80=91=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 8 + plugins/mergesiteswitch/__init__.py | 912 ++++++++++++++++++++++++++++ 2 files changed, 920 insertions(+) create mode 100644 plugins/mergesiteswitch/__init__.py diff --git a/package.json b/package.json index 9043276..1c18680 100644 --- a/package.json +++ b/package.json @@ -565,5 +565,13 @@ "icon": "PluginAutoUpgrade.png", "author": "hotlcc", "level": 1 + }, + "MergeSiteSwitch": { + "name": "聚合站点开关", + "description": "统一管理所有与站点相关的开关。", + "version": "1.0", + "icon": "world.png", + "author": "hotlcc", + "level": 2 } } diff --git a/plugins/mergesiteswitch/__init__.py b/plugins/mergesiteswitch/__init__.py new file mode 100644 index 0000000..96456d9 --- /dev/null +++ b/plugins/mergesiteswitch/__init__.py @@ -0,0 +1,912 @@ +import inspect +import os +from typing import Any, List, Dict, Tuple + +from app.core.event import eventmanager, Event +from app.db.models.site import Site +from app.db.site_oper import SiteOper +from app.db.systemconfig_oper import SystemConfigOper +from app.log import logger +from app.plugins import _PluginBase +from app.schemas.types import SystemConfigKey, EventType + + +class MergeSiteSwitch(_PluginBase): + # 插件名称 + plugin_name = "聚合站点开关" + # 插件描述 + plugin_desc = "统一管理所有与站点相关的开关。" + # 插件图标 + plugin_icon = "world.png" + # 插件版本 + plugin_version = "1.0" + # 插件作者 + plugin_author = "hotlcc" + # 作者主页 + author_url = "https://github.com/hotlcc" + # 插件配置项ID前缀 + plugin_config_prefix = "com.hotlcc.mergesiteswitch." + # 加载顺序 + plugin_order = 66 + # 可使用的用户级别 + auth_level = 2 + + # 依赖组件 + # 站点操作 + __site_oper: SiteOper = SiteOper() + # 系统配置操作 + __system_config_oper: SystemConfigOper = SystemConfigOper() + + # 其它插件ID + # 站点自动签到 + __plugin_id_auto_signin: str = 'AutoSignIn' + # 站点数据统计 + __plugin_id_site_statistic: str = 'SiteStatistic' + # IYUU自动辅种 + __plugin_id_iyuu_auto_seed: str = 'IYUUAutoSeed' + # 站点刷流 + __plugin_id_brush_flow: str = 'BrushFlow' + + # 配置相关 + # 插件缺省配置 + __config_default: Dict[str, Any] = {} + # 插件用户配置 + __config: Dict[str, Any] = {} + + def init_plugin(self, config: dict = None): + """ + 初始化插件 + """ + # 加载配置 + self.__config = config + # 当页面通过调用接口保存配置时保存其它各项配置 + if self.__check_stack_is_save_config_request(): + self.__set_config(config=config) + + def get_state(self) -> bool: + """ + 获取插件状态 + """ + return self.__check_any_follow_enable_sites() + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + """ + 定义远程控制命令 + :return: 命令关键字、事件、描述、附带数据 + """ + pass + + def get_api(self) -> List[Dict[str, Any]]: + """ + 获取插件API + """ + pass + + def get_service(self) -> List[Dict[str, Any]]: + """ + 注册插件公共服务 + """ + pass + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + """ + 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 + """ + # 站点选项 + site_options = self.__get_site_options() + # 已安装的插件IDs + installed_plugin_ids = self.__get_installed_plugin_ids() + # 建议的配置 + config_suggest = {} + + # 表单内容 + form_content = [{ + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 12, 'xl': 12, 'lg': 12, 'md': 12, 'sm': 12, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'enable_sites', + 'label': '启用的站点', + 'multiple': True, + 'chips': True, + 'items': site_options, + 'hint': '对应功能【站点管理 / 添加编辑站点 / 启用】' + } + }] + }] + }, { + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 9, 'xl': 9, 'lg': 9, 'md': 9, 'sm': 8, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'search_sites', + 'label': '设定 / 搜索 / 搜索站点', + 'multiple': True, + 'chips': True, + 'items': site_options, + 'hint': '只有选中的站点才会在搜索中使用。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 4, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': 'search_follow_enable_sites', + 'label': '跟随启用的站点', + 'hint': '与站点的启用状态保持一致,保存时会立即生效,并在后台监听站点状态变化实时生效。' + } + }] + }] + }, { + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 9, 'xl': 9, 'lg': 9, 'md': 9, 'sm': 8, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'rss_sites', + 'label': '设定 / 订阅 / 订阅站点', + 'multiple': True, + 'chips': True, + 'items': site_options, + 'hint': '只有选中的站点才会在订阅中使用。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 4, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': 'rss_follow_enable_sites', + 'label': '跟随启用的站点', + 'hint': '与站点的启用状态保持一致,保存时会立即生效,并在后台监听站点状态变化实时生效。' + } + }] + }] + }] + # 站点自动签到 + if self.__plugin_id_auto_signin in installed_plugin_ids: + form_content.append({ + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 9, 'xl': 9, 'lg': 9, 'md': 9, 'sm': 8, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'signin_sites', + 'label': '插件 / 站点自动签到 / 签到站点', + 'multiple': True, + 'chips': True, + 'items': site_options, + 'hint': '只有选中的站点才会在签到中使用。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 4, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': 'signin_follow_enable_sites', + 'label': '跟随启用的站点', + 'hint': '与站点的启用状态保持一致,保存时会立即生效,并在后台监听站点状态变化实时生效。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 9, 'xl': 9, 'lg': 9, 'md': 9, 'sm': 8, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'login_sites', + 'label': '插件 / 站点自动签到 / 登录站点', + 'multiple': True, + 'chips': True, + 'items': site_options, + 'hint': '只有选中的站点才会在登录中使用。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 4, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': 'login_follow_enable_sites', + 'label': '跟随启用的站点', + 'hint': '与站点的启用状态保持一致,保存时会立即生效,并在后台监听站点状态变化实时生效。' + } + }] + }] + }) + # 站点数据统计 + if self.__plugin_id_site_statistic in installed_plugin_ids: + form_content.append({ + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 9, 'xl': 9, 'lg': 9, 'md': 9, 'sm': 8, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'statistic_sites', + 'label': '插件 / 站点数据统计 / 统计站点', + 'multiple': True, + 'chips': True, + 'items': site_options, + 'hint': '缺省时默认全部站点。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 4, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': 'statistic_follow_enable_sites', + 'label': '跟随启用的站点', + 'hint': '与站点的启用状态保持一致,保存时会立即生效,并在后台监听站点状态变化实时生效。' + } + }] + }] + }) + # IYUU自动辅种 + if self.__plugin_id_iyuu_auto_seed in installed_plugin_ids: + form_content.append({ + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 9, 'xl': 9, 'lg': 9, 'md': 9, 'sm': 8, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'iyuu_seed_sites', + 'label': '插件 / IYUU自动辅种 / 辅种站点', + 'multiple': True, + 'chips': True, + 'items': site_options, + 'hint': '缺省时默认全部站点。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 4, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': 'iyuu_seed_follow_enable_sites', + 'label': '跟随启用的站点', + 'hint': '与站点的启用状态保持一致,保存时会立即生效,并在后台监听站点状态变化实时生效。' + } + }] + }] + }) + # 站点刷流 + if self.__plugin_id_brush_flow in installed_plugin_ids: + form_content.append({ + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 9, 'xl': 9, 'lg': 9, 'md': 9, 'sm': 8, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'brush_flow_sites', + 'label': '插件 / 站点刷流 / 刷流站点', + 'multiple': True, + 'chips': True, + 'items': site_options, + 'hint': '只有选中的站点才会在刷流中使用。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 3, 'xl': 3, 'lg': 3, 'md': 3, 'sm': 4, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': 'brush_flow_follow_enable_sites', + 'label': '跟随启用的站点', + 'hint': '与站点的启用状态保持一致,保存时会立即生效,并在后台监听站点状态变化实时生效。' + } + }] + }] + }) + # 提示信息 + form_content.append({ + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 12, 'xl': 12, 'lg': 12, 'md': 12, 'sm': 12, 'xs': 12 + }, + 'content': [{ + 'component': 'VAlert', + 'props': { + 'type': 'warning', + 'variant': 'tonal' + }, + 'text': '点击保存后会立即生效,各项站点开关配置即会更新!' + }] + }] + }) + # 表单 + form = [{ + 'component': 'VForm', + 'content': form_content + }] + + # 重载配置 + self.__get_config() + return form, config_suggest + + def get_page(self) -> List[dict]: + pass + + def stop_service(self): + """ + 退出插件 + """ + pass + + @classmethod + def __check_stack_exist_function(cls, package_name: str, function_name: str) -> bool: + """ + 判断当前调用栈是否存在指定的函数 + """ + if not package_name or not function_name: + return False + for stack in inspect.stack(): + if stack.function != 'set_plugin_config': + continue + package_path = package_name.replace('.', os.sep) + if stack.filename.endswith(f"{package_path}.py") or stack.filename.endswith(f"{package_path}{os.sep}__init__.py"): + return True + return False + + @classmethod + def __check_stack_is_save_config_request(cls) -> bool: + """ + 判断当前调用栈是否来源于“插件配置保存”接口 + """ + return cls.__check_stack_exist_function('app.api.endpoints.plugin', 'set_plugin_config') + + def __get_config_value(self, config_key: str, use_default: bool = True) -> Any: + """ + 获取插件配置值 + :param config_key: 配置键 + :param use_default: 是否使用缺省值 + :return: 配置值 + """ + if not config_key: + return None + config = self.__config if self.__config else {} + config_value = config.get(config_key) + if config_value is None and use_default: + config_default = self.__config_default if self.__config_default else {} + config_value = config_default.get(config_key) + return config_value + + def __check_follow_enable_sites(self, config_key: str, plugin_id: str = None, installed_plugin_ids: List[str] = None) -> bool: + """ + 判断某个跟随按钮是否打开 + :param plugin_id: 传插件ID时还要同时根据插件是否安装进行判断 + """ + if not self.__get_config_value(config_key=config_key): + return False + if not plugin_id: + return True + if not installed_plugin_ids: + installed_plugin_ids = self.__get_installed_plugin_ids() + return plugin_id in installed_plugin_ids + + def __check_search_follow_enable_sites(self) -> bool: + """ + 判断搜索站点的跟随按钮是否打开 + """ + return self.__check_follow_enable_sites(config_key='search_follow_enable_sites') + + def __check_rss_follow_enable_sites(self) -> bool: + """ + 判断订阅站点的跟随按钮是否打开 + """ + return self.__check_follow_enable_sites(config_key='rss_follow_enable_sites') + + def __check_signin_follow_enable_sites(self, installed_plugin_ids: List[str] = None) -> bool: + """ + 判断签到站点的跟随按钮是否打开 + """ + return self.__check_follow_enable_sites(config_key='signin_follow_enable_sites', plugin_id=self.__plugin_id_auto_signin, installed_plugin_ids=installed_plugin_ids) + + def __check_login_follow_enable_sites(self, installed_plugin_ids: List[str] = None) -> bool: + """ + 判断登录站点的跟随按钮是否打开 + """ + return self.__check_follow_enable_sites(config_key='login_follow_enable_sites', plugin_id=self.__plugin_id_auto_signin, installed_plugin_ids=installed_plugin_ids) + + def __check_statistic_follow_enable_sites(self, installed_plugin_ids: List[str] = None) -> bool: + """ + 判断统计站点的跟随按钮是否打开 + """ + return self.__check_follow_enable_sites(config_key='statistic_follow_enable_sites', plugin_id=self.__plugin_id_site_statistic, installed_plugin_ids=installed_plugin_ids) + + def __check_iyuu_seed_follow_enable_sites(self, installed_plugin_ids: List[str] = None) -> bool: + """ + 判断iyuu辅种站点的跟随按钮是否打开 + """ + return self.__check_follow_enable_sites(config_key='iyuu_seed_follow_enable_sites', plugin_id=self.__plugin_id_iyuu_auto_seed, installed_plugin_ids=installed_plugin_ids) + + def __check_brush_flow_follow_enable_sites(self, installed_plugin_ids: List[str] = None) -> bool: + """ + 判断刷流站点的跟随按钮是否打开 + """ + return self.__check_follow_enable_sites(config_key='brush_flow_follow_enable_sites', plugin_id=self.__plugin_id_brush_flow, installed_plugin_ids=installed_plugin_ids) + + def __check_any_follow_enable_sites(self) -> bool: + """ + 判断是否开启任意跟随按钮 + """ + # 已安装的插件IDs + installed_plugin_ids = self.__get_installed_plugin_ids() + return True if self.__check_search_follow_enable_sites() \ + or self.__check_rss_follow_enable_sites() \ + or self.__check_signin_follow_enable_sites(installed_plugin_ids=installed_plugin_ids) \ + or self.__check_login_follow_enable_sites(installed_plugin_ids=installed_plugin_ids) \ + or self.__check_statistic_follow_enable_sites(installed_plugin_ids=installed_plugin_ids) \ + or self.__check_iyuu_seed_follow_enable_sites(installed_plugin_ids=installed_plugin_ids) \ + or self.__check_brush_flow_follow_enable_sites(installed_plugin_ids=installed_plugin_ids) else False + + def __get_site_options(self) -> List[Dict[str, Any]]: + """ + 获取站点选项 + """ + sites = self.__site_oper.list_order_by_pri() + if not sites: + return [] + return [{ + 'title': site.name, + 'value': site.id + } for site in sites if site] + + def __get_installed_plugin_ids(self): + """ + 获取已安装的插件IDs + """ + installed_plugin_ids = self.__system_config_oper.get(SystemConfigKey.UserInstalledPlugins) + return installed_plugin_ids if installed_plugin_ids else [] + + def __get_config(self): + """ + 获取配置,包含聚合外部配置 + """ + config = self.get_config() + if not config: + config = {} + config.update({ + 'enable_sites': self.__get_enable_site_ids(), + 'search_sites': self.__get_search_site_ids(), + 'rss_sites': self.__get_rss_site_ids(), + }) + # 已安装的插件IDs + installed_plugin_ids = self.__get_installed_plugin_ids() + if self.__plugin_id_auto_signin in installed_plugin_ids: + config.update({ + 'signin_sites': self.__get_signin_site_ids(), + 'login_sites': self.__get_login_site_ids(), + }) + if self.__plugin_id_site_statistic in installed_plugin_ids: + config.update({ + 'statistic_sites': self.__get_statistic_site_ids(), + }) + if self.__plugin_id_iyuu_auto_seed in installed_plugin_ids: + config.update({ + 'iyuu_seed_sites': self.__get_iyuu_seed_site_ids(), + }) + if self.__plugin_id_brush_flow in installed_plugin_ids: + config.update({ + 'brush_flow_sites': self.__get_brush_flow_site_ids(), + }) + self.update_config(config=config) + return config + + def __pre_config_follow_enable_sites(self, config: dict) -> dict: + """ + 处理跟随站点 + """ + if not config: + config = {} + enable_sites = config.get('enable_sites') or [] + if config.get('search_follow_enable_sites'): + config.update({"search_sites": enable_sites.copy()}) + if config.get('rss_follow_enable_sites'): + config.update({"rss_sites": enable_sites.copy()}) + if config.get('signin_follow_enable_sites'): + config.update({"signin_sites": enable_sites.copy()}) + if config.get('login_follow_enable_sites'): + config.update({"login_sites": enable_sites.copy()}) + if config.get('statistic_follow_enable_sites'): + config.update({"statistic_sites": enable_sites.copy()}) + if config.get('iyuu_seed_follow_enable_sites'): + config.update({"iyuu_seed_sites": enable_sites.copy()}) + if config.get('brush_flow_follow_enable_sites'): + config.update({"brush_flow_sites": enable_sites.copy()}) + return config + + def __pre_config(self, config: dict) -> dict: + """ + 预处理配置 + """ + config = self.__pre_config_follow_enable_sites(config=config) + logger.debug(f"配置预处理完成: {config}") + return config + + def __set_config(self, config: dict): + """ + 保存配置,包含保存外部配置到各自表 + """ + # 预处理配置 + config = self.__pre_config(config=config) + # 更新各项配置 + self.update_config(config=config) + logger.info("插件配置更新完成") + self.__set_enable_site_ids(config.get('enable_sites')) + self.__set_search_site_ids(config.get('search_sites')) + self.__set_rss_site_ids(config.get('rss_sites')) + # 已安装的插件IDs + installed_plugin_ids = self.__get_installed_plugin_ids() + if self.__plugin_id_auto_signin in installed_plugin_ids: + self.__set_signin_site_ids(config.get('signin_sites')) + self.__set_login_site_ids(config.get('login_sites')) + if self.__plugin_id_site_statistic in installed_plugin_ids: + self.__set_statistic_site_ids(config.get('statistic_sites')) + if self.__plugin_id_iyuu_auto_seed in installed_plugin_ids: + self.__set_iyuu_seed_site_ids(config.get('iyuu_seed_sites')) + if self.__plugin_id_brush_flow in installed_plugin_ids: + self.__set_brush_flow_site_ids(config.get('brush_flow_sites')) + return config + + def __get_enable_site_ids(self) -> List[int]: + """ + 获取启用的站点IDs + """ + sites = self.__site_oper.list_order_by_pri() + if not sites: + return [] + return [site.id for site in sites if site and site.is_active] + + def __set_enable_site_ids(self, site_ids: List[int]): + """ + 设置启用的站点IDs + """ + sites = self.__site_oper.list_order_by_pri() + if not sites: + return + for site in sites: + if not site_ids or site.id not in site_ids: + if site.is_active: + self.__site_oper.update(site.id, {'is_active': False}) + else: + if not site.is_active: + self.__site_oper.update(site.id, {'is_active': True}) + logger.info("启用的站点配置完成") + + def __get_search_site_ids(self) -> List[int]: + """ + 获取搜索站点IDs + """ + sites = self.__system_config_oper.get(SystemConfigKey.IndexerSites) + return sites if sites else [] + + def __set_search_site_ids(self, site_ids: List[int]): + """ + 设置搜索站点IDs + """ + self.__system_config_oper.set(SystemConfigKey.IndexerSites, site_ids) + logger.info("搜索站点配置完成") + + def __get_rss_site_ids(self) -> List[int]: + """ + 获取订阅站点IDs + """ + sites = self.__system_config_oper.get(SystemConfigKey.RssSites) + return sites if sites else [] + + def __set_rss_site_ids(self, site_ids: List[int]): + """ + 设置订阅站点IDs + """ + self.__system_config_oper.set(SystemConfigKey.RssSites, site_ids) + logger.info("订阅站点配置完成") + + def __get_plugin_config_value(self, plugin_id: str, config_key: str) -> Any: + """ + 获取插件配置值 + """ + if not plugin_id or not config_key: + return None + config = self.get_config(plugin_id) + if not config: + return None + return config.get(config_key) + + def __set_plugin_config_value(self, plugin_id: str, config_key: str, config_value: Any) -> Any: + """ + 设置插件配置值 + """ + if not plugin_id or not config_key: + return + config = self.get_config(plugin_id) + if not config: + config = {} + config.update({config_key: config_value}) + self.update_config(plugin_id=plugin_id, config=config) + + def __get_signin_site_ids(self) -> List[int]: + """ + 获取签到站点IDs + """ + sites = self.__get_plugin_config_value(self.__plugin_id_auto_signin, 'sign_sites') + return sites if sites else [] + + def __set_signin_site_ids(self, site_ids: List[int]): + """ + 设置签到站点IDs + """ + self.__set_plugin_config_value(self.__plugin_id_auto_signin, 'sign_sites', site_ids) + logger.info("签到站点配置完成") + + def __get_login_site_ids(self) -> List[int]: + """ + 获取登录站点IDs + """ + sites = self.__get_plugin_config_value(self.__plugin_id_auto_signin, 'login_sites') + return sites if sites else [] + + def __set_login_site_ids(self, site_ids: List[int]): + """ + 设置登录站点IDs + """ + self.__set_plugin_config_value(self.__plugin_id_auto_signin, 'login_sites', site_ids) + logger.info("登录站点配置完成") + + def __get_statistic_site_ids(self) -> List[int]: + """ + 获取统计站点IDs + """ + sites = self.__get_plugin_config_value(self.__plugin_id_site_statistic, 'statistic_sites') + return sites if sites else [] + + def __set_statistic_site_ids(self, site_ids: List[int]): + """ + 设置统计站点IDs + """ + self.__set_plugin_config_value(self.__plugin_id_site_statistic, 'statistic_sites', site_ids) + logger.info("统计站点配置完成") + + def __get_iyuu_seed_site_ids(self) -> List[int]: + """ + 获取iyuu自动辅种站点IDs + """ + sites = self.__get_plugin_config_value(self.__plugin_id_iyuu_auto_seed, 'sites') + return sites if sites else [] + + def __set_iyuu_seed_site_ids(self, site_ids: List[int]): + """ + 设置iyuu自动辅种站点IDs + """ + self.__set_plugin_config_value(self.__plugin_id_iyuu_auto_seed, 'sites', site_ids) + logger.info("IYUU辅种站点配置完成") + + def __get_brush_flow_site_ids(self) -> List[int]: + """ + 获取刷流站点IDs + """ + sites = self.__get_plugin_config_value(self.__plugin_id_brush_flow, 'brushsites') + return sites if sites else [] + + def __set_brush_flow_site_ids(self, site_ids: List[int]): + """ + 设置刷流站点IDs + """ + self.__set_plugin_config_value(self.__plugin_id_brush_flow, 'brushsites', site_ids) + logger.info("刷流站点配置完成") + + def __update_search_site_ids_by_site(self, site_id: int, site_status: bool): + if site_id == None: + return + site_ids = self.__get_search_site_ids() or [] + if site_id not in site_ids and site_status: + site_ids.append(site_id) + self.__set_search_site_ids(site_ids=site_ids) + elif site_id in site_ids and not site_status: + site_ids.remove(site_id) + self.__set_search_site_ids(site_ids=site_ids) + + def __update_rss_site_ids_by_site(self, site_id: int, site_status: bool): + if site_id == None: + return + site_ids = self.__get_rss_site_ids() or [] + if site_id not in site_ids and site_status: + site_ids.append(site_id) + self.__set_rss_site_ids(site_ids=site_ids) + elif site_id in site_ids and not site_status: + site_ids.remove(site_id) + self.__set_rss_site_ids(site_ids=site_ids) + + def __update_signin_site_ids_by_site(self, site_id: int, site_status: bool): + if site_id == None: + return + site_ids = self.__get_signin_site_ids() or [] + if site_id not in site_ids and site_status: + site_ids.append(site_id) + self.__set_signin_site_ids(site_ids=site_ids) + elif site_id in site_ids and not site_status: + site_ids.remove(site_id) + self.__set_signin_site_ids(site_ids=site_ids) + + def __update_login_site_ids_by_site(self, site_id: int, site_status: bool): + if site_id == None: + return + site_ids = self.__get_login_site_ids() or [] + if site_id not in site_ids and site_status: + site_ids.append(site_id) + self.__set_login_site_ids(site_ids=site_ids) + elif site_id in site_ids and not site_status: + site_ids.remove(site_id) + self.__set_login_site_ids(site_ids=site_ids) + + def __update_statistic_site_ids_by_site(self, site_id: int, site_status: bool): + if site_id == None: + return + site_ids = self.__get_statistic_site_ids() or [] + if site_id not in site_ids and site_status: + site_ids.append(site_id) + self.__set_statistic_site_ids(site_ids=site_ids) + elif site_id in site_ids and not site_status: + site_ids.remove(site_id) + self.__set_statistic_site_ids(site_ids=site_ids) + + def __update_iyuu_seed_site_ids_by_site(self, site_id: int, site_status: bool): + if site_id == None: + return + site_ids = self.__get_iyuu_seed_site_ids() or [] + if site_id not in site_ids and site_status: + site_ids.append(site_id) + self.__set_iyuu_seed_site_ids(site_ids=site_ids) + elif site_id in site_ids and not site_status: + site_ids.remove(site_id) + self.__set_iyuu_seed_site_ids(site_ids=site_ids) + + def __update_brush_flow_site_ids_by_site(self, site_id: int, site_status: bool): + if site_id == None: + return + site_ids = self.__get_brush_flow_site_ids() or [] + if site_id not in site_ids and site_status: + site_ids.append(site_id) + self.__set_brush_flow_site_ids(site_ids=site_ids) + elif site_id in site_ids and not site_status: + site_ids.remove(site_id) + self.__set_brush_flow_site_ids(site_ids=site_ids) + + def __update_site_ids_for_site_event(self, site_id: int, site_status: bool): + """ + 针对站点事件更新各项配置 + """ + if site_id == None: + return + if self.__check_search_follow_enable_sites(): + self.__update_search_site_ids_by_site(site_id=site_id, site_status=site_status) + if self.__check_rss_follow_enable_sites(): + self.__update_rss_site_ids_by_site(site_id=site_id, site_status=site_status) + # 已安装的插件IDs + installed_plugin_ids = self.__get_installed_plugin_ids() + if self.__check_signin_follow_enable_sites(installed_plugin_ids=installed_plugin_ids): + self.__update_signin_site_ids_by_site(site_id=site_id, site_status=site_status) + if self.__check_login_follow_enable_sites(installed_plugin_ids=installed_plugin_ids): + self.__update_login_site_ids_by_site(site_id=site_id, site_status=site_status) + if self.__check_statistic_follow_enable_sites(installed_plugin_ids=installed_plugin_ids): + self.__update_statistic_site_ids_by_site(site_id=site_id, site_status=site_status) + if self.__check_iyuu_seed_follow_enable_sites(installed_plugin_ids=installed_plugin_ids): + self.__update_iyuu_seed_site_ids_by_site(site_id=site_id, site_status=site_status) + if self.__check_brush_flow_follow_enable_sites(installed_plugin_ids=installed_plugin_ids): + self.__update_brush_flow_site_ids_by_site(site_id=site_id, site_status=site_status) + + @eventmanager.register(EventType.SiteUpdated) + def listen_site_updated_event(self, event: Event = None): + """ + 监听站点更新事件 + """ + logger.info('监听到站点更新事件') + if not event or not event.event_data: + logger.warn('事件信息无效,忽略事件') + return + domain = event.event_data.get("domain") + if not domain: + logger.warn('事件信息无效,忽略事件') + return + if not self.__check_any_follow_enable_sites(): + logger.warn('未打开任一【跟随启用的站点】开关,忽略事件') + return + site = self.__site_oper.get_by_domain(domain=domain) + if not site: + logger.warn(f'目标站点不存在,忽略事件: domain = {domain}') + return + self.__update_site_ids_for_site_event(site_id=site.id, site_status=site.is_active) + logger.info('站点更新事件监听任务执行完成') + + @eventmanager.register(EventType.SiteDeleted) + def listen_site_deleted_event(self, event: Event = None): + """ + 监听站点删除事件 + """ + logger.info('监听到站点删除事件') + if not event or not event.event_data: + logger.warn('事件信息无效,忽略事件') + return + site_id = event.event_data.get("site_id") + if site_id == None: + logger.warn('事件信息无效,忽略事件') + return + if not self.__check_any_follow_enable_sites(): + logger.warn('未打开任一【跟随启用的站点】开关,忽略事件') + return + self.__update_site_ids_for_site_event(site_id=site_id, site_status=False) + logger.info('站点删除事件监听任务执行完成') From 7d83787cad09975a8e00caeab381fa77bbd26514 Mon Sep 17 00:00:00 2001 From: xuzhi Date: Fri, 26 Apr 2024 12:32:08 +0000 Subject: [PATCH 06/11] Update qbcommand to version 1.4 --- package.json | 7 +- plugins/qbcommand/__init__.py | 252 ++++++++++++++++++++++++++++++---- 2 files changed, 231 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index b818b08..3626306 100644 --- a/package.json +++ b/package.json @@ -450,10 +450,13 @@ "QbCommand": { "name": "QB远程操作", "description": "通过定时任务或交互命令远程操作QB暂停/开始/限速等。", - "version": "1.3", + "version": "1.4", "icon": "Qbittorrent_A.png", "author": "DzAvril", - "level": 1 + "level": 1, + "history": { + "v1.4": "可选某些站点不再做种(暂停做种后不会被恢复)" + } }, "TrCommand": { "name": "TR远程操作", diff --git a/plugins/qbcommand/__init__.py b/plugins/qbcommand/__init__.py index d6d0b09..a79cda1 100644 --- a/plugins/qbcommand/__init__.py +++ b/plugins/qbcommand/__init__.py @@ -1,5 +1,7 @@ from typing import List, Tuple, Dict, Any from enum import Enum +from urllib.parse import urlparse +import urllib from app.log import logger from app.modules.qbittorrent import Qbittorrent from app.plugins import _PluginBase @@ -7,6 +9,13 @@ from app.schemas import NotificationType from app.schemas.types import EventType from apscheduler.triggers.cron import CronTrigger from app.core.event import eventmanager, Event +from apscheduler.schedulers.background import BackgroundScheduler +from app.core.config import settings +from app.helper.sites import SitesHelper +from app.db.site_oper import SiteOper +from app.utils.string import StringUtils +from datetime import datetime, timedelta +import pytz import time @@ -18,7 +27,7 @@ class QbCommand(_PluginBase): # 插件图标 plugin_icon = "Qbittorrent_A.png" # 插件版本 - plugin_version = "1.3" + plugin_version = "1.4" # 插件作者 plugin_author = "DzAvril" # 作者主页 @@ -31,6 +40,8 @@ class QbCommand(_PluginBase): auth_level = 1 # 私有属性 + _sites = None + _siteoper = None _qb = None _enabled: bool = False _notify: bool = False @@ -45,8 +56,14 @@ class QbCommand(_PluginBase): _enable_upload_limit = False _download_limit = 0 _enable_download_limit = False + _op_site_ids = [] + _op_sites = [] + _multi_level_root_domain = ["edu.cn", "com.cn", "net.cn", "org.cn"] + _scheduler = None def init_plugin(self, config: dict = None): + self._sites = SitesHelper() + self._siteoper = SiteOper() # 停止现有任务 self.stop_service() # 读取配置 @@ -65,14 +82,33 @@ class QbCommand(_PluginBase): self._enable_download_limit = config.get("enable_download_limit") self._enable_upload_limit = config.get("enable_upload_limit") self._qb = Qbittorrent() + self._op_site_ids = config.get("op_site_ids") or [] + # 查询所有站点 + all_sites = [site for site in self._sites.get_indexers() if not site.get("public")] + self.__custom_sites() + # 过滤掉没有选中的站点 + self._op_sites = [site for site in all_sites if site.get("id") in self._op_site_ids] if self._only_pause_once or self._only_resume_once: if self._only_pause_once and self._only_resume_once: logger.warning("只能选择一个: 立即暂停或立即开始所有任务") elif self._only_pause_once: - self.pause_torrent() + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + logger.info(f"立即运行一次暂停所有任务") + self._scheduler.add_job( + self.pause_torrent, + "date", + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + + timedelta(seconds=3), + ) elif self._only_resume_once: - self.resume_torrent() + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + logger.info(f"立即运行一次开始所有任务") + self._scheduler.add_job( + self.resume_torrent, + "date", + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + + timedelta(seconds=3), + ) self._only_resume_once = False self._only_pause_once = False @@ -84,16 +120,56 @@ class QbCommand(_PluginBase): "notify": self._notify, "pause_cron": self._pause_cron, "resume_cron": self._resume_cron, + "op_site_ids": self._op_site_ids, } ) - if self._only_pause_upload or self._only_pause_download or self._only_pause_checking: + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() + + if ( + self._only_pause_upload + or self._only_pause_download + or self._only_pause_checking + ): if self._only_pause_upload: - self.pause_torrent(self.TorrentType.UPLOADING) + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + logger.info(f"立即运行一次暂停所有上传任务") + self._scheduler.add_job( + self.pause_torrent, + "date", + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + + timedelta(seconds=3), + kwargs={ + 'type': self.TorrentType.UPLOADING + } + ) if self._only_pause_download: - self.pause_torrent(self.TorrentType.DOWNLOADING) + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + logger.info(f"立即运行一次暂停所有下载任务") + self._scheduler.add_job( + self.pause_torrent, + "date", + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + + timedelta(seconds=3), + kwargs={ + 'type': self.TorrentType.DOWNLOADING + } + ) if self._only_pause_checking: - self.pause_torrent(self.TorrentType.CHECKING) + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + logger.info(f"立即运行一次暂停所有检查任务") + self._scheduler.add_job( + self.pause_torrent, + "date", + run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + + timedelta(seconds=3), + kwargs={ + 'type': self.TorrentType.CHECKING + } + ) self._only_pause_upload = False self._only_pause_download = False @@ -107,9 +183,15 @@ class QbCommand(_PluginBase): "notify": self._notify, "pause_cron": self._pause_cron, "resume_cron": self._resume_cron, + "op_site_ids": self._op_site_ids, } ) + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() + self.set_limit(self._upload_limit, self._download_limit) def get_state(self) -> bool: @@ -186,6 +268,13 @@ class QbCommand(_PluginBase): }, ] + def __custom_sites(self) -> List[Any]: + custom_sites = [] + custom_sites_config = self.get_config("CustomSites") + if custom_sites_config and custom_sites_config.get("enabled"): + custom_sites = custom_sites_config.get("sites") + return custom_sites + def get_api(self) -> List[Dict[str, Any]]: pass @@ -426,6 +515,7 @@ class QbCommand(_PluginBase): return all_torrents = self.get_all_torrents() + all_torrents = self.filter_torrents(all_torrents) hash_downloading, hash_uploading, hash_paused, hash_checking, hash_error = ( self.get_torrents_status(all_torrents) ) @@ -488,6 +578,41 @@ class QbCommand(_PluginBase): f"错误数量: {len(hash_error)}\n", ) + def filter_torrents(self, all_torrents): + """ + 过滤掉不参与保种的种子 + """ + if len(self._op_sites) == 0: + return all_torrents + + urls = [site.get("url") for site in self._op_sites] + op_sites_main_domains = [] + for url in urls: + domain = StringUtils.get_url_netloc(url) + main_domain = self.get_main_domain(domain[1]) + op_sites_main_domains.append(main_domain) + + torrents = [] + for torrent in all_torrents: + if torrent.get("state") == "pausedUP": + tracker_url = self.get_torrent_tracker(torrent) + if not tracker_url: + logger.info(f"获取种子 {torrent.name} Tracker失败,不过滤该种子") + torrents.append(torrent) + _, tracker_domain = StringUtils.get_url_netloc(tracker_url) + if not tracker_domain: + logger.info(f"获取种子 {torrent.name} Tracker失败,不过滤该种子") + torrents.append(torrent) + tracker_main_domain = self.get_main_domain(domain=tracker_domain) + if tracker_main_domain in op_sites_main_domains: + logger.info( + f"种子 {torrent.name} 属于站点{tracker_main_domain},不执行操作" + ) + continue + + torrents.append(torrent) + return torrents + @eventmanager.register(EventType.PluginAction) def handle_qb_status(self, event: Event): if not self._enabled: @@ -507,14 +632,13 @@ class QbCommand(_PluginBase): self.get_torrents_status(all_torrents) ) logger.info( - f"QB开始任务启动 \n" + f"QB任务状态 \n" f"种子总数: {len(all_torrents)} \n" f"做种数量: {len(hash_uploading)}\n" f"下载数量: {len(hash_downloading)}\n" f"检查数量: {len(hash_checking)}\n" f"暂停数量: {len(hash_paused)}\n" f"错误数量: {len(hash_error)}\n" - f"开始操作中请稍等...\n", ) if self._notify: self.post_message( @@ -648,7 +772,67 @@ class QbCommand(_PluginBase): text=f"设置QB限速失败", ) + def get_torrent_tracker(self, torrent): + """ + qb解析 tracker + :return: tracker url + """ + if not torrent: + return None + tracker = torrent.get("tracker") + if tracker and len(tracker) > 0: + return tracker + magnet_uri = torrent.get("magnet_uri") + if not magnet_uri or len(magnet_uri) <= 0: + return None + magnet_uri_obj = urlparse(magnet_uri) + query = urllib.parse.parse_qs(magnet_uri_obj.query) + tr = query["tr"] + if not tr or len(tr) <= 0: + return None + return tr[0] + + def get_main_domain(self, domain): + """ + 获取域名的主域名 + :param domain: 原域名 + :return: 主域名 + """ + if not domain: + return None + domain_arr = domain.split(".") + 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]}" + + def match_multi_level_root_domain(self, domain): + """ + 匹配多级根域名 + :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 get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + customSites = self.__custom_sites() + + site_options = [ + {"title": site.name, "value": site.id} + for site in self._siteoper.list_order_by_pri() + ] + [ + {"title": site.get("name"), "value": site.get("id")} for site in customSites + ] return [ { "component": "VForm", @@ -849,6 +1033,27 @@ class QbCommand(_PluginBase): }, ], }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VSelect", + "props": { + "chips": True, + "multiple": True, + "model": "op_site_ids", + "label": "停止保种站点(暂停保种后不会被恢复)", + "items": site_options, + }, + } + ], + } + ], + }, { "component": "VRow", "content": [ @@ -884,22 +1089,6 @@ class QbCommand(_PluginBase): } ], }, - { - "component": "VCol", - "props": { - "cols": 12, - }, - "content": [ - { - "component": "VAlert", - "props": { - "type": "info", - "variant": "tonal", - "text": "PT精神重在分享,请勿恶意限速,因此导致账号被封禁作者概不负责", - }, - } - ], - }, ], }, ], @@ -916,10 +1105,21 @@ class QbCommand(_PluginBase): "download_limit": 0, "enable_upload_limit": False, "enable_download_limit": False, + "op_site_ids": [], } def get_page(self) -> List[dict]: pass def stop_service(self): - pass + """ + 退出插件 + """ + try: + if self._scheduler: + self._scheduler.remove_all_jobs() + if self._scheduler.running: + self._scheduler.shutdown() + self._scheduler = None + except Exception as e: + logger.error("退出插件失败:%s" % str(e)) From ead01e5adef3972bd7bc59c87cdad105e7c54502 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 28 Apr 2024 12:15:57 +0800 Subject: [PATCH 07/11] fix #244 --- package.json | 4 ++-- plugins/iyuuautoseed/__init__.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index cacbb92..c79b1ef 100644 --- a/package.json +++ b/package.json @@ -208,12 +208,12 @@ "IYUUAutoSeed": { "name": "IYUU自动辅种", "description": "基于IYUU官方Api实现自动辅种。", - "version": "1.4", + "version": "1.5", "icon": "IYUU.png", "author": "jxxghp", "level": 2, "history": { - "v1.4": "支持馒头新架构辅种" + "v1.5": "支持馒头新架构辅种" } }, "CrossSeed": { diff --git a/plugins/iyuuautoseed/__init__.py b/plugins/iyuuautoseed/__init__.py index 5eb8788..d7d2afb 100644 --- a/plugins/iyuuautoseed/__init__.py +++ b/plugins/iyuuautoseed/__init__.py @@ -34,7 +34,7 @@ class IYUUAutoSeed(_PluginBase): # 插件图标 plugin_icon = "IYUU.png" # 插件版本 - plugin_version = "1.4" + plugin_version = "1.5" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -919,6 +919,7 @@ class IYUUAutoSeed(_PluginBase): if not res: logger.warn(f"m-team 获取种子下载链接失败:{tid}") return None + return res.json().get("data") def __is_special_site(url: str): """ From b1ad72be4ea827586735ba08ab08d602111d343d Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 28 Apr 2024 13:32:33 +0800 Subject: [PATCH 08/11] add labels --- package.json | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/package.json b/package.json index c79b1ef..18cd461 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "AutoSignIn": { "name": "站点自动签到", "description": "自动模拟登录、签到站点。", + "labels": "站点", "version": "2.1", "icon": "signin.png", "author": "thsrite", @@ -15,6 +16,7 @@ "CustomSites": { "name": "自定义站点", "description": "增加自定义站点为签到和统计使用。", + "labels": "站点", "version": "1.0", "icon": "world.png", "author": "lightolly", @@ -23,6 +25,7 @@ "SiteStatistic": { "name": "站点数据统计", "description": "自动统计和展示站点数据。", + "labels": "站点", "version": "2.9", "icon": "statistic.png", "author": "lightolly", @@ -37,6 +40,7 @@ "SiteRefresh": { "name": "站点自动更新", "description": "使用浏览器模拟登录站点获取Cookie和UA。", + "labels": "站点", "version": "1.2", "icon": "Chrome_A.png", "author": "thsrite", @@ -45,6 +49,7 @@ "DoubanSync": { "name": "豆瓣想看", "description": "同步豆瓣想看数据,自动添加订阅。", + "labels": "订阅", "version": "1.7", "icon": "douban.png", "author": "jxxghp", @@ -58,6 +63,7 @@ "DirMonitor": { "name": "目录监控", "description": "监控目录文件发生变化时实时整理到媒体库。", + "labels": "文件整理", "version": "2.0", "icon": "directory.png", "author": "jxxghp", @@ -70,6 +76,7 @@ "ChineseSubFinder": { "name": "ChineseSubFinder", "description": "整理入库时通知ChineseSubFinder下载字幕。", + "labels": "字幕", "version": "1.1", "icon": "chinesesubfinder.png", "author": "jxxghp", @@ -78,6 +85,7 @@ "DoubanRank": { "name": "豆瓣榜单订阅", "description": "监控豆瓣热门榜单,自动添加订阅。", + "labels": "订阅", "version": "1.9", "icon": "movie.jpg", "author": "jxxghp", @@ -90,6 +98,7 @@ "LibraryScraper": { "name": "媒体库刮削", "description": "定时对媒体库进行刮削,补齐缺失元数据和图片。", + "labels": "文件整理", "version": "1.4", "icon": "scraper.png", "author": "jxxghp", @@ -98,6 +107,7 @@ "TorrentRemover": { "name": "自动删种", "description": "自动删除下载器中的下载任务。", + "labels": "做种", "version": "1.2.2", "icon": "delete.jpg", "author": "jxxghp", @@ -106,6 +116,7 @@ "MediaSyncDel": { "name": "媒体文件同步删除", "description": "同步删除历史记录、源文件和下载任务。", + "labels": "文件整理", "version": "1.4", "icon": "mediasyncdel.png", "author": "thsrite", @@ -114,6 +125,7 @@ "CustomHosts": { "name": "自定义Hosts", "description": "修改系统hosts文件,加速网络访问。", + "labels": "网络", "version": "1.0", "icon": "hosts.png", "author": "thsrite", @@ -122,6 +134,7 @@ "SpeedLimiter": { "name": "播放限速", "description": "外网播放媒体库视频时,自动对下载器进行限速。", + "labels": "网络", "version": "1.1", "icon": "Librespeed_A.png", "author": "Shurelol", @@ -130,6 +143,7 @@ "CloudflareSpeedTest": { "name": "Cloudflare IP优选", "description": "🌩 测试 Cloudflare CDN 延迟和速度,自动优选IP。", + "labels": "网络,站点", "version": "1.2", "icon": "cloudflare.jpg", "author": "thsrite", @@ -141,6 +155,7 @@ "BestFilmVersion": { "name": "收藏洗版", "description": "Jellyfin/Emby/Plex点击收藏电影后,自动订阅洗版。", + "labels": "订阅", "version": "2.1", "icon": "like.jpg", "author": "wlj", @@ -149,6 +164,7 @@ "MediaServerMsg": { "name": "媒体库服务器通知", "description": "发送Emby/Jellyfin/Plex服务器的播放、入库等通知消息。", + "labels": "消息通知,媒体库", "version": "1.1", "icon": "mediaplay.png", "author": "jxxghp", @@ -157,6 +173,7 @@ "MediaServerRefresh": { "name": "媒体库服务器刷新", "description": "入库后自动刷新Emby/Jellyfin/Plex服务器海报墙。", + "labels": "媒体库", "version": "1.2", "icon": "refresh2.png", "author": "jxxghp", @@ -173,6 +190,7 @@ "ChatGPT": { "name": "ChatGPT", "description": "消息交互支持与ChatGPT对话。", + "labels": "消息通知,识别", "version": "1.3", "icon": "Chatgpt_A.png", "author": "jxxghp", @@ -189,6 +207,7 @@ "MessageForward": { "name": "消息转发", "description": "根据正则转发通知到其他WeChat应用。", + "labels": "消息通知", "version": "1.1", "icon": "forward.png", "author": "thsrite", @@ -197,6 +216,7 @@ "AutoBackup": { "name": "自动备份", "description": "自动备份数据和配置文件。", + "labels": "系统设置", "version": "1.2", "icon": "Time_machine_B.png", "author": "thsrite", @@ -208,6 +228,7 @@ "IYUUAutoSeed": { "name": "IYUU自动辅种", "description": "基于IYUU官方Api实现自动辅种。", + "labels": "做种", "version": "1.5", "icon": "IYUU.png", "author": "jxxghp", @@ -219,6 +240,7 @@ "CrossSeed": { "name": "青蛙辅种助手", "description": "参考ReseedPuppy和IYUU辅种插件实现自动辅种,支持站点:青蛙、AGSVPT、麒麟、UBits、聆音、憨憨等。", + "labels": "做种", "version": "2.3", "icon": "qingwa.png", "author": "233@qingwa", @@ -231,6 +253,7 @@ "VCBAnimeMonitor": { "name": "整理VCB动漫压制组作品", "description": "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理", + "labels": "文件整理,识别", "version": "1.7", "icon": "vcbmonitor.png", "author": "pixel@qingwa", @@ -239,6 +262,7 @@ "TorrentTransfer": { "name": "自动转移做种", "description": "定期转移下载器中的做种任务到另一个下载器。", + "labels": "做种", "version": "1.3", "icon": "seed.png", "author": "jxxghp", @@ -247,6 +271,7 @@ "RssSubscribe": { "name": "自定义订阅", "description": "定时刷新RSS报文,识别内容后添加订阅或直接下载。", + "labels": "订阅", "version": "1.3", "icon": "rss.png", "author": "jxxghp", @@ -258,6 +283,7 @@ "SyncDownloadFiles": { "name": "下载器文件同步", "description": "同步下载器的文件信息到数据库,删除文件时联动删除下载任务。", + "labels": "下载管理", "version": "1.1", "icon": "Youtube-dl_A.png", "author": "thsrite", @@ -266,6 +292,7 @@ "BrushFlow": { "name": "站点刷流", "description": "自动托管刷流,将会提高对应站点的访问频率。", + "labels": "刷流", "version": "2.9", "icon": "brush.jpg", "author": "jxxghp,InfinityPacer", @@ -281,6 +308,7 @@ "DownloadingMsg": { "name": "下载进度推送", "description": "定时推送正在下载进度。", + "labels": "消息通知,下载管理", "version": "1.1", "icon": "downloadmsg.png", "author": "thsrite", @@ -289,6 +317,7 @@ "AutoClean": { "name": "定时清理媒体库", "description": "定时清理用户下载的种子、源文件、媒体库文件。", + "labels": "媒体库", "version": "1.1", "icon": "clean.png", "author": "thsrite", @@ -308,6 +337,7 @@ "PersonMeta": { "name": "演职人员刮削", "description": "刮削演职人员图片以及中文名称。", + "labels": "媒体库", "version": "1.2", "icon": "actor.png", "author": "jxxghp", @@ -335,6 +365,7 @@ "BarkMsg": { "name": "Bark消息推送", "description": "支持使用Bark发送消息通知。", + "labels": "消息通知", "version": "1.1", "icon": "Bark_A.png", "author": "jxxghp", @@ -343,6 +374,7 @@ "IyuuMsg": { "name": "IYUU消息推送", "description": "支持使用IYUU发送消息通知。", + "labels": "消息通知", "version": "1.2", "icon": "Iyuu_A.png", "author": "jxxghp", @@ -351,6 +383,7 @@ "PushDeerMsg": { "name": "PushDeer消息推送", "description": "支持使用PushDeer发送消息通知。", + "labels": "消息通知", "version": "1.1", "icon": "pushdeer.png", "author": "jxxghp", @@ -359,6 +392,7 @@ "ConfigCenter": { "name": "配置中心", "description": "快速调整部分系统设定。", + "labels": "系统设置", "version": "2.4", "icon": "setting.png", "author": "jxxghp", @@ -367,6 +401,7 @@ "WorkWechatMsg": { "name": "企微机器人消息推送", "description": "支持使用企业微信群聊机器人发送消息通知。", + "labels": "消息通知", "version": "1.0", "icon": "Wecom_A.png", "author": "叮叮当", @@ -375,6 +410,7 @@ "EpisodeGroupMeta": { "name": "TMDB剧集组刮削", "description": "从TMDB剧集组刮削季集的实际顺序。", + "labels": "刮削", "version": "1.1", "icon": "Element_A.png", "author": "叮叮当", @@ -383,6 +419,7 @@ "CustomIndexer": { "name": "自定义索引站点", "description": "修改或扩展内建索引器支持的站点。", + "labels": "站点", "version": "1.0", "icon": "spider.png", "author": "jxxghp", @@ -391,6 +428,7 @@ "FFmpegThumb": { "name": "FFmpeg缩略图", "description": "TheMovieDb没有背景图片时使用FFmpeg截取视频文件缩略图", + "labels": "刮削", "version": "1.2", "icon": "ffmpeg.png", "author": "jxxghp", @@ -399,6 +437,7 @@ "PushPlusMsg": { "name": "PushPlus消息推送", "description": "支持使用PushPlus发送消息通知。", + "labels": "消息通知", "version": "1.0", "icon": "Pushplus_A.png", "author": "cheng", @@ -407,6 +446,7 @@ "DownloadSiteTag": { "name": "下载任务分类与标签", "description": "自动给下载任务分类与打站点标签、剧集名称标签", + "labels": "下载管理", "version": "2.1", "icon": "Youtube-dl_B.png", "author": "叮叮当", @@ -418,6 +458,7 @@ "RemoveLink": { "name": "清理硬链接", "description": "监控目录内文件被删除时,同步删除监控目录内所有和它硬链接的文件", + "labels": "文件整理", "version": "1.9", "icon": "Ombi_A.png", "author": "DzAvril", @@ -432,6 +473,7 @@ "LinkMonitor": { "name": "实时硬链接", "description": "监控目录文件变化,实时硬链接。", + "labels": "文件整理", "version": "1.6", "icon": "Linkace_C.png", "author": "jxxghp", @@ -443,6 +485,7 @@ "CategoryEditor": { "name": "二级分类策略", "description": "编辑下载目录和媒体库目录的二级分类规则。", + "labels": "文件整理", "version": "1.1", "icon": "Bookstack_A.png", "author": "jxxghp", @@ -451,6 +494,7 @@ "RemoteIdentifiers": { "name": "共享识别词", "description": "从Github、Etherpad等远程文件中获取共享识别词并应用。", + "labels": "识别", "version": "2.2", "icon": "words.png", "author": "honue", @@ -459,6 +503,7 @@ "NeoDBSync": { "name": "NeoDB 想看", "description": "同步 NeoDB 想看条目,自动添加订阅。", + "labels": "订阅", "version": "1.1", "icon": "NeoDB.jpeg", "author": "hcplantern", @@ -470,6 +515,7 @@ "PlayletCategory": { "name": "短剧自动分类", "description": "网络短剧自动整理到独立的分类目录。", + "labels": "文件整理", "version": "1.4", "icon": "Amule_A.png", "author": "jxxghp", @@ -486,6 +532,7 @@ "QbCommand": { "name": "QB远程操作", "description": "通过定时任务或交互命令远程操作QB暂停/开始/限速等。", + "labels": "下载管理", "version": "1.4", "icon": "Qbittorrent_A.png", "author": "DzAvril", @@ -497,6 +544,7 @@ "TrCommand": { "name": "TR远程操作", "description": "通过定时任务或交互命令远程操作TR暂停/开始/限速等。", + "labels": "下载管理", "version": "1.1", "icon": "Transmission_A.png", "author": "Hoey", @@ -513,6 +561,7 @@ "TrackerEditor": { "name": "Tracker替换", "description": "批量替换种子tracker,支持周期性巡检(如为TR,仅支持4.0以上版本)。", + "labels": "做种", "version": "1.5", "icon": "trackereditor_A.png", "author": "honue", @@ -521,6 +570,7 @@ "ContractCheck": { "name": "契约检查", "description": "定时检查保种契约达成情况。", + "labels": "做种", "version": "1.1", "icon": "contract.png", "author": "DzAvril", @@ -529,6 +579,7 @@ "DownloaderHelper": { "name": "下载器助手", "description": "自动做种、站点标签、自动删种。", + "labels": "下载管理", "version": "1.6", "icon": "DownloaderHelper.png", "author": "hotlcc", @@ -540,6 +591,7 @@ "FeiShuMsg": { "name": "飞书机器人消息通知", "description": "支持使用飞书群聊机器人发送消息通知。", + "labels": "消息通知", "version": "1.0", "icon": "FeiShu_A.png", "author": "InfinityPacer", @@ -559,6 +611,7 @@ "NtfyMsg": { "name": "ntfy消息推送", "description": "支持使用ntfy发送消息通知。", + "labels": "消息通知", "version": "1.0", "icon": "Ntfy_A.png", "author": "lethargicScribe", @@ -575,6 +628,7 @@ "MergeSiteSwitch": { "name": "聚合站点开关", "description": "统一管理所有与站点相关的开关。", + "labels": "系统设置", "version": "1.0", "icon": "world.png", "author": "hotlcc", From 010d62e0378192842ca7b82e44fcc847c4c78553 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 28 Apr 2024 13:35:16 +0800 Subject: [PATCH 09/11] fix labels --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 18cd461..9c2f932 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "LibraryScraper": { "name": "媒体库刮削", "description": "定时对媒体库进行刮削,补齐缺失元数据和图片。", - "labels": "文件整理", + "labels": "刮削", "version": "1.4", "icon": "scraper.png", "author": "jxxghp", From bfc8726403155a5c0cf22b49e1209258ef8766db Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 28 Apr 2024 13:50:23 +0800 Subject: [PATCH 10/11] fix labels --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c2f932..e378ba4 100644 --- a/package.json +++ b/package.json @@ -337,7 +337,7 @@ "PersonMeta": { "name": "演职人员刮削", "description": "刮削演职人员图片以及中文名称。", - "labels": "媒体库", + "labels": "媒体库,刮削", "version": "1.2", "icon": "actor.png", "author": "jxxghp", From fdeaf4bcfce551f8d250b4020582e787223d8e31 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 28 Apr 2024 13:59:16 +0800 Subject: [PATCH 11/11] fix labels --- package.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e378ba4..d83995e 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "IYUUAutoSeed": { "name": "IYUU自动辅种", "description": "基于IYUU官方Api实现自动辅种。", - "labels": "做种", + "labels": "做种,IYUU", "version": "1.5", "icon": "IYUU.png", "author": "jxxghp", @@ -326,6 +326,7 @@ "InvitesSignin": { "name": "药丸签到", "description": "药丸论坛签到。", + "labels": "站点", "version": "1.4", "icon": "invites.png", "author": "thsrite", @@ -346,6 +347,7 @@ "MoviePilotUpdateNotify": { "name": "MoviePilot更新推送", "description": "MoviePilot推送release更新通知、自动重启。", + "labels": "消息通知,自动更新", "version": "1.3", "icon": "Moviepilot_A.png", "author": "thsrite", @@ -357,6 +359,7 @@ "CloudDiskDel": { "name": "云盘文件删除", "description": "媒体库删除strm文件后同步删除云盘资源。", + "labels": "媒体库", "version": "1.3", "icon": "clouddisk.png", "author": "thsrite", @@ -374,7 +377,7 @@ "IyuuMsg": { "name": "IYUU消息推送", "description": "支持使用IYUU发送消息通知。", - "labels": "消息通知", + "labels": "消息通知,IYUU", "version": "1.2", "icon": "Iyuu_A.png", "author": "jxxghp", @@ -524,6 +527,7 @@ "DiagParamAdjust": { "name": "诊断参数调整", "description": "Emby专用插件|暂时性解决emby字幕偏移问题,需要emby安装Diagnostics插件。", + "labels": "Emby", "version": "1.3", "icon": "Gatus_A.png", "author": "jeblove", @@ -532,7 +536,7 @@ "QbCommand": { "name": "QB远程操作", "description": "通过定时任务或交互命令远程操作QB暂停/开始/限速等。", - "labels": "下载管理", + "labels": "下载管理,Qbittorrent", "version": "1.4", "icon": "Qbittorrent_A.png", "author": "DzAvril", @@ -544,7 +548,7 @@ "TrCommand": { "name": "TR远程操作", "description": "通过定时任务或交互命令远程操作TR暂停/开始/限速等。", - "labels": "下载管理", + "labels": "下载管理,Transmission", "version": "1.1", "icon": "Transmission_A.png", "author": "Hoey", @@ -553,6 +557,7 @@ "IpDetect": { "name": "本地IP检测", "description": "如果QB、TR等服务在本地部署,当本地IP改变时自动修改其Server IP。", + "labels": "系统设置", "version": "1.1", "icon": "ipAddress.png", "author": "DzAvril", @@ -600,6 +605,7 @@ "IyuuAuth": { "name": "IYUU站点绑定", "description": "为IYUU账号绑定认证站点,以便用于用户认证和辅种。", + "labels": "IYUU", "version": "1.1", "icon": "Iyuu_A.png", "author": "jxxghp", @@ -620,6 +626,7 @@ "PluginAutoUpgrade": { "name": "插件自动升级", "description": "定时检测、升级插件。", + "labels": "自动更新", "version": "1.4", "icon": "PluginAutoUpgrade.png", "author": "hotlcc",