From 95df9dc45fc2264bc0490fcff999eee56b4530b9 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 23 May 2024 08:22:35 +0800 Subject: [PATCH 01/27] =?UTF-8?q?=E7=9B=AE=E5=BD=95=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=AE=E5=89=8A=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/dirmonitor/__init__.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/plugins/dirmonitor/__init__.py b/plugins/dirmonitor/__init__.py index abf941a..934db48 100644 --- a/plugins/dirmonitor/__init__.py +++ b/plugins/dirmonitor/__init__.py @@ -59,7 +59,7 @@ class DirMonitor(_PluginBase): # 插件图标 plugin_icon = "directory.png" # 插件版本 - plugin_version = "2.0" + plugin_version = "2.1" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -83,6 +83,7 @@ class DirMonitor(_PluginBase): _onlyonce = False _cron = None _size = 0 + _scrape = True # 模式 compatibility/fast _mode = "fast" # 转移方式 @@ -119,6 +120,7 @@ class DirMonitor(_PluginBase): self._interval = config.get("interval") or 10 self._cron = config.get("cron") self._size = config.get("size") or 0 + self._scrape = config.get("scrape") or False # 停止现有任务 self.stop_service() @@ -235,7 +237,8 @@ class DirMonitor(_PluginBase): "exclude_keywords": self._exclude_keywords, "interval": self._interval, "cron": self._cron, - "size": self._size + "size": self._size, + "scrape": self._scrape }) @eventmanager.register(EventType.PluginAction) @@ -419,7 +422,8 @@ class DirMonitor(_PluginBase): transfer_type=transfer_type, target=target, meta=file_meta, - episodes_info=episodes_info) + episodes_info=episodes_info, + scrape=self._scrape) if not transferinfo: logger.error("文件转移模块运行失败") @@ -457,7 +461,7 @@ class DirMonitor(_PluginBase): ) # 刮削单个文件 - if settings.SCRAP_METADATA: + if transferinfo.need_scrape: self.chain.scrape_metadata(path=transferinfo.target_path, mediainfo=mediainfo, transfer_type=transfer_type) @@ -824,6 +828,22 @@ class DirMonitor(_PluginBase): } } ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'scrape', + 'label': '刮削元数据', + } + } + ] } ] }, @@ -950,7 +970,8 @@ class DirMonitor(_PluginBase): "exclude_keywords": "", "interval": 10, "cron": "", - "size": 0 + "size": 0, + "scrape": True } def get_page(self) -> List[dict]: From 9de855601bcea639da165e10af62f4b095a92787 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 23 May 2024 08:33:11 +0800 Subject: [PATCH 02/27] =?UTF-8?q?=E9=80=82=E9=85=8D=E5=88=AE=E5=89=8A?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8F=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/dirmonitor/__init__.py | 9 ++++----- plugins/vcbanimemonitor/__init__.py | 23 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/plugins/dirmonitor/__init__.py b/plugins/dirmonitor/__init__.py index 934db48..14fce73 100644 --- a/plugins/dirmonitor/__init__.py +++ b/plugins/dirmonitor/__init__.py @@ -87,7 +87,7 @@ class DirMonitor(_PluginBase): # 模式 compatibility/fast _mode = "fast" # 转移方式 - _transfer_type = settings.TRANSFER_TYPE + _transfer_type = "link" _monitor_dirs = "" _exclude_keywords = "" _interval: int = 10 @@ -422,8 +422,7 @@ class DirMonitor(_PluginBase): transfer_type=transfer_type, target=target, meta=file_meta, - episodes_info=episodes_info, - scrape=self._scrape) + episodes_info=episodes_info) if not transferinfo: logger.error("文件转移模块运行失败") @@ -461,7 +460,7 @@ class DirMonitor(_PluginBase): ) # 刮削单个文件 - if transferinfo.need_scrape: + if self._scrape: self.chain.scrape_metadata(path=transferinfo.target_path, mediainfo=mediainfo, transfer_type=transfer_type) @@ -965,7 +964,7 @@ class DirMonitor(_PluginBase): "notify": False, "onlyonce": False, "mode": "fast", - "transfer_type": settings.TRANSFER_TYPE, + "transfer_type": "link", "monitor_dirs": "", "exclude_keywords": "", "interval": 10, diff --git a/plugins/vcbanimemonitor/__init__.py b/plugins/vcbanimemonitor/__init__.py index 150b0e0..de3edd1 100644 --- a/plugins/vcbanimemonitor/__init__.py +++ b/plugins/vcbanimemonitor/__init__.py @@ -77,7 +77,7 @@ class VCBAnimeMonitor(_PluginBase): # 插件图标 plugin_icon = "vcbmonitor.png" # 插件版本 - plugin_version = "1.7.1" + plugin_version = "1.8" # 插件作者 plugin_author = "pixel@qingwa" # 作者主页 @@ -106,6 +106,7 @@ class VCBAnimeMonitor(_PluginBase): _onlyonce = False _cron = None _size = 0 + _scrape = True # 模式 compatibility/fast _mode = "fast" # 转移方式 @@ -142,6 +143,7 @@ class VCBAnimeMonitor(_PluginBase): self._interval = config.get("interval") or 10 self._cron = config.get("cron") self._size = config.get("size") or 0 + self._scrape = config.get("scrape") self._switch_ova = config.get("ova") self._high_mode = config.get("high_mode") self._torrents_path = config.get("torrents_path") or "" @@ -286,6 +288,7 @@ class VCBAnimeMonitor(_PluginBase): "interval": self._interval, "cron": self._cron, "size": self._size, + "scrape": self._scrape, "ova": self._switch_ova, "high_mode": self._high_mode, "torrents_path": self._torrents_path @@ -508,7 +511,7 @@ class VCBAnimeMonitor(_PluginBase): ) # 刮削单个文件 - if settings.SCRAP_METADATA: + if self._scrape: self.chain.scrape_metadata(path=transferinfo.target_path, mediainfo=mediainfo, transfer_type=transfer_type) @@ -826,6 +829,22 @@ class VCBAnimeMonitor(_PluginBase): } ] }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'scrape', + 'label': '刮削元数据', + } + } + ] + } ] }, { From 351a4a2f5f9e2b43f1e6f61218bf255d2103c2b2 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 23 May 2024 19:46:08 +0800 Subject: [PATCH 03/27] =?UTF-8?q?=E9=80=82=E9=85=8D=E5=88=AE=E5=89=8A?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8F=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bac94f0..da69c3d 100644 --- a/package.json +++ b/package.json @@ -74,11 +74,12 @@ "name": "目录监控", "description": "监控目录文件发生变化时实时整理到媒体库。", "labels": "文件整理", - "version": "2.0", + "version": "2.1", "icon": "directory.png", "author": "jxxghp", "level": 1, "history": { + "v2.1": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削", "v2.0": "增强API安全性", "v1.9": "修复目录监控不能正确获取下载历史记录进行识别的问题" } @@ -278,11 +279,12 @@ "name": "整理VCB动漫压制组作品", "description": "提高部分VCB-Studio作品的识别准确率,将VCB-Studio的作品统一转移到指定目录同时进行刮削整理", "labels": "文件整理,识别", - "version": "1.7.1", + "version": "1.8", "icon": "vcbmonitor.png", "author": "pixel@qingwa", "level": 2, "history": { + "v1.8": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削", "v1.7.1": "修复偶尔安装失败问题" } }, From 9c32c875e752a453c886035c4bc077e23483a3d5 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 24 May 2024 06:27:54 +0800 Subject: [PATCH 04/27] =?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/dirmonitor/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/dirmonitor/__init__.py b/plugins/dirmonitor/__init__.py index 14fce73..64381a1 100644 --- a/plugins/dirmonitor/__init__.py +++ b/plugins/dirmonitor/__init__.py @@ -59,7 +59,7 @@ class DirMonitor(_PluginBase): # 插件图标 plugin_icon = "directory.png" # 插件版本 - plugin_version = "2.1" + plugin_version = "2.2" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -759,7 +759,7 @@ class DirMonitor(_PluginBase): 'component': 'VSelect', 'props': { 'model': 'transfer_type', - 'label': '转移方式', + 'label': '整理方式', 'items': [ {'title': '移动', 'value': 'move'}, {'title': '复制', 'value': 'copy'}, @@ -863,9 +863,9 @@ class DirMonitor(_PluginBase): 'rows': 5, 'placeholder': '每一行一个目录,支持以下几种配置方式,转移方式支持 move、copy、link、softlink、rclone_copy、rclone_move:\n' '监控目录\n' - '监控目录#转移方式\n' - '监控目录:转移目的目录\n' - '监控目录:转移目的目录#转移方式' + '监控目录#整理方式\n' + '监控目录:整理目的目录\n' + '监控目录:整理目的目录#转移方式' } } ] @@ -908,7 +908,7 @@ class DirMonitor(_PluginBase): 'props': { 'type': 'info', 'variant': 'tonal', - 'text': '监控目录不指定目的目录时,将转移到媒体库目录,并自动创建一级分类目录,同时按配置创建二级分类目录;监控目录指定了目的目录时,不会自动创建一级目录,但会根据配置创建二级分类目录。' + 'text': '支持4种配置方式:1、监控目录,2、监控目录#整理方式,3、监控目录:整理目的目录,4、监控目录:整理目的目录#转移方式。监控目录不指定目的目录时,将按媒体库目录设置整理到媒体库目录,并根据目录的分类设置自动创建一二级分类目录;监控目录指定了目的目录时,将直接整理到对应目录,v1.9.1-beta前会自动创建一级目录,v1.9.1-beta+不会自动创建一二级分类目录,建议不设置目的目录,在系统的设定中灵活设置好媒体库目录及分类。' } } ] From 251b0571045202c6c85488e1d4377859737fc441 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 24 May 2024 06:29:01 +0800 Subject: [PATCH 05/27] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20package.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index da69c3d..98fd81a 100644 --- a/package.json +++ b/package.json @@ -74,11 +74,12 @@ "name": "目录监控", "description": "监控目录文件发生变化时实时整理到媒体库。", "labels": "文件整理", - "version": "2.1", + "version": "2.2", "icon": "directory.png", "author": "jxxghp", "level": 1, "history": { + "v2.2": "更新目录设置说明", "v2.1": "增加了元数据刮削开关,升级后需要手动打开,否则默认不刮削", "v2.0": "增强API安全性", "v1.9": "修复目录监控不能正确获取下载历史记录进行识别的问题" From 69813a367dc0835cfaa92cbae49f2e2ac9a10f66 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 24 May 2024 06:32:32 +0800 Subject: [PATCH 06/27] =?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/categoryeditor/__init__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/categoryeditor/__init__.py b/plugins/categoryeditor/__init__.py index a7f2cfe..cafc705 100644 --- a/plugins/categoryeditor/__init__.py +++ b/plugins/categoryeditor/__init__.py @@ -13,7 +13,7 @@ class CategoryEditor(_PluginBase): # 插件图标 plugin_icon = "Bookstack_A.png" # 插件版本 - plugin_version = "1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -39,12 +39,9 @@ class CategoryEditor(_PluginBase): # 写入文件 if self._enabled: self.user_yaml.write_text(self._content, encoding="utf-8") - if not settings.LIBRARY_CATEGORY: - self.systemmessage.put("二级分类未开启,策略已保存但未生效!", title="二级分类策略") - return # 立即生效 CategoryHelper().init() - self.systemmessage.put("二级分类策略已更新!", title="二级分类策略") + self.systemmessage.put("二级分类策略已更新,请注意同步调整目录设置!", title="二级分类策略") def get_state(self) -> bool: return self._enabled From bea1916a7a2ee470958ab41cce93cb5cf6dc52f9 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 24 May 2024 06:33:21 +0800 Subject: [PATCH 07/27] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20package.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98fd81a..21a7f33 100644 --- a/package.json +++ b/package.json @@ -532,7 +532,7 @@ "name": "二级分类策略", "description": "编辑下载目录和媒体库目录的二级分类规则。", "labels": "文件整理", - "version": "1.1", + "version": "1.2", "icon": "Bookstack_A.png", "author": "jxxghp", "level": 1 From a7cdd78d11200f239525a49171087a613200de7c Mon Sep 17 00:00:00 2001 From: Allen Date: Fri, 24 May 2024 17:20:13 +0800 Subject: [PATCH 08/27] =?UTF-8?q?=E4=BB=AA=E8=A1=A8=E6=9D=BF=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A4=9A=E4=B8=AA=E4=B8=8B=E8=BD=BD=E5=99=A8=E6=B4=BB?= =?UTF-8?q?=E5=8A=A8=E7=A7=8D=E5=AD=90=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- plugins/downloaderhelper/__init__.py | 206 +++++++++++++++++++-------- plugins/downloaderhelper/module.py | 4 + 3 files changed, 152 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 21a7f33..8da6919 100644 --- a/package.json +++ b/package.json @@ -634,11 +634,12 @@ "name": "下载器助手", "description": "自动做种、站点标签、自动删种。", "labels": "下载管理,仪表板", - "version": "2.2", + "version": "2.3", "icon": "DownloaderHelper.png", "author": "hotlcc", "level": 2, "history": { + "v2.3": "仪表板支持多个下载器活动种子组件(主程序版本需大于v1.9.1)。", "v2.2": "优化仪表板组件标题;优化仪表板下载剩余时间描述。", "v2.1": "优化了初始配置建议;优化了配置Tracker的弹窗大小。", "v2.0": "优化了仪表板种子状态;提升仪表板对TR的适配度。", diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index a6445f0..911dbb2 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -20,7 +20,7 @@ from app.log import logger from app.modules.qbittorrent.qbittorrent import Qbittorrent from app.modules.transmission.transmission import Transmission from app.plugins import _PluginBase -from app.plugins.downloaderhelper.module import TaskContext, TaskResult, Downloader, TorrentField, TorrentFieldMap +from app.plugins.downloaderhelper.module import TaskContext, TaskResult, Downloader, TorrentField, TorrentFieldMap, DownloaderMap from app.schemas.types import EventType from app.utils.string import StringUtils @@ -33,7 +33,7 @@ class DownloaderHelper(_PluginBase): # 插件图标 plugin_icon = "DownloaderHelper.png" # 插件版本 - plugin_version = "2.2" + plugin_version = "2.3" # 插件作者 plugin_author = "hotlcc" # 作者主页 @@ -62,7 +62,7 @@ class DownloaderHelper(_PluginBase): 'site_name_priority': True, 'tag_prefix': '站点/', 'dashboard_widget_size': 12, - 'dashboard_widget_target_downloader': 'default', + 'dashboard_widget_target_downloaders': ['default'], 'dashboard_widget_display_fields': [ TorrentField.NAME.name, TorrentField.SELECT_SIZE.name, @@ -561,8 +561,9 @@ class DownloaderHelper(_PluginBase): 'content': [{ 'component': 'VSelect', 'props': { - 'model': 'dashboard_widget_target_downloader', + 'model': 'dashboard_widget_target_downloaders', 'label': '目标下载器', + 'multiple': True, 'items': [ {'title': '系统默认下载器', 'value': 'default'}, {'title': Downloader.QB.name_, 'value': Downloader.QB.id}, @@ -649,7 +650,31 @@ class DownloaderHelper(_PluginBase): def get_page(self) -> List[dict]: pass - def get_dashboard(self) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + def get_dashboard_meta(self) -> Optional[List[Dict[str, str]]]: + """ + 获取插件仪表盘元信息 + 返回示例: + [{ + "key": "dashboard1", // 仪表盘的key,在当前插件范围唯一 + "name": "仪表盘1" // 仪表盘的名称 + }, { + "key": "dashboard2", + "name": "仪表盘2" + }] + """ + dashboard_meta = [] + target_downloader_ids = self.__get_target_downloader_ids() + for target_downloader_id in target_downloader_ids: + downloader = self.__get_downloader_enum_by_id(downloader_id=target_downloader_id) + if not downloader: + continue + dashboard_meta.append({ + "key": downloader.id, + "name": f"活动种子[{downloader.short_name}]", + }) + return dashboard_meta + + def get_dashboard(self, key: str = None, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: """ 获取插件仪表盘页面,需要返回:1、仪表板col配置字典;2、全局配置(自动刷新等);3、仪表板页面元素配置json(含数据) 1、col配置参考: @@ -661,14 +686,36 @@ class DownloaderHelper(_PluginBase): "refresh": 10 // 自动刷新时间,单位秒 } 3、页面配置使用Vuetify组件拼装,参考:https://vuetifyjs.com/ + + kwargs参数可获取的值:1、user_agent:浏览器UA + + :param key: 仪表盘key,根据指定的key返回相应的仪表盘数据,缺省时返回一个固定的仪表盘数据(兼容旧版) """ if not self.get_state() or not self.__check_enable_dashboard_widget(): return None + target_downloader_ids = self.__get_target_downloader_ids() + if not key: + if not target_downloader_ids: + return None + return self.__get_active_torrent_dashboard(downloader_id=target_downloader_ids[0]) + if key in target_downloader_ids: + return self.__get_active_torrent_dashboard(downloader_id=key) + return None + + def __get_active_torrent_dashboard(self, + downloader_id: str) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + """ + 获取活动种子仪表板 + """ + downloader = self.__get_downloader_enum_by_id(downloader_id=downloader_id) + if not downloader: + return None if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') return None - dashboard_widget_size = self.__get_config_item('dashboard_widget_size') + # 列配置 + dashboard_widget_size = self.__get_config_item('dashboard_widget_size') cols = { 'cols': 12, 'xxl': dashboard_widget_size, @@ -678,14 +725,17 @@ class DownloaderHelper(_PluginBase): 'sm': 12, 'xs': 12 } + # 全局配置 attrs = { - 'title': '活动种子' + 'title': f'活动种子[{downloader.short_name}]' } - if self.__check_target_downloader(): + if self.__check_target_downloader(downloader_id=downloader_id): attrs['refresh'] = self.__get_config_item('dashboard_widget_refresh') + # 页面元素 - elements = self.__get_dashboard_elememts() + elements = self.__get_dashboard_elememts(downloader_id=downloader_id) + return cols, attrs, elements def stop_service(self): @@ -1811,13 +1861,13 @@ class DownloaderHelper(_PluginBase): result.append(field) return result - def __build_dashboard_widget_table_head_content(self, fields: List[Union[str, TorrentField]] = None) -> list: + def __build_dashboard_widget_table_head_content(self, + fields: List[TorrentField] = None) -> list: """ 构造仪表板组件表头内容 """ if not fields: - fields = self.__get_config_item('dashboard_widget_display_fields') - fields = self.__ensure_torrent_fields(fields=fields) + fields = self.__get_dashboard_widget_display_fields() if not fields: return [] return [{ @@ -1826,9 +1876,10 @@ class DownloaderHelper(_PluginBase): 'class': 'text-start ps-4' }, 'text': field.name_ - } for field in fields] + } for field in fields if field] - def __build_dashboard_widget_table_head(self, fields: List[Union[str, TorrentField]] = None) -> dict: + def __build_dashboard_widget_table_head(self, + fields: List[TorrentField] = None) -> dict: """ 构造仪表板组件表头 """ @@ -1837,9 +1888,15 @@ class DownloaderHelper(_PluginBase): 'content': self.__build_dashboard_widget_table_head_content(fields=fields) } - def __build_dashboard_widget_table_body_content(self, data: List[List[Any]], fields: List[Union[str, TorrentField]] = None) -> list: + def __build_dashboard_widget_table_body_content(self, + data: List[List[Any]], + field_count: int, + downloader_id: str) -> list: """ 构造仪表板组件表体内容 + :param downloader_id: 下载器ID + :param data: 表格数据 + :param field_count: 字段数量 """ if data: return [{ @@ -1856,7 +1913,7 @@ class DownloaderHelper(_PluginBase): } for col in row] } for row in data if row] else: - empty_text = '暂无数据' if self.__check_target_downloader() else '目标下载器配置无效' + empty_text = '暂无数据' if self.__check_target_downloader(downloader_id=downloader_id) else '目标下载器配置无效' return [{ 'component': 'tr', 'props': { @@ -1865,68 +1922,89 @@ class DownloaderHelper(_PluginBase): 'content': [{ 'component': 'td', 'props': { - 'colspan': len(fields), + 'colspan': field_count, 'class': 'text-center' }, 'text': empty_text }] }] - def __build_dashboard_widget_table_body(self, data: List[List[Any]], - fields: List[Union[str, TorrentField]] = None) -> dict: + def __build_dashboard_widget_table_body(self, + data: List[List[Any]], + field_count: int, + downloader_id: str) -> dict: """ 构造仪表板组件表体内容 """ return { 'component': 'tbody', - 'content': self.__build_dashboard_widget_table_body_content(data=data, fields=fields) + 'content': self.__build_dashboard_widget_table_body_content(data=data, field_count=field_count, downloader_id=downloader_id) } - def __get_target_downloader_id(self) -> str: + def __get_target_downloader_ids(self) -> List[str]: """ - 获取目标下载器id + 获取目标下载器ids """ - target_downloader = self.__get_config_item('dashboard_widget_target_downloader') - if target_downloader == 'default': - target_downloader = settings.DEFAULT_DOWNLOADER - if not target_downloader: - return None - return target_downloader + target_downloader_ids = [] + target_downloaders = self.__get_config_item('dashboard_widget_target_downloaders') + if not target_downloaders: + return target_downloader_ids + for target_downloader in target_downloaders: + if target_downloader == 'default': + target_downloader = settings.DEFAULT_DOWNLOADER + if target_downloader and target_downloader not in target_downloader_ids: + target_downloader_ids.append(target_downloader) + return target_downloader_ids - def __check_target_downloader(self) -> bool: + def __get_dashboard_widget_display_fields(self) -> List[TorrentField]: + """ + 获取仪表板组件展示字段 + """ + fields = self.__get_config_item('dashboard_widget_display_fields') + return self.__ensure_torrent_fields(fields=fields) + + @staticmethod + def __get_downloader_enum_by_id(downloader_id: str) -> Downloader: + """ + 根据下载器id获取枚举 + """ + if not downloader_id: + return None + return DownloaderMap.get(downloader_id) + + def __check_target_downloader(self, downloader_id: str) -> bool: """ 检查目标下载器是否有效 """ - target_downloader = self.__get_target_downloader_id() - if not target_downloader: + if not downloader_id: return False - if target_downloader == Downloader.QB.id: + if downloader_id == Downloader.QB.id: return self.__get_qbittorrent() is not None - elif target_downloader == Downloader.TR.id: + elif downloader_id == Downloader.TR.id: return self.__get_transmission() is not None else: return False - def __get_downloader_torrent_data(self, fields: List[Union[str, TorrentField]] = None): + def __get_downloader_torrent_data(self, + downloader_id: str, + fields: List[TorrentField] = None): """ 获取下载器种子数据 """ - # 目标下载器 - target_downloader = self.__get_target_downloader_id() - if not target_downloader: + if not downloader_id: return None # 字段 if not fields: - fields = self.__get_config_item('dashboard_widget_display_fields') - fields = self.__ensure_torrent_fields(fields=fields) - if target_downloader == Downloader.QB.id: + fields = self.__get_dashboard_widget_display_fields() + if downloader_id == Downloader.QB.id: return self.__get_qbittorrent_torrent_data(fields=fields) - elif target_downloader == Downloader.TR.id: + elif downloader_id == Downloader.TR.id: return self.__get_transmission_torrent_data(fields=fields) else: return None - def __get_qbittorrent_torrent_data(self, fields: List[Union[str, TorrentField]] = None): + def __get_qbittorrent_torrent_data(self, + fields: List[TorrentField] = None): """ 获取qb种子数据 """ @@ -1938,8 +2016,7 @@ class DownloaderHelper(_PluginBase): return None # 字段 if not fields: - fields = self.__get_config_item('dashboard_widget_display_fields') - fields = self.__ensure_torrent_fields(fields=fields) + fields = self.__get_dashboard_widget_display_fields() # 活动种子 torrents, error = qbittorrent.get_torrents(status=['active']) if error: @@ -1968,7 +2045,8 @@ class DownloaderHelper(_PluginBase): torrent] @staticmethod - def __process_torrent_for_qbittorrent(torrent: TorrentDictionary, fields: List[TorrentField]): + def __process_torrent_for_qbittorrent(torrent: TorrentDictionary, + fields: List[TorrentField]): """ 加工qb种子 """ @@ -2005,7 +2083,8 @@ class DownloaderHelper(_PluginBase): logger.error(f'加工qb种子: {str(e)}, torrent = {str(torrent)}', exc_info=True) return None - def __convert_qbittorrent_torrent_data(self, torrent: TorrentDictionary, + def __convert_qbittorrent_torrent_data(self, + torrent: TorrentDictionary, fields: List[TorrentField]) -> Optional[List[Any]]: """ 转换qb种子数据 @@ -2021,7 +2100,8 @@ class DownloaderHelper(_PluginBase): return data @staticmethod - def __extract_torrent_value_for_qbittorrent(torrent: TorrentDictionary, field: TorrentField) -> Any: + def __extract_torrent_value_for_qbittorrent(torrent: TorrentDictionary, + field: TorrentField) -> Any: """ 从qb种子中提取值 """ @@ -2065,7 +2145,8 @@ class DownloaderHelper(_PluginBase): arguments.append('uploadLimited') return list(set(arguments)) - def __get_transmission_torrent_data(self, fields: List[Union[str, TorrentField]] = None): + def __get_transmission_torrent_data(self, + fields: List[TorrentField] = None): """ 获取tr种子数据 """ @@ -2077,8 +2158,7 @@ class DownloaderHelper(_PluginBase): return None # 字段 if not fields: - fields = self.__get_config_item('dashboard_widget_display_fields') - fields = self.__ensure_torrent_fields(fields=fields) + fields = self.__get_dashboard_widget_display_fields() torrents, _ = transmission.trc.get_recently_active_torrents(arguments=self.__build_transmission_field_arguments(fields=fields)) if not torrents: return None @@ -2086,7 +2166,8 @@ class DownloaderHelper(_PluginBase): torrents = sorted(torrents, key=lambda torrent: torrent.fields.get(TorrentField.ADD_TIME.tr), reverse=True) return self.__convert_transmission_torrents_data(torrents=torrents, fields=fields) - def __convert_transmission_torrents_data(self, torrents: List[Torrent], + def __convert_transmission_torrents_data(self, + torrents: List[Torrent], fields: List[TorrentField]) -> Optional[List[List[Any]]]: """ 转换tr种子数据 @@ -2097,13 +2178,14 @@ class DownloaderHelper(_PluginBase): torrent] @staticmethod - def __process_torrent_for_transmission(torrent: Torrent, fields: List[TorrentField]): + def __process_torrent_for_transmission(torrent: Torrent, + fields: List[TorrentField]): """ 加工tr种子 """ if not torrent or not fields: return - + def calculate_completed(torrent: Torrent): """ 计算已完成大小 @@ -2157,7 +2239,8 @@ class DownloaderHelper(_PluginBase): logger.error(f'加工tr种子异常: {str(e)}, torrent = {str(torrent.fields)}', exc_info=True) return None - def __convert_transmission_torrent_data(self, torrent: Torrent, + def __convert_transmission_torrent_data(self, + torrent: Torrent, fields: List[TorrentField]) -> Optional[List[Any]]: """ 转换tr种子数据 @@ -2173,7 +2256,8 @@ class DownloaderHelper(_PluginBase): return data @staticmethod - def __extract_torrent_value_for_transmission(torrent: Torrent, field: TorrentField) -> Any: + def __extract_torrent_value_for_transmission(torrent: Torrent, + field: TorrentField) -> Any: """ 从tr种子中提取值 """ @@ -2190,16 +2274,18 @@ class DownloaderHelper(_PluginBase): logger.error(f'从tr种子中提取值异常: {str(e)}, torrent = {str(torrent.fields)}', exc_info=True) return None - def __get_dashboard_elememts(self) -> list: + def __get_dashboard_elememts(self, downloader_id: str) -> list: """ 获取仪表板元素 """ + if not downloader_id: + return None if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') return None - fields = self.__get_config_item('dashboard_widget_display_fields') - fields = self.__ensure_torrent_fields(fields=fields) - data = self.__get_downloader_torrent_data(fields=fields) + fields = self.__get_dashboard_widget_display_fields() + field_count=len(fields) + data = self.__get_downloader_torrent_data(downloader_id=downloader_id, fields=fields) if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') return None @@ -2215,7 +2301,7 @@ class DownloaderHelper(_PluginBase): }, 'content': [ self.__build_dashboard_widget_table_head(fields=fields), - self.__build_dashboard_widget_table_body(data=data, fields=fields) + self.__build_dashboard_widget_table_body(data=data, field_count=field_count, downloader_id=downloader_id) ] }] diff --git a/plugins/downloaderhelper/module.py b/plugins/downloaderhelper/module.py index 855f6b9..72dbc4a 100644 --- a/plugins/downloaderhelper/module.py +++ b/plugins/downloaderhelper/module.py @@ -17,6 +17,10 @@ class Downloader(Enum): self.short_name: str = short_name +# Downloader 映射 +DownloaderMap = dict((d.id, d) for d in Downloader) + + class TaskResult: """ 任务执行结果 From 58bf34280dc651ed3b466bc8780887380bbc7476 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 24 May 2024 17:31:16 +0800 Subject: [PATCH 09/27] fix README --- README.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e0d332c..0a162c5 100644 --- a/README.md +++ b/README.md @@ -448,11 +448,27 @@ class EventType(Enum): ### 8. 如何将插件内容显示到仪表板? - `v1.8.7+` 支持将插件的内容显示到仪表盘,并支持定义占据的单元格大小,插件产生的仪表板仅管理员可见。 - 1. 根据插件需要展示的Widget内容规划展示内容的样式和规格,也可设计多个规格样式并提供配置项供用户选择。 -- 2. 实现 `get_dashboard` 方法,返回仪表盘的配置信息,包括仪表盘的cols列配置(适配不同屏幕),以及仪表盘的页面配置json,具体可参考插件`站点数据统计`: +- 2. 实现 `get_dashboard_meta` 方法,定义仪表板key及名称,支持一件插件有多个仪表板: ```python -def get_dashboard(self, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: +def get_dashboard_meta(self) -> Optional[List[Dict[str, str]]]: """ - 获取插件仪表盘页面,需要返回:1、仪表板cols配置字典;2、全局配置(自动刷新等);2、仪表板页面元素配置json(含数据) + 获取插件仪表盘元信息 + 返回示例: + [{ + "key": "dashboard1", // 仪表盘的key,在当前插件范围唯一 + "name": "仪表盘1" // 仪表盘的名称 + }, { + "key": "dashboard2", + "name": "仪表盘2" + }] + """ + pass +``` +- 3. 实现 `get_dashboard` 方法,根据key返回仪表盘的详细配置信息,包括仪表盘的cols列配置(适配不同屏幕),以及仪表盘的页面配置json,具体可参考插件`站点数据统计`: +```python +def get_dashboard(self, key: str, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + """ + 获取插件仪表盘页面,需要返回:1、仪表板col配置字典;2、全局配置(自动刷新等);3、仪表板页面元素配置json(含数据) 1、col配置参考: { "cols": 12, "md": 6 @@ -465,8 +481,10 @@ def get_dashboard(self, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, An "subtitle": "组件子标题", // 组件子标题,缺省时不展示子标题 } 3、页面配置使用Vuetify组件拼装,参考:https://vuetifyjs.com/ - + kwargs参数可获取的值:1、user_agent:浏览器UA + + :param key: 仪表盘key,根据指定的key返回相应的仪表盘数据,缺省时返回一个固定的仪表盘数据(兼容旧版) """ pass ``` From 29303d3818a21ceea285fde52f940c0066027a0b Mon Sep 17 00:00:00 2001 From: thsrite Date: Fri, 24 May 2024 20:18:25 +0800 Subject: [PATCH 10/27] =?UTF-8?q?fix=20=E5=AA=92=E4=BD=93=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=90=8C=E6=AD=A5=E5=88=A0=E9=99=A4v1.6=3D>=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=88=A0=E9=99=A4=E8=BE=85=E7=A7=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- plugins/mediasyncdel/__init__.py | 77 +++++++++----------------------- 2 files changed, 22 insertions(+), 58 deletions(-) diff --git a/package.json b/package.json index 21a7f33..1f21309 100644 --- a/package.json +++ b/package.json @@ -130,11 +130,12 @@ "name": "媒体文件同步删除", "description": "同步删除历史记录、源文件和下载任务。", "labels": "文件整理", - "version": "1.5", + "version": "1.6", "icon": "mediasyncdel.png", "author": "thsrite", "level": 1, "history": { + "v1.6": "修复删除辅种", "v1.5": "支持手动删除订阅历史记录(本次更新之后的历史)" } }, diff --git a/plugins/mediasyncdel/__init__.py b/plugins/mediasyncdel/__init__.py index 7ca9ed6..2ff19c1 100644 --- a/plugins/mediasyncdel/__init__.py +++ b/plugins/mediasyncdel/__init__.py @@ -17,9 +17,6 @@ from app.db.models.transferhistory import TransferHistory from app.log import logger from app.modules.emby import Emby from app.modules.jellyfin import Jellyfin -from app.modules.qbittorrent import Qbittorrent -from app.modules.themoviedb.tmdbv3api import Episode -from app.modules.transmission import Transmission from app.plugins import _PluginBase from app.schemas.types import NotificationType, EventType, MediaType, MediaImageType @@ -32,7 +29,7 @@ class MediaSyncDel(_PluginBase): # 插件图标 plugin_icon = "mediasyncdel.png" # 插件版本 - plugin_version = "1.5" + plugin_version = "1.6" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -45,7 +42,6 @@ class MediaSyncDel(_PluginBase): auth_level = 1 # 私有属性 - episode = None _scheduler: Optional[BackgroundScheduler] = None _enabled = False _sync_type: str = "" @@ -58,16 +54,11 @@ class MediaSyncDel(_PluginBase): _transferchain = None _transferhis = None _downloadhis = None - qb = None - tr = None def init_plugin(self, config: dict = None): self._transferchain = TransferChain() self._transferhis = self._transferchain.transferhis self._downloadhis = self._transferchain.downloadhis - self.episode = Episode() - self.qb = Qbittorrent() - self.tr = Transmission() # 停止现有任务 self.stop_service() @@ -1199,12 +1190,8 @@ class MediaSyncDel(_PluginBase): # 删除转种后任务 logger.info(f"删除转种后下载任务:{download} - {download_id}") # 删除转种后下载任务 - if download == "transmission": - self.tr.delete_torrents(delete_file=True, - ids=download_id) - else: - self.qb.delete_torrents(delete_file=True, - ids=download_id) + self.chain.remove_torrents(hashs=torrent_hash, + downloader=download) handle_torrent_hashs.append(download_id) else: # 暂停种子 @@ -1219,10 +1206,7 @@ class MediaSyncDel(_PluginBase): logger.info(f"暂停转种后下载任务:{download} - {download_id}") # 删除转种后下载任务 - if download == "transmission": - self.tr.stop_torrents(ids=download_id) - else: - self.qb.stop_torrents(ids=download_id) + self.chain.stop_torrents(hashs=download_id, downloader=download) handle_torrent_hashs.append(download_id) else: # 未转种de情况 @@ -1237,8 +1221,7 @@ class MediaSyncDel(_PluginBase): handle_torrent_hashs.append(download_id) # 处理辅种 - handle_torrent_hashs = self.__del_seed(download=download, - download_id=download_id, + handle_torrent_hashs = self.__del_seed(download_id=download_id, delete_flag=delete_flag, handle_torrent_hashs=handle_torrent_hashs) # 处理合集 @@ -1284,27 +1267,19 @@ class MediaSyncDel(_PluginBase): # 删除合集种子 if delete_flag: - if str(download_file.downloader) == "transmission": - self.tr.delete_torrents(delete_file=True, - ids=download_file.download_hash) - else: - self.qb.delete_torrents(delete_file=True, - ids=download_file.download_hash) - + self.chain.remove_torrents(hashs=download_file.download_hash, + downloader=download_file.downloader) logger.info(f"删除合集种子 {download_file.downloader} {download_file.download_hash}") else: # 暂停合集种子 - if str(download_file.downloader) == "transmission": - self.tr.stop_torrents(ids=download_file.download_hash) - else: - self.qb.stop_torrents(ids=download_file.download_hash) + self.chain.stop_torrents(hashs=download_file.download_hash, + downloader=download_file.downloader) logger.info(f"暂停合集种子 {download_file.downloader} {download_file.download_hash}") # 已处理种子+1 handle_torrent_hashs.append(download_file.download_hash) # 处理合集辅种 - handle_torrent_hashs = self.__del_seed(download=download_file.downloader, - download_id=download_file.download_hash, + handle_torrent_hashs = self.__del_seed(download_id=download_file.download_hash, delete_flag=delete_flag, handle_torrent_hashs=handle_torrent_hashs) except Exception as e: @@ -1313,7 +1288,7 @@ class MediaSyncDel(_PluginBase): return handle_torrent_hashs - def __del_seed(self, download, download_id, delete_flag, handle_torrent_hashs): + def __del_seed(self, download_id, delete_flag, handle_torrent_hashs): """ 删除辅种 """ @@ -1337,30 +1312,18 @@ class MediaSyncDel(_PluginBase): # 删除辅种历史 for torrent in torrents: handle_torrent_hashs.append(torrent) - if str(download) == "qbittorrent": - # 删除辅种 - if delete_flag: - logger.info(f"删除辅种:{downloader} - {torrent}") - self.qb.delete_torrents(delete_file=True, - ids=torrent) - # 暂停辅种 - else: - self.qb.stop_torrents(ids=torrent) - logger.info(f"辅种:{downloader} - {torrent} 暂停") + # 删除辅种 + if delete_flag: + logger.info(f"删除辅种:{downloader} - {torrent}") + self.chain.remove_torrents(hashs=torrent, + downloader=downloader) + # 暂停辅种 else: - # 删除辅种 - if delete_flag: - logger.info(f"删除辅种:{downloader} - {torrent}") - self.tr.delete_torrents(delete_file=True, - ids=torrent) - # 暂停辅种 - else: - self.tr.stop_torrents(ids=torrent) - logger.info(f"辅种:{downloader} - {torrent} 暂停") + self.chain.stop_torrents(hashs=torrent, download=downloader) + logger.info(f"辅种:{downloader} - {torrent} 暂停") # 处理辅种的辅种 - handle_torrent_hashs = self.__del_seed(download=downloader, - download_id=torrent, + handle_torrent_hashs = self.__del_seed(download_id=torrent, delete_flag=delete_flag, handle_torrent_hashs=handle_torrent_hashs) From 1465e76edbeb07b131d91f8d2a3339ec12275791 Mon Sep 17 00:00:00 2001 From: Allen Date: Sat, 25 May 2024 22:13:42 +0800 Subject: [PATCH 11/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dtr=E6=B4=BB=E5=8A=A8?= =?UTF-8?q?=E7=A7=8D=E5=AD=90=E4=BB=AA=E8=A1=A8=E6=9D=BF=E7=9A=84=E7=A7=8D?= =?UTF-8?q?=E5=AD=90=E6=8E=92=E5=BA=8F=E7=9A=84bug=EF=BC=9B=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=8F=92=E4=BB=B6=E7=9A=84=E6=B6=88=E6=81=AF=E5=8F=91?= =?UTF-8?q?=E9=80=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- plugins/downloaderhelper/__init__.py | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 89e64df..9318e3d 100644 --- a/package.json +++ b/package.json @@ -635,11 +635,12 @@ "name": "下载器助手", "description": "自动做种、站点标签、自动删种。", "labels": "下载管理,仪表板", - "version": "2.3", + "version": "2.4", "icon": "DownloaderHelper.png", "author": "hotlcc", "level": 2, "history": { + "v2.4": "修复tr活动种子仪表板的种子排序的bug;优化插件的消息发送。", "v2.3": "仪表板支持多个下载器活动种子组件(主程序版本需大于v1.9.1)。", "v2.2": "优化仪表板组件标题;优化仪表板下载剩余时间描述。", "v2.1": "优化了初始配置建议;优化了配置Tracker的弹窗大小。", diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 911dbb2..29f8eaa 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -33,7 +33,7 @@ class DownloaderHelper(_PluginBase): # 插件图标 plugin_icon = "DownloaderHelper.png" # 插件版本 - plugin_version = "2.3" + plugin_version = "2.4" # 插件作者 plugin_author = "hotlcc" # 作者主页 @@ -670,7 +670,7 @@ class DownloaderHelper(_PluginBase): continue dashboard_meta.append({ "key": downloader.id, - "name": f"活动种子[{downloader.short_name}]", + "name": f"活动种子 #{downloader.short_name}", }) return dashboard_meta @@ -728,7 +728,7 @@ class DownloaderHelper(_PluginBase): # 全局配置 attrs = { - 'title': f'活动种子[{downloader.short_name}]' + 'title': f'活动种子 #{downloader.short_name}' } if self.__check_target_downloader(downloader_id=downloader_id): attrs['refresh'] = self.__get_config_item('dashboard_widget_refresh') @@ -1293,7 +1293,7 @@ class DownloaderHelper(_PluginBase): text = self.__build_notify_message(context=context) if not text: return - self.post_message(title=f'{self.plugin_name}任务执行结果', text=text, userid=context.get_username()) + self.post_message(title=f'{self.plugin_name}任务执行结果', text=text) @staticmethod def __build_notify_message(context: TaskContext): @@ -2128,6 +2128,7 @@ class DownloaderHelper(_PluginBase): arguments.append('id') arguments.append(TorrentField.NAME.tr) arguments.append('hashString') + arguments.append(TorrentField.ADD_TIME.tr) # 处理依赖的字段 if TorrentField.COMPLETED in fields: arguments.append('fileStats') From 9f425100e31cac6b109dc1bbe6d006d0c29b357b Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 26 May 2024 10:10:12 +0800 Subject: [PATCH 12/27] =?UTF-8?q?fix=20=E7=9B=AE=E5=BD=95=E7=9B=91?= =?UTF-8?q?=E6=8E=A7=E6=8F=92=E4=BB=B6=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/dirmonitor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dirmonitor/__init__.py b/plugins/dirmonitor/__init__.py index 64381a1..82cb002 100644 --- a/plugins/dirmonitor/__init__.py +++ b/plugins/dirmonitor/__init__.py @@ -908,7 +908,7 @@ class DirMonitor(_PluginBase): 'props': { 'type': 'info', 'variant': 'tonal', - 'text': '支持4种配置方式:1、监控目录,2、监控目录#整理方式,3、监控目录:整理目的目录,4、监控目录:整理目的目录#转移方式。监控目录不指定目的目录时,将按媒体库目录设置整理到媒体库目录,并根据目录的分类设置自动创建一二级分类目录;监控目录指定了目的目录时,将直接整理到对应目录,v1.9.1-beta前会自动创建一级目录,v1.9.1-beta+不会自动创建一二级分类目录,建议不设置目的目录,在系统的设定中灵活设置好媒体库目录及分类。' + 'text': '支持4种配置方式:1、监控目录,2、监控目录#整理方式,3、监控目录:整理目的目录,4、监控目录:整理目的目录#转移方式。监控目录不指定目的目录时,将按媒体库目录设置整理到媒体库目录,并根据目录的分类设置自动创建一二级分类目录;监控目录指定了目的目录时,会尝试在媒体库目录设定中查找对应路径的目录配置,如存在则以目录设定的分类选项创建子目录,否则直接整理到该目的目录下。建议不设置目的目录,由系统根据目录设定自动分类整理。' } } ] From 5383b771df9d5d202421e0d93080ec17026e98af Mon Sep 17 00:00:00 2001 From: John Connor Date: Sun, 26 May 2024 13:46:51 +0800 Subject: [PATCH 13/27] =?UTF-8?q?[fix]=20=E4=BF=AE=E5=A4=8D=20YemaPT=20?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E7=99=BB=E5=BD=95=E3=80=81=E6=94=AF=E6=8C=81?= =?UTF-8?q?YemaPT=20=E8=87=AA=E5=8A=A8=E7=AD=BE=E5=88=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- plugins/autosignin/__init__.py | 2 +- plugins/autosignin/sites/yema.py | 48 ++++++++++++++++++++++---------- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 9318e3d..163f0f7 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,12 @@ "name": "站点自动签到", "description": "自动模拟登录、签到站点。", "labels": "站点", - "version": "2.3.1", + "version": "2.3.2", "icon": "signin.png", "author": "thsrite", "level": 2, "history": { + "v2.3.2": "修复YemaPT登录失败,支持YemaPT自动签到", "v2.3.1": "修复签到报错问题", "v2.3": "优化模拟登录逻辑,支持YemaPT模拟登录", "v2.2": "适配馒头最新变化,需要升级至v1.8.5+版本且维护好Authorization", diff --git a/plugins/autosignin/__init__.py b/plugins/autosignin/__init__.py index eb6f09b..6c3a439 100644 --- a/plugins/autosignin/__init__.py +++ b/plugins/autosignin/__init__.py @@ -38,7 +38,7 @@ class AutoSignIn(_PluginBase): # 插件图标 plugin_icon = "signin.png" # 插件版本 - plugin_version = "2.3.1" + plugin_version = "2.3.2" # 插件作者 plugin_author = "thsrite" # 作者主页 diff --git a/plugins/autosignin/sites/yema.py b/plugins/autosignin/sites/yema.py index dc62cc9..879611f 100644 --- a/plugins/autosignin/sites/yema.py +++ b/plugins/autosignin/sites/yema.py @@ -8,9 +8,9 @@ from app.plugins.autosignin.sites import _ISiteSigninHandler from app.utils.http import RequestUtils -class MTorrent(_ISiteSigninHandler): +class YemaPT(_ISiteSigninHandler): """ - m-team签到 + YemaPT 签到 """ # 匹配的站点Url,每一个实现类都需要设置为自己的站点Url site_url = "yemapt.org" @@ -26,7 +26,7 @@ class MTorrent(_ISiteSigninHandler): def signin(self, site_info: CommentedMap) -> Tuple[bool, str]: """ - 执行签到操作,馒头实际没有签到,非仿真模式下需要更新访问时间 + 执行签到操作 :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息 :return: 签到结果信息 """ @@ -35,19 +35,20 @@ class MTorrent(_ISiteSigninHandler): "User-Agent": site_info.get("ua"), "Accept": "application/json, text/plain, */*", } - # 更新最后访问时间 - res = RequestUtils(headers=headers, - timeout=15, - cookies=site_info.get("cookie"), - proxies=settings.PROXY if site_info.get("proxy") else None, - referer=site_info.get('url') - ).post_res(url=urljoin(site_info.get('url'), "api/user/profile")) + # 获取用户信息,更新最后访问时间 + res = (RequestUtils(headers=headers, + timeout=15, + cookies=site_info.get("cookie"), + proxies=settings.PROXY if site_info.get("proxy") else None, + referer=site_info.get('url') + ).get_res(urljoin(site_info.get('url'), "api/consumer/checkIn"))) + if res and res.json().get("success"): - return True, "模拟登录成功" + return True, "签到成功" elif res is not None: - return False, f"模拟登录失败,状态码:{res.status_code}" + return False, f"签到失败,签到结果:{res.json().get('errorMessage')}" else: - return False, "模拟登录失败,无法打开网站" + return False, "签到失败,无法打开网站" def login(self, site_info: CommentedMap) -> Tuple[bool, str]: """ @@ -55,4 +56,23 @@ class MTorrent(_ISiteSigninHandler): :param site_info: 站点信息,含有站点Url、站点Cookie、UA等信息 :return: 登录结果信息 """ - return self.signin(site_info) + + headers = { + "Content-Type": "application/json", + "User-Agent": site_info.get("ua"), + "Accept": "application/json, text/plain, */*", + } + # 获取用户信息,更新最后访问时间 + res = (RequestUtils(headers=headers, + timeout=15, + cookies=site_info.get("cookie"), + proxies=settings.PROXY if site_info.get("proxy") else None, + referer=site_info.get('url') + ).get_res(urljoin(site_info.get('url'), "api/user/profile"))) + + if res and res.json().get("success"): + return True, "模拟登录成功" + elif res is not None: + return False, f"模拟登录失败,状态码:{res.status_code}" + else: + return False, "模拟登录失败,无法打开网站" From 9296ad29f8577cbe8e9757807d52e36050c9f9c3 Mon Sep 17 00:00:00 2001 From: xuzhi Date: Mon, 27 May 2024 01:34:21 +0000 Subject: [PATCH 14/27] Update cleaninvalidseed to v1.6 --- package.json | 3 ++- plugins/cleaninvalidseed/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9c4e8f9..efcc7be 100644 --- a/package.json +++ b/package.json @@ -702,11 +702,12 @@ "name": "清理QB无效做种", "description": "清理已经被站点删除的种子及对应源文件,仅支持QB", "labels": "Qbittorrent", - "version": "1.5", + "version": "1.6", "icon": "clean_a.png", "author": "DzAvril", "level": 1, "history": { + "v1.6": "修复当种子有多个标签时,通过标签过滤不删除种子会失效的问题", "v1.5": "1. 增加通过分类、标签过滤不删除种子功能;2. 全量通知提供更多信息", "v1.4": "修复插件功能失效的问题", "v1.3": "1. 增加远程命令 2. 根据tracker error_message字段进行过滤,避免误删", diff --git a/plugins/cleaninvalidseed/__init__.py b/plugins/cleaninvalidseed/__init__.py index aee2cea..1c44fc1 100644 --- a/plugins/cleaninvalidseed/__init__.py +++ b/plugins/cleaninvalidseed/__init__.py @@ -28,7 +28,7 @@ class CleanInvalidSeed(_PluginBase): # 插件图标 plugin_icon = "clean_a.png" # 插件版本 - plugin_version = "1.5" + plugin_version = "1.6" # 插件作者 plugin_author = "DzAvril" # 作者主页 @@ -307,7 +307,7 @@ class CleanInvalidSeed(_PluginBase): if torrent.category in exclude_categories: is_excluded = True invalid_torrents_exclude_categories.append(torrent) - torrent_labels = torrent.tags.split(",") + torrent_labels = [tag.strip() for tag in torrent.tags.split(",")] for label in torrent_labels: if label in exclude_labels: is_excluded = True From 8ecc9ed857e3e4e21aaceb616b6d2a4e8d8acd8c Mon Sep 17 00:00:00 2001 From: jxxghp Date: Mon, 27 May 2024 12:22:07 +0800 Subject: [PATCH 15/27] fix https://github.com/jxxghp/MoviePilot/issues/2190 --- package.json | 3 +- plugins/rsssubscribe/__init__.py | 87 ++++++++++++++++---------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index f08f70b..a94d759 100644 --- a/package.json +++ b/package.json @@ -307,11 +307,12 @@ "name": "自定义订阅", "description": "定时刷新RSS报文,识别内容后添加订阅或直接下载。", "labels": "订阅", - "version": "1.3", + "version": "1.4", "icon": "rss.png", "author": "jxxghp", "level": 2, "history": { + "v1.4": "修复剧集本地是否存在的判断错误问题", "v1.3": "支持手动删除订阅历史记录" } }, diff --git a/plugins/rsssubscribe/__init__.py b/plugins/rsssubscribe/__init__.py index 39ec693..723dafc 100644 --- a/plugins/rsssubscribe/__init__.py +++ b/plugins/rsssubscribe/__init__.py @@ -19,6 +19,7 @@ from app.core.metainfo import MetaInfo from app.helper.rss import RssHelper from app.log import logger from app.plugins import _PluginBase +from app.schemas import ExistMediaInfo from app.schemas.types import SystemConfigKey, MediaType lock = Lock() @@ -32,7 +33,7 @@ class RssSubscribe(_PluginBase): # 插件图标 plugin_icon = "rss.png" # 插件版本 - plugin_version = "1.3" + plugin_version = "1.4" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -656,51 +657,49 @@ class RssSubscribe(_PluginBase): if not result: logger.info(f"{title} {description} 不匹配过滤规则") continue - # 查询缺失的媒体信息 - exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) - if exist_flag: - logger.info(f'{mediainfo.title_year} 媒体库中已存在') + # 媒体库已存在的剧集 + exist_info: Optional[ExistMediaInfo] = self.chain.media_exists(mediainfo=mediainfo) + if mediainfo.type == MediaType.TV: + if exist_info: + exist_season = exist_info.seasons + if exist_season: + exist_episodes = exist_season.get(meta.begin_season) + if exist_episodes and set(meta.episode_list).issubset(set(exist_episodes)): + logger.info(f'{mediainfo.title_year} {meta.season_episode} 己存在') + continue + elif exist_info: + # 电影已存在 + logger.info(f'{mediainfo.title_year} 己存在') continue + # 下载或订阅 + if self._action == "download": + # 添加下载 + result = self.downloadchain.download_single( + context=Context( + meta_info=meta, + media_info=mediainfo, + torrent_info=torrentinfo, + ), + save_path=self._save_path, + username="RSS订阅" + ) + if not result: + logger.error(f'{title} 下载失败') + continue else: - if self._action == "download": - if mediainfo.type == MediaType.TV: - if no_exists: - exist_info = no_exists.get(mediainfo.tmdb_id) - season_info = exist_info.get(meta.begin_season or 1) - if not season_info: - logger.info(f'{mediainfo.title_year} {meta.season} 己存在') - continue - if (season_info.episodes - and not set(meta.episode_list).issubset(set(season_info.episodes))): - logger.info(f'{mediainfo.title_year} {meta.season_episode} 己存在') - continue - # 添加下载 - result = self.downloadchain.download_single( - context=Context( - meta_info=meta, - media_info=mediainfo, - torrent_info=torrentinfo, - ), - save_path=self._save_path, - username="RSS订阅" - ) - if not result: - logger.error(f'{title} 下载失败') - continue - else: - # 检查是否在订阅中 - subflag = self.subscribechain.exists(mediainfo=mediainfo, meta=meta) - if subflag: - logger.info(f'{mediainfo.title_year} {meta.season} 正在订阅中') - continue - # 添加订阅 - self.subscribechain.add(title=mediainfo.title, - year=mediainfo.year, - mtype=mediainfo.type, - tmdbid=mediainfo.tmdb_id, - season=meta.begin_season, - exist_ok=True, - username="RSS订阅") + # 检查是否在订阅中 + subflag = self.subscribechain.exists(mediainfo=mediainfo, meta=meta) + if subflag: + logger.info(f'{mediainfo.title_year} {meta.season} 正在订阅中') + continue + # 添加订阅 + self.subscribechain.add(title=mediainfo.title, + year=mediainfo.year, + mtype=mediainfo.type, + tmdbid=mediainfo.tmdb_id, + season=meta.begin_season, + exist_ok=True, + username="RSS订阅") # 存储历史记录 history.append({ "title": f"{mediainfo.title} {meta.season}", From d69914640678e268cb99c01d489835b4d310f951 Mon Sep 17 00:00:00 2001 From: xuzhi Date: Mon, 27 May 2024 05:00:48 +0000 Subject: [PATCH 16/27] Update cleaninvalidseed to v1.7 --- package.json | 3 ++- plugins/cleaninvalidseed/__init__.py | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index efcc7be..2f20c1b 100644 --- a/package.json +++ b/package.json @@ -702,11 +702,12 @@ "name": "清理QB无效做种", "description": "清理已经被站点删除的种子及对应源文件,仅支持QB", "labels": "Qbittorrent", - "version": "1.6", + "version": "1.7", "icon": "clean_a.png", "author": "DzAvril", "level": 1, "history": { + "v1.7": "修复因消息内容包含'_'导致telegram API调用失败的问题", "v1.6": "修复当种子有多个标签时,通过标签过滤不删除种子会失效的问题", "v1.5": "1. 增加通过分类、标签过滤不删除种子功能;2. 全量通知提供更多信息", "v1.4": "修复插件功能失效的问题", diff --git a/plugins/cleaninvalidseed/__init__.py b/plugins/cleaninvalidseed/__init__.py index 1c44fc1..a748f4b 100644 --- a/plugins/cleaninvalidseed/__init__.py +++ b/plugins/cleaninvalidseed/__init__.py @@ -28,7 +28,7 @@ class CleanInvalidSeed(_PluginBase): # 插件图标 plugin_icon = "clean_a.png" # 插件版本 - plugin_version = "1.6" + plugin_version = "1.7" # 插件作者 plugin_author = "DzAvril" # 作者主页 @@ -398,29 +398,34 @@ class CleanInvalidSeed(_PluginBase): logger.info(exclude_labels_msg) # 通知 if self._notify: + invalid_msg = invalid_msg.replace('_', '\_') self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", text=invalid_msg, ) if self._notify_all: + tracker_not_working_msg = tracker_not_working_msg.replace('_', '\_') self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", text=tracker_not_working_msg, ) if self._delete_invalid_torrents: + deleted_msg = deleted_msg.replace('_', '\_') self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", text=deleted_msg, ) if self._notify_all: + exclude_categories_msg = exclude_categories_msg.replace('_', '\_') self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", text=exclude_categories_msg, ) + exclude_labels_msg = exclude_labels_msg.replace('_', '\_') self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", @@ -489,6 +494,7 @@ class CleanInvalidSeed(_PluginBase): message += f"***已删除无效源文件,释放{StringUtils.str_filesize(total_size)}空间!***\n" logger.info(message) if self._notify: + message = message.replace('_', '\_') self.post_message( mtype=NotificationType.SiteMessage, title=f"【清理无效做种】", From f405935a961ea23e4e6caba03de40010ba102fa3 Mon Sep 17 00:00:00 2001 From: Allen Date: Mon, 27 May 2024 16:59:03 +0800 Subject: [PATCH 17/27] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E3=80=90?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=99=A8=E5=8A=A9=E6=89=8B=E3=80=91=E5=92=8C?= =?UTF-8?q?=E3=80=90=E6=8F=92=E4=BB=B6=E8=87=AA=E5=8A=A8=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E3=80=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 8 +++-- plugins/downloaderhelper/__init__.py | 47 +++++++++++++++------------ plugins/pluginautoupgrade/__init__.py | 5 +-- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 69b87fd..b84970e 100644 --- a/package.json +++ b/package.json @@ -637,11 +637,12 @@ "name": "下载器助手", "description": "自动做种、站点标签、自动删种。", "labels": "下载管理,仪表板", - "version": "2.4", + "version": "2.5", "icon": "DownloaderHelper.png", "author": "hotlcc", - "level": 2, + "level": 1, "history": { + "v2.5": "优化通知类型;降低认证级别要求,使MP非认证用户可用,但无法使用【站点名称优先】功能。主程序需升级至v1.9.2及以上版本,否则插件功能异常!", "v2.4": "修复tr活动种子仪表板的种子排序的bug;优化插件的消息发送。", "v2.3": "仪表板支持多个下载器活动种子组件(主程序版本需大于v1.9.1)。", "v2.2": "优化仪表板组件标题;优化仪表板下载剩余时间描述。", @@ -687,11 +688,12 @@ "name": "插件自动升级", "description": "定时检测、升级插件。", "labels": "自动更新", - "version": "1.8", + "version": "1.9", "icon": "PluginAutoUpgrade.png", "author": "hotlcc", "level": 1, "history": { + "v1.9": "优化通知类型。", "v1.8": "修复重置插件后丢失配置建议的问题。", "v1.7": "修复了一些BUG。", "v1.6": "修正数字配置值提交为字符串导致的问题。", diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index 29f8eaa..c1fc0b4 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -11,6 +11,7 @@ from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger from qbittorrentapi import TorrentDictionary, TorrentState from transmission_rpc.torrent import Torrent, Status as TorrentStatus +from ruamel.yaml.comments import CommentedMap from app.core.config import settings from app.core.event import eventmanager, Event @@ -21,6 +22,7 @@ from app.modules.qbittorrent.qbittorrent import Qbittorrent from app.modules.transmission.transmission import Transmission from app.plugins import _PluginBase from app.plugins.downloaderhelper.module import TaskContext, TaskResult, Downloader, TorrentField, TorrentFieldMap, DownloaderMap +from app.schemas import NotificationType from app.schemas.types import EventType from app.utils.string import StringUtils @@ -33,7 +35,7 @@ class DownloaderHelper(_PluginBase): # 插件图标 plugin_icon = "DownloaderHelper.png" # 插件版本 - plugin_version = "2.4" + plugin_version = "2.5" # 插件作者 plugin_author = "hotlcc" # 作者主页 @@ -43,7 +45,7 @@ class DownloaderHelper(_PluginBase): # 加载顺序 plugin_order = 66 # 可使用的用户级别 - auth_level = 2 + auth_level = 1 # 插件说明链接 __help_url = 'https://github.com/jxxghp/MoviePilot-Plugins/tree/main/plugins/downloaderhelper' @@ -99,6 +101,9 @@ class DownloaderHelper(_PluginBase): # 停止现有服务 self.stop_service() + # 检查环境 + self.__check_environment() + # 修正配置 config = self.__fix_config(config=config) # 加载插件配置 @@ -358,7 +363,7 @@ class DownloaderHelper(_PluginBase): 'props': { 'model': 'site_name_priority', 'label': '站点名称优先', - 'hint': '给种子添加站点标签时,是否优先以站点名称作为标签内容(否则将使用域名关键字)?' + 'hint': '给种子添加站点标签时,是否优先以站点名称作为标签内容(否则将使用域名关键字)?MoviePilot需要认证,否则将不生效。' } }] }] @@ -752,6 +757,14 @@ class DownloaderHelper(_PluginBase): finally: self.__exit_event.clear() + @staticmethod + def __check_mp_user_auth() -> bool: + """ + 检查mp用户认证 + :return: True表示已认证 + """ + return SitesHelper().auth_level >= 2 + def __parse_tracker_mappings(self, tracker_mappings: str) -> Dict[str, str]: """ 解析配置的tracker映射 @@ -870,6 +883,13 @@ class DownloaderHelper(_PluginBase): self.update_config(config=config) return config + def __check_environment(self): + """" + 检查环境 + """ + if not self.__check_mp_user_auth(): + logger.warn("MoviePilot未认证,【站点名称优先】功能将不可用。") + def __get_config_item(self, config_key: str, use_default: bool = True) -> Any: """ 获取插件配置项 @@ -913,7 +933,7 @@ class DownloaderHelper(_PluginBase): return value return None - def __get_site_info_by_domain(self, site_domain: str) -> Optional[str]: + def __get_site_info_by_domain(self, site_domain: str) -> CommentedMap: """ 根据站点域名从索引中获取站点信息 :param site_domain: 站点域名 @@ -1293,7 +1313,7 @@ class DownloaderHelper(_PluginBase): text = self.__build_notify_message(context=context) if not text: return - self.post_message(title=f'{self.plugin_name}任务执行结果', text=text) + self.post_message(title=f'{self.plugin_name}任务执行结果', text=text, mtype=NotificationType.Plugin) @staticmethod def __build_notify_message(context: TaskContext): @@ -1328,24 +1348,11 @@ class DownloaderHelper(_PluginBase): text += '\n————————————\n' return text - def __get_system_module_instance(self, module_id: str) -> Union[Qbittorrent, Transmission]: - """ - 获取系统模块实例 - """ - if not module_id: - return None - module_manager = ModuleManager() - running_modules = module_manager._running_modules - if not running_modules: - return None - module = running_modules.get(module_id) - return module if module else None - def __get_qbittorrent(self) -> Qbittorrent: """ 获取qb实例 """ - module = self.__get_system_module_instance(module_id='QbittorrentModule') + module = ModuleManager().get_running_module(module_id='QbittorrentModule') if not module: return None qbittorrent = getattr(module, 'qbittorrent') @@ -1357,7 +1364,7 @@ class DownloaderHelper(_PluginBase): """ 获取tr实例 """ - module = self.__get_system_module_instance(module_id='TransmissionModule') + module = ModuleManager().get_running_module(module_id='TransmissionModule') if not module: return None transmission = getattr(module, 'transmission') diff --git a/plugins/pluginautoupgrade/__init__.py b/plugins/pluginautoupgrade/__init__.py index 5de5a10..20187c7 100644 --- a/plugins/pluginautoupgrade/__init__.py +++ b/plugins/pluginautoupgrade/__init__.py @@ -12,6 +12,7 @@ from app.helper.plugin import PluginHelper from app.log import logger from app.plugins import _PluginBase from app.scheduler import Scheduler +from app.schemas import NotificationType from app.schemas.types import SystemConfigKey @@ -23,7 +24,7 @@ class PluginAutoUpgrade(_PluginBase): # 插件图标 plugin_icon = "PluginAutoUpgrade.png" # 插件版本 - plugin_version = "1.8" + plugin_version = "1.9" # 插件作者 plugin_author = "hotlcc" # 作者主页 @@ -665,7 +666,7 @@ class PluginAutoUpgrade(_PluginBase): text = self.__build_notify_message(results=results) if not text: return - self.post_message(title=f'{self.plugin_name}任务执行结果', text=text) + self.post_message(title=f'{self.plugin_name}任务执行结果', text=text, mtype=NotificationType.Plugin) @staticmethod def __build_notify_message(results: List[Dict[str, Any]]) -> str: From 972dedef04d303f2b92778c2926dea2e21626486 Mon Sep 17 00:00:00 2001 From: Allen Date: Mon, 27 May 2024 17:00:43 +0800 Subject: [PATCH 18/27] update --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b84970e..17dbdb0 100644 --- a/package.json +++ b/package.json @@ -693,7 +693,7 @@ "author": "hotlcc", "level": 1, "history": { - "v1.9": "优化通知类型。", + "v1.9": "优化通知类型。主程序需升级至v1.9.2及以上版本,否则插件功能异常!", "v1.8": "修复重置插件后丢失配置建议的问题。", "v1.7": "修复了一些BUG。", "v1.6": "修正数字配置值提交为字符串导致的问题。", From 375ec217300417da2363112346282af799b76edc Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 28 May 2024 15:43:27 +0800 Subject: [PATCH 19/27] fix https://github.com/jxxghp/MoviePilot-Plugins/issues/339 --- package.json | 7 +++++-- plugins/libraryscraper/__init__.py | 12 ++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 17dbdb0..e68f297 100644 --- a/package.json +++ b/package.json @@ -113,10 +113,13 @@ "name": "媒体库刮削", "description": "定时对媒体库进行刮削,补齐缺失元数据和图片。", "labels": "刮削", - "version": "1.4", + "version": "1.4.1", "icon": "scraper.png", "author": "jxxghp", - "level": 1 + "level": 1, + "history": { + "v1.4.1": "修复nfo文件读取失败时任务中断问题" + } }, "TorrentRemover": { "name": "自动删种", diff --git a/plugins/libraryscraper/__init__.py b/plugins/libraryscraper/__init__.py index 01f8adc..900fef9 100644 --- a/plugins/libraryscraper/__init__.py +++ b/plugins/libraryscraper/__init__.py @@ -25,7 +25,7 @@ class LibraryScraper(_PluginBase): # 插件图标 plugin_icon = "scraper.png" # 插件版本 - plugin_version = "1.4" + plugin_version = "1.4.1" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -410,14 +410,14 @@ class LibraryScraper(_PluginBase): "uniqueid[@type='TMDB']", "tmdbid" ] - reader = NfoReader(file_path) - for xpath in xpaths: - try: + try: + reader = NfoReader(file_path) + for xpath in xpaths: tmdbid = reader.get_element_value(xpath) if tmdbid: return tmdbid - except Exception as err: - print(str(err)) + except Exception as err: + logger.warn(f"从nfo文件中获取tmdbid失败:{str(err)}") return None def stop_service(self): From 57cc748942c723ff7544b74a0587d4c694ddbadb Mon Sep 17 00:00:00 2001 From: xuzhi Date: Tue, 28 May 2024 14:25:12 +0000 Subject: [PATCH 20/27] Update cleaninvalidseed to v1.8 --- package.json | 3 +- plugins/cleaninvalidseed/__init__.py | 94 ++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 2f20c1b..91b5a0b 100644 --- a/package.json +++ b/package.json @@ -702,11 +702,12 @@ "name": "清理QB无效做种", "description": "清理已经被站点删除的种子及对应源文件,仅支持QB", "labels": "Qbittorrent", - "version": "1.7", + "version": "1.8", "icon": "clean_a.png", "author": "DzAvril", "level": 1, "history": { + "v1.8": "增加远程命令切换全量通知;修复bug", "v1.7": "修复因消息内容包含'_'导致telegram API调用失败的问题", "v1.6": "修复当种子有多个标签时,通过标签过滤不删除种子会失效的问题", "v1.5": "1. 增加通过分类、标签过滤不删除种子功能;2. 全量通知提供更多信息", diff --git a/plugins/cleaninvalidseed/__init__.py b/plugins/cleaninvalidseed/__init__.py index a748f4b..2457f4c 100644 --- a/plugins/cleaninvalidseed/__init__.py +++ b/plugins/cleaninvalidseed/__init__.py @@ -28,7 +28,7 @@ class CleanInvalidSeed(_PluginBase): # 插件图标 plugin_icon = "clean_a.png" # 插件版本 - plugin_version = "1.7" + plugin_version = "1.8" # 插件作者 plugin_author = "DzAvril" # 作者主页 @@ -94,22 +94,7 @@ class CleanInvalidSeed(_PluginBase): ) # 关闭一次性开关 self._onlyonce = False - self.update_config( - { - "onlyonce": False, - "cron": self._cron, - "enabled": self._enabled, - "notify": self._notify, - "delete_invalid_torrents": self._delete_invalid_torrents, - "delete_invalid_files": self._delete_invalid_files, - "detect_invalid_files": self._detect_invalid_files, - "notify_all": self._notify_all, - "download_dirs": self._download_dirs, - "exclude_keywords": self._exclude_keywords, - "exclude_categories": self._exclude_categories, - "exclude_labels": self._exclude_labels, - } - ) + self._update_config() # 启动任务 if self._scheduler.get_jobs(): @@ -119,6 +104,24 @@ class CleanInvalidSeed(_PluginBase): def get_state(self) -> bool: return self._enabled + def _update_config(self): + self.update_config( + { + "onlyonce": False, + "cron": self._cron, + "enabled": self._enabled, + "notify": self._notify, + "delete_invalid_torrents": self._delete_invalid_torrents, + "delete_invalid_files": self._delete_invalid_files, + "detect_invalid_files": self._detect_invalid_files, + "notify_all": self._notify_all, + "download_dirs": self._download_dirs, + "exclude_keywords": self._exclude_keywords, + "exclude_categories": self._exclude_categories, + "exclude_labels": self._exclude_labels, + } + ) + @staticmethod def get_command() -> List[Dict[str, Any]]: """ @@ -154,6 +157,13 @@ class CleanInvalidSeed(_PluginBase): "category": "QB", "data": {"action": "delete_invalid_files"}, }, + { + "cmd": "/toggle_notify_all", + "event": EventType.PluginAction, + "desc": "QB清理插件切换全量通知", + "category": "QB", + "data": {"action": "toggle_notify_all"}, + }, ] @eventmanager.register(EventType.PluginAction) @@ -189,6 +199,22 @@ class CleanInvalidSeed(_PluginBase): logger.info("收到远程命令,开始清理无效源文件") self._delete_invalid_files = True self.detect_invalid_files() + elif event_data.get("action") == "toggle_notify_all": + self._notify_all = not self._notify_all + self._update_config() + if self._notify_all: + self.post_message( + channel=event.event_data.get("channel"), + title="已开启全量通知", + userid=event.event_data.get("user"), + ) + else: + self.post_message( + channel=event.event_data.get("channel"), + title="已关闭全量通知", + userid=event.event_data.get("user"), + ) + return else: logger.error("收到未知远程命令") return @@ -256,8 +282,8 @@ class CleanInvalidSeed(_PluginBase): # tracker未工作,但暂时不能判定为失效做种,需人工判断 tracker_not_working_torrents = [] working_tracker_set = set() - exclude_categories = self._exclude_categories.split("\n") - exclude_labels = self._exclude_labels.split("\n") + exclude_categories = self._exclude_categories.split("\n") if self._exclude_categories else [] + exclude_labels = self._exclude_labels.split("\n") if self._exclude_labels else [] # 第一轮筛选出所有未工作的种子 for torrent in all_torrents: trackers = torrent.trackers @@ -442,7 +468,15 @@ class CleanInvalidSeed(_PluginBase): source_paths = [] total_size = 0 deleted_file_cnt = 0 - exclude_key_words = self._exclude_keywords.split("\n") + exclude_key_words = self._exclude_keywords.split("\n") if self._exclude_keywords else [] + if not self._download_dirs: + logger.error("未配置下载目录,无法检测未做种无效源文件") + self.post_message( + mtype=NotificationType.SiteMessage, + title=f"【检测无效源文件】", + text="未配置下载目录,无法检测未做种无效源文件", + ) + return for path in self._download_dirs.split("\n"): mp_path, qb_path = path.split(":") source_path_map[mp_path] = qb_path @@ -760,3 +794,23 @@ class CleanInvalidSeed(_PluginBase): self._scheduler = None except Exception as e: logger.error("退出插件失败:%s" % str(e)) + + +if __name__ == "__main__": + clean = CleanInvalidSeed() + config = { + "enabled": True, + "notify": True, + "download_dirs": "/sata16t/春天:/保种/春天\n/sata16t/观众:/保种/观众\n/sata16t/UB:/保种/UB\n/sata16t/听听歌:/保种/听听歌\n/ssd/Download/shualiu:/Downloads/shualiu", + "delete_invalid_torrents": False, + "delete_invalid_files": False, + "detect_invalid_files": True, + "notify_all": False, + "onlyonce": False, + "cron": "0 0 * * *", + "exclude_keywords": "ABF-075\nIPZZ-002-C_GG5\nIPZZ-061\n.!qB", + "exclude_categories": "电影", + "exclude_labels": "春天", + } + clean.init_plugin(config) + clean.clean_invalid_seed() \ No newline at end of file From e0efc1dabb15b996c91ccceb39f09f3f2bc4fb20 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 29 May 2024 14:36:28 +0800 Subject: [PATCH 21/27] add DailyWord --- package.json | 9 ++ plugins/crossseed/__init__.py | 3 +- plugins/dailyword/__init__.py | 250 +++++++++++++++++++++++++++++++ plugins/trendingshow/__init__.py | 2 +- 4 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 plugins/dailyword/__init__.py diff --git a/package.json b/package.json index fe4c4fd..47ceb39 100644 --- a/package.json +++ b/package.json @@ -764,5 +764,14 @@ "icon": "Dsphoto_A.png", "author": "jxxghp", "level": 1 + }, + "DailyWord": { + "name": "每日一言", + "description": "在仪表板中显示每日一言卡片。", + "labels": "仪表板", + "version": "1.0", + "icon": "Calibre_B.png", + "author": "jxxghp", + "level": 1 } } diff --git a/plugins/crossseed/__init__.py b/plugins/crossseed/__init__.py index 1ef301a..82fb138 100644 --- a/plugins/crossseed/__init__.py +++ b/plugins/crossseed/__init__.py @@ -294,7 +294,8 @@ class CrossSeed(_PluginBase): for site_key in self._token.strip().split("\n"): site_key_arr = re.split(r"[\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) > 1: + 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]) diff --git a/plugins/dailyword/__init__.py b/plugins/dailyword/__init__.py new file mode 100644 index 0000000..e0c9af8 --- /dev/null +++ b/plugins/dailyword/__init__.py @@ -0,0 +1,250 @@ +from typing import List, Tuple, Dict, Any, Optional + +from cachetools import TTLCache, cached + +from app.plugins import _PluginBase +from app.utils.http import RequestUtils + + +class DailyWord(_PluginBase): + # 插件名称 + plugin_name = "每日一言" + # 插件描述 + plugin_desc = "在仪表板中显示每日一言卡片。" + # 插件图标 + plugin_icon = "Calibre_B.png" + # 插件版本 + plugin_version = "1.0" + # 插件作者 + plugin_author = "jxxghp" + # 作者主页 + author_url = "https://github.com/jxxghp" + # 插件配置项ID前缀 + plugin_config_prefix = "dailyowrd_" + # 加载顺序 + plugin_order = 99 + # 可使用的用户级别 + auth_level = 1 + + _enable: bool = False + _size: str = "mini" + + def init_plugin(self, config: dict = None): + self._enable = config.get("enable") + self._size = config.get("size") + + @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]]: + return [ + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enable', + 'label': '启用插件', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'model': 'size', + 'label': '组件规格', + 'items': [ + {"title": "迷你", "value": "mini"}, + {"title": "小型", "value": "small"}, + {"title": "中型", "value": "medium"}, + {"title": "大型", "value": "large"} + ] + } + } + ] + } + ] + } + ] + } + ], { + "enable": self._enable, + "size": self._size + } + + def get_page(self) -> List[dict]: + pass + + def get_dashboard_meta(self) -> Optional[List[Dict[str, str]]]: + """ + 获取插件仪表盘元信息 + 返回示例: + [{ + "key": "dashboard1", // 仪表盘的key,在当前插件范围唯一 + "name": "仪表盘1" // 仪表盘的名称 + }, { + "key": "dashboard2", + "name": "仪表盘2" + }] + """ + return [{ + "key": "dailyword_dashboard", + "name": "每日一言" + }] + + @cached(cache=TTLCache(maxsize=1, ttl=43200)) + def __get_youngam(self) -> Optional[dict]: + """ + 获取每日一言,缓存12小时 + """ + res = RequestUtils().get_res("https://apier.youngam.cn/essay/one") + if res: + datalist = res.json().get("dataList") + return datalist[0] if datalist else {} + return {} + + def get_dashboard(self, key: str = None, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + """ + 获取插件仪表盘页面,需要返回:1、仪表板col配置字典;2、全局配置(自动刷新等);3、仪表板页面元素配置json(含数据) + 1、col配置参考: + { + "cols": 12, "md": 6 + } + 2、全局配置参考: + { + "refresh": 10 // 自动刷新时间,单位秒 + } + 3、页面配置使用Vuetify组件拼装,参考:https://vuetifyjs.com/ + """ + # 列配置 + if self._size == "mini": + cols = { + "cols": 12, + "md": 4 + } + height = 160 + elif self._size == "small": + cols = { + "cols": 12, + "md": 6 + } + height = 262 + elif self._size == "medium": + cols = { + "cols": 12, + "md": 8 + } + height = 335 + else: + cols = { + "cols": 12, + "md": 12 + } + height = 500 + # 全局配置 + attrs = { + "border": False + } + # 获取流行越势数据 + data = self.__get_youngam() + if not data: + elements = [ + { + 'component': 'VCard', + 'content': [ + { + 'component': 'VCardText', + 'props': { + 'class': 'text-center', + }, + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-h6' + }, + 'text': '无数据' + } + ] + } + ] + } + ] + else: + elements = [ + { + 'component': 'VCard', + 'props': { + 'class': 'p-0' + }, + 'content': [ + { + 'component': 'VImg', + 'props': { + 'src': data.get('src'), + 'cover': True, + 'height': height + }, + 'content': [ + { + 'component': 'VCardText', + 'props': { + 'class': 'w-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 pa-4', + }, + 'content': [ + { + 'component': 'h1', + 'props': { + 'class': 'mb-1 text-white text-shadow text-xl line-clamp-4 overflow-hidden text-ellipsis ...' + }, + 'html': data.get('text'), + }, + { + 'component': 'span', + 'props': { + 'class': 'text-right text-shadow line-clamp-2 overflow-hidden text-ellipsis ...' + }, + 'text': f"{data.get('year')}年{data.get('month')}月{data.get('day')}日", + } + ] + } + ] + } + ] + }] + + return cols, attrs, elements + + def get_state(self) -> bool: + return self._enable + + def stop_service(self): + pass diff --git a/plugins/trendingshow/__init__.py b/plugins/trendingshow/__init__.py index a7e7df4..3f594ef 100644 --- a/plugins/trendingshow/__init__.py +++ b/plugins/trendingshow/__init__.py @@ -197,7 +197,7 @@ class TrendingShow(_PluginBase): { 'component': 'VCardText', 'props': { - 'class': 'w-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-4', + 'class': 'w-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 pa-4', }, 'content': [ { From e4790ec2c6f13483a88e74d463bb9d4d8eb86096 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 29 May 2024 16:30:35 +0800 Subject: [PATCH 22/27] fix README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0a162c5..0dc5fcd 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ class EventType(Enum): SubscribeAdded = "subscribe.added" # 订阅已完成 SubscribeComplete = "subscribe.complete" + # 系统错误 + SystemError = "system.error" ``` ### 2. 如何在插件中实现远程命令响应? @@ -426,7 +428,7 @@ class EventType(Enum): - **请不要添加对黄赌毒站点的支持,否则随时封闭接口。** ### 7. 如何在插件中调用API接口? -- 目前仅在插件的数据页面支持`GET/POST`API接口调用,可调用插件自身、主程序或其它插件的API(v1.8.4+)。 +- `v1.8.4+` 在插件的数据页面支持`GET/POST`API接口调用,可调用插件自身、主程序或其它插件的API。 - 在`get_page`中定义好元素的事件,以及相应的API参数,具体可参考插件`豆瓣想看`: ```json { @@ -448,7 +450,7 @@ class EventType(Enum): ### 8. 如何将插件内容显示到仪表板? - `v1.8.7+` 支持将插件的内容显示到仪表盘,并支持定义占据的单元格大小,插件产生的仪表板仅管理员可见。 - 1. 根据插件需要展示的Widget内容规划展示内容的样式和规格,也可设计多个规格样式并提供配置项供用户选择。 -- 2. 实现 `get_dashboard_meta` 方法,定义仪表板key及名称,支持一件插件有多个仪表板: +- 2. 实现 `get_dashboard_meta` 方法,定义仪表板key及名称,支持一个插件有多个仪表板: ```python def get_dashboard_meta(self) -> Optional[List[Dict[str, str]]]: """ From e7e824b7c6ca14b58dd497ac5b8f5d9ea56befd3 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 29 May 2024 17:41:41 +0800 Subject: [PATCH 23/27] =?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/trendingshow/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/trendingshow/__init__.py b/plugins/trendingshow/__init__.py index 3f594ef..13b5eab 100644 --- a/plugins/trendingshow/__init__.py +++ b/plugins/trendingshow/__init__.py @@ -183,7 +183,7 @@ class TrendingShow(_PluginBase): 'show-arrows': 'hover', 'hide-delimiters': True, 'cycle': True, - 'interval': 5000, + 'interval': 10000, 'height': height }, 'content': [ From de1d78839d715bb53da6f80dd3a0b75e51455c40 Mon Sep 17 00:00:00 2001 From: Allen Date: Wed, 29 May 2024 18:31:19 +0800 Subject: [PATCH 24/27] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BB=AA=E8=A1=A8?= =?UTF-8?q?=E6=9D=BF=E5=AE=9E=E6=97=B6=E9=80=9F=E7=8E=87=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E5=8D=95=E7=8B=AC=E5=B1=95=E7=A4=BA?= =?UTF-8?q?qb=E5=92=8Ctr=E7=9A=84=E5=AE=9E=E6=97=B6=E9=80=9F=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- plugins/downloaderhelper/__init__.py | 559 +++++++++++++++++++++++---- plugins/downloaderhelper/module.py | 17 + 3 files changed, 509 insertions(+), 70 deletions(-) diff --git a/package.json b/package.json index 47ceb39..04a4bf6 100644 --- a/package.json +++ b/package.json @@ -640,11 +640,12 @@ "name": "下载器助手", "description": "自动做种、站点标签、自动删种。", "labels": "下载管理,仪表板", - "version": "2.5", + "version": "2.6", "icon": "DownloaderHelper.png", "author": "hotlcc", "level": 1, "history": { + "v2.6": "新增仪表板实时速率组件,支持单独展示qb和tr的实时速率(tr未测试,有问题提Issue并@hotlcc)。", "v2.5": "优化通知类型;降低认证级别要求,使MP非认证用户可用,但无法使用【站点名称优先】功能。主程序需升级至v1.9.2及以上版本,否则插件功能异常!", "v2.4": "修复tr活动种子仪表板的种子排序的bug;优化插件的消息发送。", "v2.3": "仪表板支持多个下载器活动种子组件(主程序版本需大于v1.9.1)。", diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index c1fc0b4..38bedc8 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -9,6 +9,7 @@ from urllib.parse import urlparse import pytz from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger +from cachetools import TTLCache from qbittorrentapi import TorrentDictionary, TorrentState from transmission_rpc.torrent import Torrent, Status as TorrentStatus from ruamel.yaml.comments import CommentedMap @@ -21,7 +22,7 @@ from app.log import logger from app.modules.qbittorrent.qbittorrent import Qbittorrent from app.modules.transmission.transmission import Transmission from app.plugins import _PluginBase -from app.plugins.downloaderhelper.module import TaskContext, TaskResult, Downloader, TorrentField, TorrentFieldMap, DownloaderMap +from app.plugins.downloaderhelper.module import TaskContext, TaskResult, Downloader, TorrentField, TorrentFieldMap, DownloaderMap, DownloaderTransferInfo from app.schemas import NotificationType from app.schemas.types import EventType from app.utils.string import StringUtils @@ -35,7 +36,7 @@ class DownloaderHelper(_PluginBase): # 插件图标 plugin_icon = "DownloaderHelper.png" # 插件版本 - plugin_version = "2.5" + plugin_version = "2.6" # 插件作者 plugin_author = "hotlcc" # 作者主页 @@ -57,6 +58,8 @@ class DownloaderHelper(_PluginBase): __exit_event: ThreadEvent = ThreadEvent() # 任务锁 __task_lock: RLock = RLock() + # 缓存 + __ttl_cache = TTLCache(maxsize=128, ttl=1800) # 配置相关 # 插件缺省配置 @@ -77,7 +80,8 @@ class DownloaderHelper(_PluginBase): TorrentField.TAGS.name, TorrentField.ADD_TIME.name, TorrentField.UPLOADED.name, - ] + ], + 'dashboard_speed_widget_target_downloaders': ['default'], } # 插件用户配置 __config: Dict[str, Any] = {} @@ -93,6 +97,12 @@ class DownloaderHelper(_PluginBase): __exclude_tags: Set[str] = set() # 多级根域名,用于在打标时做特殊处理 __multi_level_root_domain: List[str] = ['edu.cn', 'com.cn', 'net.cn', 'org.cn'] + # vuetifyjs mdi 图标 svg path 值 + __mdi_icon_svg_path = { + 'mdi-cloud-upload': 'M11 20H6.5q-2.28 0-3.89-1.57Q1 16.85 1 14.58q0-1.95 1.17-3.48q1.18-1.53 3.08-1.95q.63-2.3 2.5-3.72Q9.63 4 12 4q2.93 0 4.96 2.04Q19 8.07 19 11q1.73.2 2.86 1.5q1.14 1.28 1.14 3q0 1.88-1.31 3.19T18.5 20H13v-7.15l1.6 1.55L16 13l-4-4l-4 4l1.4 1.4l1.6-1.55Z', + 'mdi-download-box': 'M5 3h14a2 2 0 0 1 2 2v14c0 1.11-.89 2-2 2H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m3 14h8v-2H8zm8-7h-2.5V7h-3v3H8l4 4z', + 'mdi-content-save': 'M15 9H5V5h10m-3 14a3 3 0 0 1-3-3a3 3 0 0 1 3-3a3 3 0 0 1 3 3a3 3 0 0 1-3 3m5-16H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7z', + } def init_plugin(self, config: dict = None): """ @@ -143,7 +153,8 @@ class DownloaderHelper(_PluginBase): ) and self.__check_enable_any_task() ) - or self.__check_enable_dashboard_widget() + or self.__check_enable_dashboard_active_torrent_widget() + or self.__check_enable_dashboard_speed_widget() ) else False return state @@ -439,9 +450,23 @@ class DownloaderHelper(_PluginBase): 'content': [{ 'component': 'VSwitch', 'props': { - 'model': '_config_dashboard_dialog_closed', + 'model': '_config_dashboard_active_torrent_dialog_closed', 'label': '配置仪表板活动种子组件', - 'hint': '点击展开仪表板组件配置窗口。' + 'hint': '点击展开仪表板活动种子组件配置窗口。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 4, 'xl': 4, 'lg': 4, 'md': 4, 'sm': 6, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': '_config_dashboard_speed_dialog_closed', + 'label': '配置仪表板实时速率组件', + 'hint': '点击展开仪表板实时速率组件配置窗口。' } }] }] @@ -489,7 +514,7 @@ class DownloaderHelper(_PluginBase): }, { 'component': 'VDialog', 'props': { - 'model': '_config_dashboard_dialog_closed', + 'model': '_config_dashboard_active_torrent_dialog_closed', 'max-width': '40rem' }, 'content': [{ @@ -503,7 +528,7 @@ class DownloaderHelper(_PluginBase): 'content': [{ 'component': 'VDialogCloseBtn', 'props': { - 'model': '_config_dashboard_dialog_closed' + 'model': '_config_dashboard_active_torrent_dialog_closed' } }, { 'component': 'VRow', @@ -517,8 +542,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VSwitch', 'props': { 'model': 'enable_dashboard_widget', - 'label': '启用仪表板组件', - 'hint': '是否启用仪表板组件。' + 'label': '启用组件', + 'hint': '是否启用仪表板活动种子组件。' } }] }, { @@ -597,6 +622,83 @@ class DownloaderHelper(_PluginBase): }] }] }] + }, { + 'component': 'VDialog', + 'props': { + 'model': '_config_dashboard_speed_dialog_closed', + 'max-width': '40rem' + }, + 'content': [{ + 'component': 'VCard', + 'props': { + 'title': '配置仪表板实时速率组件', + 'style': { + 'padding': '0 20px 20px 20px' + } + }, + 'content': [{ + 'component': 'VDialogCloseBtn', + 'props': { + 'model': '_config_dashboard_speed_dialog_closed' + } + }, { + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 6, 'xl': 6, 'lg': 6, 'md': 6, 'sm': 6, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': 'enable_dashboard_speed_widget', + 'label': '启用组件', + 'hint': '是否启用仪表板实时速率组件。' + } + }] + }] + }, { + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 6, 'xl': 6, 'lg': 6, 'md': 6, 'sm': 6, 'xs': 12 + }, + 'content': [{ + 'component': 'VTextField', + 'props': { + 'model': 'dashboard_speed_widget_refresh', + 'label': '刷新间隔(秒)', + 'placeholder': '5', + 'type': 'number', + 'hint': '组件刷新时间间隔,单位为秒,缺省时不刷新。请合理配置,间隔太短可能会导致下载器假死。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 6, 'xl': 6, 'lg': 6, 'md': 6, 'sm': 6, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'dashboard_speed_widget_target_downloaders', + 'label': '目标下载器', + 'multiple': True, + 'items': [ + {'title': '系统默认下载器', 'value': 'default'}, + {'title': Downloader.QB.name_, 'value': Downloader.QB.id}, + {'title': Downloader.TR.name_, 'value': Downloader.TR.id} + ], + 'hint': '选择要展示的目标下载器。' + } + }] + }] + }] + }] }, { 'component': 'VRow', 'content': [{ @@ -668,15 +770,28 @@ class DownloaderHelper(_PluginBase): }] """ dashboard_meta = [] - target_downloader_ids = self.__get_target_downloader_ids() - for target_downloader_id in target_downloader_ids: - downloader = self.__get_downloader_enum_by_id(downloader_id=target_downloader_id) - if not downloader: - continue - dashboard_meta.append({ - "key": downloader.id, - "name": f"活动种子 #{downloader.short_name}", - }) + if not self.get_state(): + return dashboard_meta + if self.__check_enable_dashboard_active_torrent_widget(): + target_downloader_ids = self.__get_dashboard_active_torrent_widget_target_downloader_ids() + for target_downloader_id in target_downloader_ids: + downloader = self.__get_downloader_enum_by_id(downloader_id=target_downloader_id) + if not downloader: + continue + dashboard_meta.append({ + "key": downloader.id, + "name": f"活动种子 #{downloader.short_name}", + }) + if self.__check_enable_dashboard_speed_widget(): + target_downloader_ids = self.__get_dashboard_speed_widget_target_downloader_ids() + for target_downloader_id in target_downloader_ids: + downloader = self.__get_downloader_enum_by_id(downloader_id=target_downloader_id) + if not downloader: + continue + dashboard_meta.append({ + "key": f"{downloader.id}_speed", + "name": f"实时速率 #{downloader.short_name}", + }) return dashboard_meta def get_dashboard(self, key: str = None, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: @@ -696,21 +811,35 @@ class DownloaderHelper(_PluginBase): :param key: 仪表盘key,根据指定的key返回相应的仪表盘数据,缺省时返回一个固定的仪表盘数据(兼容旧版) """ - if not self.get_state() or not self.__check_enable_dashboard_widget(): + if not self.get_state(): return None - target_downloader_ids = self.__get_target_downloader_ids() + enable_dashboard_active_torrent_widget = self.__check_enable_dashboard_active_torrent_widget() + enable_dashboard_speed_widget = self.__check_enable_dashboard_speed_widget() + if not enable_dashboard_active_torrent_widget and not enable_dashboard_speed_widget: + return None + # 无key兼容历史 + dashboard_active_torrent_widget_target_downloader_ids = self.__get_dashboard_active_torrent_widget_target_downloader_ids() if not key: - if not target_downloader_ids: + if enable_dashboard_active_torrent_widget and dashboard_active_torrent_widget_target_downloader_ids: + return self.__get_dashboard_active_torrent_widget(downloader_id=dashboard_active_torrent_widget_target_downloader_ids[0]) + else: return None - return self.__get_active_torrent_dashboard(downloader_id=target_downloader_ids[0]) - if key in target_downloader_ids: - return self.__get_active_torrent_dashboard(downloader_id=key) + # 有key + dashboard_speed_widget_target_downloader_ids = self.__get_dashboard_speed_widget_target_downloader_ids() + if key == Downloader.QB.id and enable_dashboard_active_torrent_widget and Downloader.QB.id in dashboard_active_torrent_widget_target_downloader_ids: + return self.__get_dashboard_active_torrent_widget(downloader_id=Downloader.QB.id) + if key == Downloader.TR.id and enable_dashboard_active_torrent_widget and Downloader.TR.id in dashboard_active_torrent_widget_target_downloader_ids: + return self.__get_dashboard_active_torrent_widget(downloader_id=Downloader.TR.id) + if key == f"{Downloader.QB.id}_speed" and enable_dashboard_speed_widget and Downloader.QB.id in dashboard_speed_widget_target_downloader_ids: + return self.__get_dashboard_speed_widget(downloader_id=Downloader.QB.id) + if key == f"{Downloader.TR.id}_speed" and enable_dashboard_speed_widget and Downloader.TR.id in dashboard_speed_widget_target_downloader_ids: + return self.__get_dashboard_speed_widget(downloader_id=Downloader.TR.id) return None - def __get_active_torrent_dashboard(self, - downloader_id: str) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + def __get_dashboard_active_torrent_widget(self, + downloader_id: str) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: """ - 获取活动种子仪表板 + 获取仪表板活动种子组件 """ downloader = self.__get_downloader_enum_by_id(downloader_id=downloader_id) if not downloader: @@ -739,7 +868,42 @@ class DownloaderHelper(_PluginBase): attrs['refresh'] = self.__get_config_item('dashboard_widget_refresh') # 页面元素 - elements = self.__get_dashboard_elememts(downloader_id=downloader_id) + elements = self.__get_dashboard_active_torrent_widget_elememts(downloader_id=downloader_id) + + return cols, attrs, elements + + def __get_dashboard_speed_widget(self, + downloader_id: str) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + """ + 获取仪表板实时速率组件 + """ + downloader = self.__get_downloader_enum_by_id(downloader_id=downloader_id) + if not downloader: + return None + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return None + + # 列配置 + cols = { + 'cols': 12, + 'xxl': 4, + 'xl': 4, + 'lg': 4, + 'md': 4, + 'sm': 12, + 'xs': 12 + } + + # 全局配置 + attrs = { + 'title': f'实时速率 #{downloader.short_name}' + } + if self.__check_target_downloader(downloader_id=downloader_id): + attrs['refresh'] = self.__get_config_item('dashboard_speed_widget_refresh') + + # 页面元素 + elements = self.__get_dashboard_speed_widget_elememts(downloader_id=downloader_id) return cols, attrs, elements @@ -751,6 +915,7 @@ class DownloaderHelper(_PluginBase): logger.info('尝试停止插件服务...') self.__exit_event.set() self.__stop_scheduler() + self.__clear_cache() logger.info('插件服务停止完成') except Exception as e: logger.error(f"插件服务停止异常: {str(e)}", exc_info=True) @@ -855,6 +1020,20 @@ class DownloaderHelper(_PluginBase): except Exception as e: logger.error(f"插件服务调度器停止异常: {str(e)}", exc_info=True) + def __clear_cache(self): + """ + 清除缓存 + """ + try: + logger.info('尝试清除插件缓存...') + if self.__ttl_cache: + self.__ttl_cache.clear() + logger.info('插件缓存清除成功') + else: + logger.info('插件未启用缓存,无须清除') + except Exception as e: + logger.error(f"插件缓存清除异常: {str(e)}", exc_info=True) + def __fix_config(self, config: dict) -> dict: """ 修正配置 @@ -993,13 +1172,20 @@ class DownloaderHelper(_PluginBase): return True if self.__check_enable_qb_task() \ or self.__check_enable_tr_task() else False - def __check_enable_dashboard_widget(self) -> bool: + def __check_enable_dashboard_active_torrent_widget(self) -> bool: """ - 判断是否启用了仪表板组件 - :return: 是否启用了仪表板组件 + 判断是否启用了仪表板活动种子组件 + :return: 是否启用了仪表板活动种子组件 """ return True if self.__get_config_item('enable_dashboard_widget') else False + def __check_enable_dashboard_speed_widget(self) -> bool: + """ + 判断是否启用了仪表板实时速率组件 + :return: 是否启用了仪表板实时速率组件 + """ + return True if self.__get_config_item('enable_dashboard_speed_widget') else False + @classmethod def __parse_tracker_for_qbittorrent(cls, torrent: TorrentDictionary) -> Optional[str]: """ @@ -1868,13 +2054,13 @@ class DownloaderHelper(_PluginBase): result.append(field) return result - def __build_dashboard_widget_table_head_content(self, + def __build_dashboard_widget_torrent_table_head_content(self, fields: List[TorrentField] = None) -> list: """ - 构造仪表板组件表头内容 + 构造仪表板组件种子表头内容 """ if not fields: - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() if not fields: return [] return [{ @@ -1885,22 +2071,22 @@ class DownloaderHelper(_PluginBase): 'text': field.name_ } for field in fields if field] - def __build_dashboard_widget_table_head(self, + def __build_dashboard_widget_torrent_table_head(self, fields: List[TorrentField] = None) -> dict: """ - 构造仪表板组件表头 + 构造仪表板组件种子表头 """ return { 'component': 'thead', - 'content': self.__build_dashboard_widget_table_head_content(fields=fields) + 'content': self.__build_dashboard_widget_torrent_table_head_content(fields=fields) } - def __build_dashboard_widget_table_body_content(self, + def __build_dashboard_widget_torrent_table_body_content(self, data: List[List[Any]], field_count: int, downloader_id: str) -> list: """ - 构造仪表板组件表体内容 + 构造仪表板组件种子表体内容 :param downloader_id: 下载器ID :param data: 表格数据 :param field_count: 字段数量 @@ -1936,24 +2122,26 @@ class DownloaderHelper(_PluginBase): }] }] - def __build_dashboard_widget_table_body(self, + def __build_dashboard_widget_torrent_table_body(self, data: List[List[Any]], field_count: int, downloader_id: str) -> dict: """ - 构造仪表板组件表体内容 + 构造仪表板组件种子表体 """ return { 'component': 'tbody', - 'content': self.__build_dashboard_widget_table_body_content(data=data, field_count=field_count, downloader_id=downloader_id) + 'content': self.__build_dashboard_widget_torrent_table_body_content(data=data, field_count=field_count, downloader_id=downloader_id) } - def __get_target_downloader_ids(self) -> List[str]: + def __get_dashboard_widget_target_downloader_ids(self, config_key: str) -> List[str]: """ - 获取目标下载器ids + 获取仪表板组件目标下载器ids """ target_downloader_ids = [] - target_downloaders = self.__get_config_item('dashboard_widget_target_downloaders') + if not config_key: + return target_downloader_ids + target_downloaders = self.__get_config_item(config_key) if not target_downloaders: return target_downloader_ids for target_downloader in target_downloaders: @@ -1963,9 +2151,21 @@ class DownloaderHelper(_PluginBase): target_downloader_ids.append(target_downloader) return target_downloader_ids - def __get_dashboard_widget_display_fields(self) -> List[TorrentField]: + def __get_dashboard_active_torrent_widget_target_downloader_ids(self) -> List[str]: """ - 获取仪表板组件展示字段 + 获取仪表板活动种子组件目标下载器ids + """ + return self.__get_dashboard_widget_target_downloader_ids(config_key='dashboard_widget_target_downloaders') + + def __get_dashboard_speed_widget_target_downloader_ids(self) -> List[str]: + """ + 获取仪表板实时速率组件目标下载器ids + """ + return self.__get_dashboard_widget_target_downloader_ids(config_key='dashboard_speed_widget_target_downloaders') + + def __get_dashboard_active_torrent_widget_display_fields(self) -> List[TorrentField]: + """ + 获取仪表板活动种子组件展示字段 """ fields = self.__get_config_item('dashboard_widget_display_fields') return self.__ensure_torrent_fields(fields=fields) @@ -1992,28 +2192,28 @@ class DownloaderHelper(_PluginBase): else: return False - def __get_downloader_torrent_data(self, - downloader_id: str, - fields: List[TorrentField] = None): + def __get_downloader_active_torrent_data(self, + downloader_id: str, + fields: List[TorrentField] = None): """ - 获取下载器种子数据 + 获取下载器活动种子数据 """ if not downloader_id: return None # 字段 if not fields: - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() if downloader_id == Downloader.QB.id: - return self.__get_qbittorrent_torrent_data(fields=fields) + return self.__get_qbittorrent_active_torrent_data(fields=fields) elif downloader_id == Downloader.TR.id: - return self.__get_transmission_torrent_data(fields=fields) + return self.__get_transmission_active_torrent_data(fields=fields) else: return None - def __get_qbittorrent_torrent_data(self, - fields: List[TorrentField] = None): + def __get_qbittorrent_active_torrent_data(self, + fields: List[TorrentField] = None): """ - 获取qb种子数据 + 获取qb活动种子数据 """ if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') @@ -2023,7 +2223,7 @@ class DownloaderHelper(_PluginBase): return None # 字段 if not fields: - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() # 活动种子 torrents, error = qbittorrent.get_torrents(status=['active']) if error: @@ -2153,10 +2353,10 @@ class DownloaderHelper(_PluginBase): arguments.append('uploadLimited') return list(set(arguments)) - def __get_transmission_torrent_data(self, - fields: List[TorrentField] = None): + def __get_transmission_active_torrent_data(self, + fields: List[TorrentField] = None): """ - 获取tr种子数据 + 获取tr活动种子数据 """ if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') @@ -2166,7 +2366,7 @@ class DownloaderHelper(_PluginBase): return None # 字段 if not fields: - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() torrents, _ = transmission.trc.get_recently_active_torrents(arguments=self.__build_transmission_field_arguments(fields=fields)) if not torrents: return None @@ -2282,18 +2482,18 @@ class DownloaderHelper(_PluginBase): logger.error(f'从tr种子中提取值异常: {str(e)}, torrent = {str(torrent.fields)}', exc_info=True) return None - def __get_dashboard_elememts(self, downloader_id: str) -> list: + def __get_dashboard_active_torrent_widget_elememts(self, downloader_id: str) -> list: """ - 获取仪表板元素 + 获取仪表板活动种子组件元素 """ if not downloader_id: return None if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') return None - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() field_count=len(fields) - data = self.__get_downloader_torrent_data(downloader_id=downloader_id, fields=fields) + data = self.__get_downloader_active_torrent_data(downloader_id=downloader_id, fields=fields) if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') return None @@ -2308,11 +2508,232 @@ class DownloaderHelper(_PluginBase): } }, 'content': [ - self.__build_dashboard_widget_table_head(fields=fields), - self.__build_dashboard_widget_table_body(data=data, field_count=field_count, downloader_id=downloader_id) + self.__build_dashboard_widget_torrent_table_head(fields=fields), + self.__build_dashboard_widget_torrent_table_body(data=data, field_count=field_count, downloader_id=downloader_id) ] }] + def __get_downloader_transfer_info(self, + downloader_id: str) -> DownloaderTransferInfo: + """ + 获取下载器传输信息 + """ + if downloader_id == Downloader.QB.id: + return self.__get_qbittorrent_transfer_info() + elif downloader_id == Downloader.TR.id: + return self.__get_transmission_transfer_info() + else: + return DownloaderTransferInfo() + + def __get_qbittorrent_transfer_info(self) -> DownloaderTransferInfo: + """ + 获取qb下载器传输信息 + """ + result = DownloaderTransferInfo() + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return result + qbittorrent = self.__get_qbittorrent() + if not qbittorrent: + return result + info = qbittorrent.transfer_info() + if info: + result.download_speed = f'{StringUtils.str_filesize(info.get("dl_info_speed"))}/s' + result.upload_speed = f'{StringUtils.str_filesize(info.get("up_info_speed"))}/s' + result.download_size = StringUtils.str_filesize(info.get("dl_info_data")) + result.upload_size = StringUtils.str_filesize(info.get("up_info_data")) + maindata = self.__get_qbittorrent_maindata() + if maindata: + server_state = maindata.get("server_state") + if server_state: + result.free_space = StringUtils.str_filesize(server_state.get("free_space_on_disk")) + return result + + def __get_qbittorrent_maindata(self): + """ + 获取qb的maindata + """ + cache_key = "qbittorrent_maindata" + maindata = self.__ttl_cache.get(cache_key) + if not maindata: + qbittorrent = self.__get_qbittorrent() + if qbittorrent: + maindata = qbittorrent.qbc.sync_maindata() + self.__ttl_cache[cache_key] = maindata + return maindata + + def __get_transmission_transfer_info(self) -> DownloaderTransferInfo: + """ + 获取qb下载器传输信息 + """ + result = DownloaderTransferInfo() + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return result + transmission = self.__get_transmission() + if not transmission: + return result + info = transmission.transfer_info() + if info: + result.download_speed = f"{StringUtils.str_filesize(info.download_speed)}/s" + result.upload_speed = f"{StringUtils.str_filesize(info.upload_speed,)}/s" + result.download_size = StringUtils.str_filesize(info.current_stats.downloaded_bytes) + result.upload_size = StringUtils.str_filesize(info.current_stats.uploaded_bytes) + session = self.__get_transmission_session() + if session: + result.free_space = StringUtils.str_filesize(session.download_dir_free_space) + return result + + def __get_transmission_session(self): + """ + 获取tr的session + """ + cache_key = "transmission_session" + session = self.__ttl_cache.get(cache_key) + if not session: + transmission = self.__get_transmission() + if transmission: + session = transmission.get_session() + self.__ttl_cache[cache_key] = session + return session + + def __build_mdi_icon_svg_elememt(self, mdi_icon: str) -> dict: + """ + 构造 svg mdi 图标元素 + """ + if not mdi_icon: + return None + path = self.__mdi_icon_svg_path.get(mdi_icon) + if not path: + return None + return { + 'component': 'svg', + 'props': { + 'class': 'v-icon notranslate v-theme--light v-icon--size-default iconify iconify--mdi', + 'rounded': True, + 'width': '1em', + 'height': '1em', + 'viewBox': '0 0 24 24', + 'style': { + 'top': '-1px' + } + }, + 'content': [{ + 'component': 'path', + 'props': { + 'fill': 'currentColor', + 'd': path + } + }] + } + + def __build_dashboard_speed_widget_list_item_element(self, mdi_icon: str, label: str, value: str) -> dict: + """ + 构造仪表板实时速率组件列表item元素 + """ + if not mdi_icon or not label or not value: + return None + return { + 'component': 'div', + 'props': { + 'style': { + 'display': 'grid', + 'grid-template-areas': '"prepend content append"', + 'grid-template-columns': 'max-content 1fr auto', + 'padding-bottom': '16px' + } + }, + 'content': [{ + 'component': 'div', + 'props': { + 'style': { + 'grid-area': 'prepend', + 'height': '21px', + 'color': '#6a6670' + } + }, + 'content': [self.__build_mdi_icon_svg_elememt(mdi_icon=mdi_icon)] + }, { + 'component': 'div', + 'props': { + 'style': { + 'grid-area': 'content', + 'margin-left': '15px' + } + }, + 'content': [{ + 'component': 'h6', + 'props': { + 'class': 'text-sm font-weight-medium mb-1' + }, + 'text': label + }] + }, { + 'component': 'div', + 'props': { + 'style': { + 'grid-area': 'append' + } + }, + 'content': [{ + 'component': 'h6', + 'props': { + 'class': 'text-sm font-weight-medium mb-2' + }, + 'text': value + }] + }] + } + + def __get_dashboard_speed_widget_elememts(self, downloader_id: str) -> list: + """ + 获取仪表板实时速率组件元素 + """ + if not downloader_id: + return None + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return None + data = self.__get_downloader_transfer_info(downloader_id=downloader_id) + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return None + list_items = [ + self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-cloud-upload', label='总上传量', value=data.upload_size), + self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-download-box', label='总下载量', value=data.download_size), + self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-content-save', label='磁盘剩余空间', value=data.free_space), + ] + return [{ + 'component': 'div', + 'props': { + 'style': { + 'padding': '16px 0 20px 0' + } + }, + 'content': [{ + 'component': 'div', + 'content': [{ + 'component': 'p', + 'props': { + 'class': 'text-h5 me-2' + }, + 'text': f'↑{data.upload_speed}' + }, { + 'component': 'p', + 'props': { + 'class': 'text-h4 me-2' + }, + 'text': f'↓{data.download_speed}' + }] + }, { + 'component': 'div', + 'props': { + 'class': 'card-list mt-9' + }, + 'content': list_items + }] + }] + @eventmanager.register(EventType.DownloadAdded) def listen_download_added_event(self, event: Event = None): """ diff --git a/plugins/downloaderhelper/module.py b/plugins/downloaderhelper/module.py index 72dbc4a..cbabf2d 100644 --- a/plugins/downloaderhelper/module.py +++ b/plugins/downloaderhelper/module.py @@ -308,3 +308,20 @@ class TorrentField(Enum): # TorrentField 映射 TorrentFieldMap = dict((field.name, field) for field in TorrentField) + + +class DownloaderTransferInfo(): + """ + 下载器传输信息 + """ + + # 下载速度 + download_speed: Optional[str] = '0.00B/s' + # 上传速度 + upload_speed: Optional[str] = '0.00B/s' + # 下载量 + download_size: Optional[str] = '0.00B' + # 上传量 + upload_size: Optional[str] = '0.00B' + # 剩余空间 + free_space: Optional[str] = '0.00B' From 29db61489d8abb6fd55c61c41053e397d7f0b4d2 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 29 May 2024 19:51:40 +0800 Subject: [PATCH 25/27] fix icon --- icons/TrendingShow.jpg | Bin 0 -> 124051 bytes package.json | 2 +- plugins/trendingshow/__init__.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 icons/TrendingShow.jpg diff --git a/icons/TrendingShow.jpg b/icons/TrendingShow.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5f58e0bad9f19c7a161e3aa87c7b505142ef9492 GIT binary patch literal 124051 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 Q>GWml%oRPpC6^!n*|Viu6aWAK literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 04a4bf6..90f8255 100644 --- a/package.json +++ b/package.json @@ -762,7 +762,7 @@ "description": "在仪表板中显示流行趋势海报轮播图。", "labels": "仪表板", "version": "1.0", - "icon": "Dsphoto_A.png", + "icon": "TrendingShow.png", "author": "jxxghp", "level": 1 }, diff --git a/plugins/trendingshow/__init__.py b/plugins/trendingshow/__init__.py index 13b5eab..fb33c15 100644 --- a/plugins/trendingshow/__init__.py +++ b/plugins/trendingshow/__init__.py @@ -10,7 +10,7 @@ class TrendingShow(_PluginBase): # 插件描述 plugin_desc = "在仪表板中显示流行趋势海报轮播图。" # 插件图标 - plugin_icon = "Dsphoto_A.png" + plugin_icon = "TrendingShow.png" # 插件版本 plugin_version = "1.0" # 插件作者 From 831adaf94ae3e616a938fbf3955fe53374f4b88c Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 29 May 2024 20:36:55 +0800 Subject: [PATCH 26/27] fix icon --- package.json | 2 +- plugins/trendingshow/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 90f8255..e4d2d5c 100644 --- a/package.json +++ b/package.json @@ -762,7 +762,7 @@ "description": "在仪表板中显示流行趋势海报轮播图。", "labels": "仪表板", "version": "1.0", - "icon": "TrendingShow.png", + "icon": "TrendingShow.jpg", "author": "jxxghp", "level": 1 }, diff --git a/plugins/trendingshow/__init__.py b/plugins/trendingshow/__init__.py index fb33c15..b443d8b 100644 --- a/plugins/trendingshow/__init__.py +++ b/plugins/trendingshow/__init__.py @@ -10,7 +10,7 @@ class TrendingShow(_PluginBase): # 插件描述 plugin_desc = "在仪表板中显示流行趋势海报轮播图。" # 插件图标 - plugin_icon = "TrendingShow.png" + plugin_icon = "TrendingShow.jpg" # 插件版本 plugin_version = "1.0" # 插件作者 From c13f8a67497f59d1d6e96d59ceaca5a8abe1dc33 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 29 May 2024 22:11:07 +0800 Subject: [PATCH 27/27] fix --- package.json | 2 +- plugins/dailyword/__init__.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index e4d2d5c..a8370c7 100644 --- a/package.json +++ b/package.json @@ -770,7 +770,7 @@ "name": "每日一言", "description": "在仪表板中显示每日一言卡片。", "labels": "仪表板", - "version": "1.0", + "version": "1.1", "icon": "Calibre_B.png", "author": "jxxghp", "level": 1 diff --git a/plugins/dailyword/__init__.py b/plugins/dailyword/__init__.py index e0c9af8..a64bc4e 100644 --- a/plugins/dailyword/__init__.py +++ b/plugins/dailyword/__init__.py @@ -1,7 +1,7 @@ +from datetime import datetime +from functools import lru_cache from typing import List, Tuple, Dict, Any, Optional -from cachetools import TTLCache, cached - from app.plugins import _PluginBase from app.utils.http import RequestUtils @@ -14,7 +14,7 @@ class DailyWord(_PluginBase): # 插件图标 plugin_icon = "Calibre_B.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "jxxghp" # 作者主页 @@ -120,8 +120,8 @@ class DailyWord(_PluginBase): "name": "每日一言" }] - @cached(cache=TTLCache(maxsize=1, ttl=43200)) - def __get_youngam(self) -> Optional[dict]: + @lru_cache(maxsize=1) + def __get_youngam(self, **kwargs) -> Optional[dict]: """ 获取每日一言,缓存12小时 """ @@ -174,7 +174,7 @@ class DailyWord(_PluginBase): "border": False } # 获取流行越势数据 - data = self.__get_youngam() + data = self.__get_youngam(today=datetime.now().strftime("%Y-%m-%d")) if not data: elements = [ {