From 80b5f64478d9c83614e06ffdf3bdd904d8d0c57f Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 3 Aug 2023 16:28:39 +0800 Subject: [PATCH 1/5] fix ChineseSubFinder --- app/modules/filetransfer/__init__.py | 28 ++-- app/plugins/chinesesubfinder/__init__.py | 175 ++++++++++++++++++----- app/schemas/transfer.py | 4 + 3 files changed, 155 insertions(+), 52 deletions(-) diff --git a/app/modules/filetransfer/__init__.py b/app/modules/filetransfer/__init__.py index 8443c395..1be36df1 100644 --- a/app/modules/filetransfer/__init__.py +++ b/app/modules/filetransfer/__init__.py @@ -51,14 +51,16 @@ class FileTransferModule(_ModuleBase): if isinstance(result, str): return TransferInfo(message=result) # 解包结果 - target_path, file_count, file_size, fail_list, msg = result + is_bluray, target_path, file_list, file_size, fail_list, msg = result # 返回 return TransferInfo(path=path, target_path=target_path, message=msg, - file_count=file_count, + file_count=len(file_list), total_size=file_size, - fail_list=fail_list) + fail_list=fail_list, + is_bluray=is_bluray, + file_list=file_list) @staticmethod def __transfer_command(file_item: Path, target_file: Path, rmt_mode) -> int: @@ -333,14 +335,14 @@ class FileTransferModule(_ModuleBase): mediainfo: MediaInfo, rmt_mode: str = None, target_dir: Path = None - ) -> Union[str, Tuple[Path, int, int, List[Path], str]]: + ) -> Union[str, Tuple[bool, Path, list, int, List[Path], str]]: """ 识别并转移一个文件、多个文件或者目录 :param in_path: 转移的路径,可能是一个文件也可以是一个目录 :param target_dir: 目的文件夹,非空的转移到该文件夹,为空时则按类型转移到配置文件中的媒体库文件夹 :param rmt_mode: 文件转移方式 :param mediainfo: 媒体信息 - :return: 目的路径、处理文件数、总大小、失败文件列表、错误信息 + :return: 是否蓝光原盘、目的路径、处理文件清单、总大小、失败文件列表、错误信息 """ # 检查目录路径 if not in_path.exists(): @@ -359,8 +361,8 @@ class FileTransferModule(_ModuleBase): # 总大小 total_filesize = 0 - # 处理文件数 - total_num = 0 + # 处理文件清单 + file_list = [] # 失败文件清单 fail_list = [] @@ -387,12 +389,10 @@ class FileTransferModule(_ModuleBase): if retcode != 0: return f"{retcode},蓝光原盘转移失败" else: - # 计算文件数 - total_num += 1 # 计算大小 total_filesize += in_path.stat().st_size # 返回转移后的路径 - return new_path, total_num, total_filesize, [], "" + return bluray_flag, new_path, [], total_filesize, [], "" else: # 获取文件清单 transfer_files: List[Path] = SystemUtils.list_files_with_extensions(in_path, settings.RMT_MEDIAEXT) @@ -455,7 +455,7 @@ class FileTransferModule(_ModuleBase): fail_list.append(transfer_file) continue # 计算文件数 - total_num += 1 + file_list.append(str(new_file)) # 计算大小 total_filesize += new_file.stat().st_size except Exception as err: @@ -463,12 +463,12 @@ class FileTransferModule(_ModuleBase): logger.error(f"{transfer_file}转移失败:{err}") fail_list.append(transfer_file) - if total_num == 0: + if not file_list: # 没有成功的 return "\n".join(err_msgs) - # 新路径、处理文件数、总大小、失败文件列表、错误信息 - return new_path, total_num, total_filesize, fail_list, "\n".join(err_msgs) + # 蓝光原盘、新路径、处理文件清单、总大小、失败文件列表、错误信息 + return bluray_flag, new_path, file_list, total_filesize, fail_list, "\n".join(err_msgs) @staticmethod def __get_naming_dict(meta: MetaBase, mediainfo: MediaInfo, file_ext: str = None) -> dict: diff --git a/app/plugins/chinesesubfinder/__init__.py b/app/plugins/chinesesubfinder/__init__.py index ff744a45..c183a299 100644 --- a/app/plugins/chinesesubfinder/__init__.py +++ b/app/plugins/chinesesubfinder/__init__.py @@ -3,9 +3,11 @@ from pathlib import Path from typing import List, Tuple, Dict, Any from app.core.config import settings +from app.core.context import MediaInfo from app.core.event import eventmanager from app.log import logger from app.plugins import _PluginBase +from app.schemas import TransferInfo from app.schemas.types import EventType, MediaType from app.utils.http import RequestUtils @@ -14,7 +16,7 @@ class ChineseSubFinder(_PluginBase): # 插件名称 plugin_name = "ChineseSubFinder" # 插件描述 - plugin_desc = "通知ChineseSubFinder下载字幕。" + plugin_desc = "整理入库时通知ChineseSubFinder下载字幕。" # 插件图标 plugin_icon = "chinesesubfinder.png" # 主题色 @@ -34,20 +36,16 @@ class ChineseSubFinder(_PluginBase): # 私有属性 _save_tmp_path = None - _enable = False + _enabled = False _host = None _api_key = None _remote_path = None _local_path = None - _remote_path2 = None - _local_path2 = None - _remote_path3 = None - _local_path3 = None def init_plugin(self, config: dict = None): self._save_tmp_path = settings.TEMP_PATH if config: - self._enable = config.get("enable") + self._enabled = config.get("enabled") self._api_key = config.get("api_key") self._host = config.get('host') if self._host: @@ -57,10 +55,6 @@ class ChineseSubFinder(_PluginBase): self._host = self._host + "/" self._local_path = config.get("local_path") self._remote_path = config.get("remote_path") - self._local_path2 = config.get("local_path2") - self._remote_path2 = config.get("remote_path2") - self._local_path3 = config.get("local_path3") - self._remote_path3 = config.get("remote_path3") @staticmethod def get_command() -> List[Dict[str, Any]]: @@ -70,7 +64,114 @@ class ChineseSubFinder(_PluginBase): pass def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: - pass + return [ + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enabled', + 'label': '启用插件', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'host', + 'label': '服务器' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'api_key', + 'label': 'API密钥' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'local_path', + 'label': '本地路径' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'remote_path', + 'label': '远端路径' + } + } + ] + } + ] + } + ] + } + ], { + "enabled": False, + "host": "", + "api_key": "", + "local_path": "", + "remote_path": "" + } def get_page(self) -> List[dict]: pass @@ -88,38 +189,36 @@ class ChineseSubFinder(_PluginBase): item = event.event_data if not item: return - # FIXME + # 请求地址 req_url = "%sapi/v1/add-job" % self._host - item_media = item.get("media_info") - item_type = item_media.get("type") - item_bluray = item.get("bluray") - item_file = item.get("file") - item_file_ext = item.get("file_ext") + # 媒体信息 + item_media: MediaInfo = item.get("mediainfo") + # 转移信息 + item_transfer: TransferInfo = item.get("transferinfo") + # 类型 + item_type = item_media.type + # 目的路径 + item_dest: Path = item_transfer.target_path + # 是否蓝光原盘 + item_bluray = item_transfer.is_bluray + # 文件清单 + item_file_list = item_transfer.file_list if item_bluray: - file_path = "%s.mp4" % item_file - else: - if Path(item_file).suffix != item_file_ext: - file_path = "%s%s" % (item_file, item_file_ext) - else: - file_path = item_file + # 蓝光原盘虚拟个文件 + item_file_list = ["%s.mp4" % item_dest / item_dest.name] - # 路径替换 - if self._local_path and self._remote_path and file_path.startswith(self._local_path): - file_path = file_path.replace(self._local_path, self._remote_path).replace('\\', '/') + for file_path in item_file_list: + # 路径替换 + if self._local_path and self._remote_path and file_path.startswith(self._local_path): + file_path = file_path.replace(self._local_path, self._remote_path).replace('\\', '/') - if self._local_path2 and self._remote_path2 and file_path.startswith(self._local_path2): - file_path = file_path.replace(self._local_path2, self._remote_path2).replace('\\', '/') - - if self._local_path3 and self._remote_path3 and file_path.startswith(self._local_path3): - file_path = file_path.replace(self._local_path3, self._remote_path3).replace('\\', '/') - - # 调用CSF下载字幕 - self.__request_csf(req_url=req_url, - file_path=file_path, - item_type=0 if item_type == MediaType.MOVIE.value else 1, - item_bluray=item_bluray) + # 调用CSF下载字幕 + self.__request_csf(req_url=req_url, + file_path=file_path, + item_type=0 if item_type == MediaType.MOVIE.value else 1, + item_bluray=item_bluray) @lru_cache(maxsize=128) def __request_csf(self, req_url, file_path, item_type, item_bluray): diff --git a/app/schemas/transfer.py b/app/schemas/transfer.py index 5048b941..7f95b3de 100644 --- a/app/schemas/transfer.py +++ b/app/schemas/transfer.py @@ -39,8 +39,12 @@ class TransferInfo(BaseModel): path: Optional[Path] = None # 转移后路径 target_path: Optional[Path] = None + # 是否蓝光原盘 + is_bluray: Optional[bool] = False # 处理文件数 file_count: Optional[int] = 0 + # 处理文件清单 + file_list: Optional[list] = [] # 总文件大小 total_size: Optional[float] = 0 # 失败清单 From 723565ecea5a2bf3d63ef23bc4e7fec662bacba4 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 3 Aug 2023 17:05:47 +0800 Subject: [PATCH 2/5] fix CustomHosts --- app/plugins/customhosts/__init__.py | 97 +++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/app/plugins/customhosts/__init__.py b/app/plugins/customhosts/__init__.py index f6a351f4..68edc029 100644 --- a/app/plugins/customhosts/__init__.py +++ b/app/plugins/customhosts/__init__.py @@ -1,14 +1,12 @@ from typing import List, Tuple, Dict, Any -from app.core.event import eventmanager +from python_hosts import Hosts, HostsEntry + from app.log import logger from app.plugins import _PluginBase -from app.schemas.types import EventType from app.utils.ip import IpUtils from app.utils.system import SystemUtils -from python_hosts import Hosts, HostsEntry - class CustomHosts(_PluginBase): # 插件名称 @@ -34,16 +32,16 @@ class CustomHosts(_PluginBase): # 私有属性 _hosts = [] - _enable = False + _enabled = False def init_plugin(self, config: dict = None): # 读取配置 if config: - self._enable = config.get("enable") + self._enabled = config.get("enabled") self._hosts = config.get("hosts") if isinstance(self._hosts, str): self._hosts = str(self._hosts).split('\n') - if self._enable and self._hosts: + if self._enabled and self._hosts: # 排除空的host new_hosts = [] for host in self._hosts: @@ -53,13 +51,13 @@ class CustomHosts(_PluginBase): # 添加到系统 error_flag, error_hosts = self.__add_hosts_to_system(self._hosts) - self._enable = self._enable and not error_flag + self._enabled = self._enabled and not error_flag # 更新错误Hosts self.update_config({ "hosts": self._hosts, "err_hosts": error_hosts, - "enable": self._enable + "enable": self._enabled }) @staticmethod @@ -70,7 +68,79 @@ class CustomHosts(_PluginBase): pass def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: - pass + return [ + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enabled', + 'label': '启用插件', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VTextarea', + 'props': { + 'model': 'hosts', + 'label': '系统hosts' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VTextarea', + 'props': { + 'model': 'err_hosts', + 'readonly': True, + 'label': '错误记录' + } + } + ] + } + ] + } + ] + } + ], { + "enabled": False, + "hosts": "", + "err_hosts": "" + } def get_page(self) -> List[dict]: pass @@ -118,6 +188,8 @@ class CustomHosts(_PluginBase): except Exception as err: err_hosts.append(host + "\n") logger.error(f"{host} 格式转换错误:{str(err)}") + # 推送实时消息 + self.systemmessage.put(f"{host} 格式转换错误:{str(err)}") # 写入系统hosts if new_entrys: @@ -131,11 +203,10 @@ class CustomHosts(_PluginBase): except Exception as err: err_flag = True logger.error(f"更新系统hosts文件失败:{str(err) or '请检查权限'}") + # 推送实时消息 + self.systemmessage.put(f"更新系统hosts文件失败:{str(err) or '请检查权限'}") return err_flag, err_hosts - def get_state(self): - return self._enable and self._hosts and self._hosts[0] - def stop_service(self): """ 退出插件 From 51016d636bfd1a67cfeef86e613fb68884b5112c Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 3 Aug 2023 17:46:57 +0800 Subject: [PATCH 3/5] fix DoubanRank --- app/plugins/doubanrank/__init__.py | 330 ++++++++++++++++++++++++++--- app/plugins/doubansync/__init__.py | 37 ++-- 2 files changed, 317 insertions(+), 50 deletions(-) diff --git a/app/plugins/doubanrank/__init__.py b/app/plugins/doubanrank/__init__.py index 47403d46..31f9d201 100644 --- a/app/plugins/doubanrank/__init__.py +++ b/app/plugins/doubanrank/__init__.py @@ -1,12 +1,17 @@ +import datetime import re import xml.dom.minidom from threading import Event -from typing import Tuple, List, Dict, Any +from typing import Tuple, List, Dict, Any, Optional from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger +from app.chain.download import DownloadChain +from app.chain.subscribe import SubscribeChain from app.core.config import settings +from app.core.context import MediaInfo +from app.core.metainfo import MetaInfo from app.log import logger from app.plugins import _PluginBase from app.utils.dom import DomUtils @@ -14,7 +19,6 @@ from app.utils.http import RequestUtils class DoubanRank(_PluginBase): - # 插件名称 plugin_name = "豆瓣榜单订阅" # 插件描述 @@ -39,10 +43,9 @@ class DoubanRank(_PluginBase): # 退出事件 _event = Event() # 私有属性 - mediaserver = None - subscribe = None - rsshelper = None - media = None + downloadchain: DownloadChain = None + subscribechain: SubscribeChain = None + _scheduler = None _douban_address = { 'movie-ustop': 'https://rsshub.app/douban/movie/ustop', 'movie-weekly': 'https://rsshub.app/douban/movie/weekly', @@ -52,16 +55,18 @@ class DoubanRank(_PluginBase): 'tv-hot': 'https://rsshub.app/douban/movie/weekly/tv_hot', 'movie-top250': 'https://rsshub.app/douban/movie/weekly/movie_top250', } - _enable = False + _enabled = False _cron = "" _rss_addrs = [] _ranks = [] _vote = 0 - _scheduler = None - + def init_plugin(self, config: dict = None): + self.downloadchain = DownloadChain() + self.subscribechain = SubscribeChain() + if config: - self._enable = config.get("enable") + self._enabled = config.get("enabled") self._cron = config.get("cron") self._vote = float(config.get("vote")) if config.get("vote") else 0 rss_addrs = config.get("rss_addrs") @@ -78,12 +83,19 @@ class DoubanRank(_PluginBase): self.stop_service() # 启动服务 - if self._enable: + if self._enabled: self._scheduler = BackgroundScheduler(timezone=settings.TZ) if self._cron: logger.info(f"豆瓣榜单订阅服务启动,周期:{self._cron}") - self._scheduler.add_job(self.__refresh_rss, - CronTrigger.from_crontab(self._cron)) + try: + self._scheduler.add_job(self.__refresh_rss, + CronTrigger.from_crontab(self._cron)) + except Exception as e: + logger.error(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}") + self.systemmessage.put(f"豆瓣榜单订阅服务启动失败,错误信息:{str(e)}") + else: + self._scheduler.add_job(self.__refresh_rss, CronTrigger.from_crontab("0 8 * * *")) + logger.info("豆瓣榜单订阅服务启动,周期:每天 08:00") if self._scheduler.get_jobs(): # 启动服务 @@ -98,13 +110,227 @@ class DoubanRank(_PluginBase): pass def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: - pass + return [ + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enabled', + 'label': '启用插件', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'cron', + 'label': '执行周期', + 'placeholder': '5位cron表达式,留空自动' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'vote', + 'label': '评分', + 'placeholder': '评分大于等于该值才订阅' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'chips': True, + 'multiple': True, + 'model': 'ranks', + 'label': '热门榜单', + 'items': [ + {'title': '电影北美票房榜', 'value': 'movie-ustop'}, + {'title': '一周口碑电影榜', 'value': 'movie-weekly'}, + {'title': '实时热门电影', 'value': 'movie-real-time'}, + {'title': '热门综艺', 'value': 'show-domestic'}, + {'title': '热门电影', 'value': 'movie-hot-gaia'}, + {'title': '热门电视剧', 'value': 'tv-hot'}, + {'title': '电影TOP10', 'value': 'movie-top250'}, + ] + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'content': [ + { + 'component': 'VTextarea', + 'props': { + 'model': 'rss_addrs', + 'label': '自定义榜单地址', + 'placeholder': '每行一个地址,如:https://rsshub.app/douban/movie/ustop' + } + } + ] + } + ] + } + ] + } + ], { + "enabled": False, + "cron": "", + "vote": "", + "ranks": [], + "rss_addrs": "", + } def get_page(self) -> List[dict]: """ 拼装插件详情页面,需要返回页面配置,同时附带数据 """ - pass + # 查询历史记录 + historys = self.get_data('history') + if not historys: + return [ + { + 'component': 'div', + 'text': '暂无数据', + 'props': { + 'class': 'text-center', + } + } + ] + # 数据按时间降序排序 + historys = sorted(historys, key=lambda x: x.get('time'), reverse=True) + # 拼装页面 + contents = [] + for history in historys: + title = history.get("title") + poster = history.get("poster") + mtype = history.get("type") + time_str = history.get("time") + doubanid = history.get("doubanid") + contents.append( + { + 'component': 'VCard', + 'content': [ + { + 'component': 'div', + 'props': { + 'class': 'd-flex justify-space-start flex-nowrap flex-row', + }, + 'content': [ + { + 'component': 'div', + 'content': [ + { + 'component': 'VImg', + 'props': { + 'src': poster, + 'height': 120, + 'width': 80, + 'aspect-ratio': '2/3', + 'class': 'object-cover shadow ring-gray-500', + 'cover': True + } + } + ] + }, + { + 'component': 'div', + 'content': [ + { + 'component': 'VCardSubtitle', + 'props': { + 'class': 'pa-2 font-bold break-words whitespace-break-spaces' + }, + 'content': [ + { + 'component': 'a', + 'props': { + 'href': f"https://movie.douban.com/subject/{doubanid}", + 'target': '_blank' + }, + 'text': title + } + ] + }, + { + 'component': 'VCardText', + 'props': { + 'class': 'pa-0 px-2' + }, + 'text': f'类型:{mtype}' + }, + { + 'component': 'VCardText', + 'props': { + 'class': 'pa-0 px-2' + }, + 'text': f'时间:{time_str}' + } + ] + } + ] + } + ] + } + ) + + return [ + { + 'component': 'div', + 'props': { + 'class': 'grid gap-3 grid-info-card', + }, + 'content': contents + } + ] def stop_service(self): """ @@ -125,13 +351,17 @@ class DoubanRank(_PluginBase): """ 刷新RSS """ - logger.info(f"开始刷新RSS ...") + logger.info(f"开始刷新豆瓣榜单 ...") addr_list = self._rss_addrs + [self._douban_address.get(rank) for rank in self._ranks] if not addr_list: - logger.info(f"未设置RSS地址") + logger.info(f"未设置榜单RSS地址") return else: - logger.info(f"共 {len(addr_list)} 个RSS地址需要刷新") + logger.info(f"共 {len(addr_list)} 个榜单RSS地址需要刷新") + + # 读取历史记录 + history: List[dict] = self.get_data('history') or [] + for addr in addr_list: if not addr: continue @@ -150,18 +380,64 @@ class DoubanRank(_PluginBase): title = rss_info.get('title') douban_id = rss_info.get('doubanid') - mtype = rss_info.get('type') unique_flag = f"doubanrank: {title} (DB:{douban_id})" - # TODO 检查是否已处理过 - # TODO 识别媒体信息 - # TODO 检查媒体服务器是否存在 - # TODO 检查是否已订阅过 - # TODO 添加处理历史 - # TODO 添加订阅 - # TODO 发送通知 - # TODO 更新历史记录 + # 检查是否已处理过 + if unique_flag in [h.get("unique") for h in history]: + continue + # 识别媒体信息 + if douban_id: + # 根据豆瓣ID获取豆瓣数据 + doubaninfo: Optional[dict] = self.chain.douban_info(doubanid=douban_id) + if not doubaninfo: + logger.warn(f'未获取到豆瓣信息,标题:{title},豆瓣ID:{douban_id}') + continue + logger.info(f'获取到豆瓣信息,标题:{title},豆瓣ID:{douban_id}') + # 识别 + title = doubaninfo.get("title") + meta = MetaInfo(doubaninfo.get("original_title") or title) + if doubaninfo.get("year"): + meta.year = doubaninfo.get("year") + else: + meta = MetaInfo(title) + # 匹配媒体信息 + mediainfo: MediaInfo = self.chain.recognize_media(meta=meta) + if not mediainfo: + logger.warn(f'未识别到媒体信息,标题:{title},豆瓣ID:{douban_id}') + continue + # 查询缺失的媒体信息 + exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) + if exist_flag: + logger.info(f'{mediainfo.title_year} 媒体库中已存在') + action = "exist" + else: + # 添加订阅 + self.subscribechain.add(title=mediainfo.title, + year=mediainfo.year, + mtype=mediainfo.type, + tmdbid=mediainfo.tmdb_id, + season=meta.begin_season, + exist_ok=True, + username="豆瓣榜单") + action = "subscribe" + # 存储历史记录 + history.append({ + "action": action, + "title": title, + "type": mediainfo.type.value, + "year": mediainfo.year, + "poster": mediainfo.get_poster_image(), + "overview": mediainfo.overview, + "tmdbid": mediainfo.tmdb_id, + "doubanid": douban_id, + "time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "unique": unique_flag + }) except Exception as e: logger.error(str(e)) + + # 保存历史记录 + self.save_data('history', history) + logger.info(f"所有榜单RSS刷新完成") @staticmethod diff --git a/app/plugins/doubansync/__init__.py b/app/plugins/doubansync/__init__.py index e6cf93dd..b419920d 100644 --- a/app/plugins/doubansync/__init__.py +++ b/app/plugins/doubansync/__init__.py @@ -61,7 +61,6 @@ class DoubanSync(_PluginBase): _users: str = "" def init_plugin(self, config: dict = None): - self._cache_path = settings.TEMP_PATH / "__doubansync_cache__" self.rsshelper = RssHelper() self.downloadchain = DownloadChain() self.searchchain = SearchChain() @@ -259,7 +258,6 @@ class DoubanSync(_PluginBase): poster = history.get("poster") mtype = history.get("type") time_str = history.get("time") - overview = history.get("overview") doubanid = history.get("doubanid") contents.append( { @@ -357,10 +355,8 @@ class DoubanSync(_PluginBase): """ if not self._users: return - # 读取缓存 - caches = self._cache_path.read_text().split("\n") if self._cache_path.exists() else [] # 读取历史记录 - history = self.get_data('history') or [] + history: List[dict] = self.get_data('history') or [] for user_id in self._users.split(","): # 同步每个用户的豆瓣数据 if not user_id: @@ -387,8 +383,8 @@ class DoubanSync(_PluginBase): logger.info(f'已超过同步天数,标题:{title},发布时间:{pubdate}') continue douban_id = result.get("link", "").split("/")[-2] - # 检查缓存 - if not douban_id or douban_id in caches: + # 检查是否处理过 + if not douban_id or douban_id in [h.get("doubanid") for h in history]: continue # 根据豆瓣ID获取豆瓣数据 doubaninfo: Optional[dict] = self.chain.douban_info(doubanid=douban_id) @@ -404,8 +400,6 @@ class DoubanSync(_PluginBase): if not mediainfo: logger.warn(f'未识别到媒体信息,标题:{title},豆瓣ID:{douban_id}') continue - # 加入缓存 - caches.append(douban_id) # 查询缺失的媒体信息 exist_flag, no_exists = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=mediainfo) if exist_flag: @@ -447,25 +441,22 @@ class DoubanSync(_PluginBase): username="豆瓣想看") action = "subscribe" # 存储历史记录 - if douban_id not in [h.get("doubanid") for h in history]: - history.append({ - "action": action, - "title": doubaninfo.get("title") or mediainfo.title, - "type": mediainfo.type.value, - "year": mediainfo.year, - "poster": mediainfo.get_poster_image(), - "overview": mediainfo.overview, - "tmdbid": mediainfo.tmdb_id, - "doubanid": douban_id, - "time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - }) + history.append({ + "action": action, + "title": doubaninfo.get("title") or mediainfo.title, + "type": mediainfo.type.value, + "year": mediainfo.year, + "poster": mediainfo.get_poster_image(), + "overview": mediainfo.overview, + "tmdbid": mediainfo.tmdb_id, + "doubanid": douban_id, + "time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + }) except Exception as err: logger.error(f'同步用户 {user_id} 豆瓣想看数据出错:{err}') logger.info(f"用户 {user_id} 豆瓣想看同步完成") # 保存历史记录 self.save_data('history', history) - # 保存缓存 - self._cache_path.write_text("\n".join(caches)) @eventmanager.register(EventType.DoubanSync) def remote_sync(self, event: Event): From 79a57b057637a72d9c718858737bfbbff689a265 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 3 Aug 2023 17:58:00 +0800 Subject: [PATCH 4/5] fix plugin state --- app/core/plugin.py | 3 +++ app/plugins/__init__.py | 7 +++++++ app/plugins/autosignin/__init__.py | 3 +++ app/plugins/chinesesubfinder/__init__.py | 3 +++ app/plugins/customhosts/__init__.py | 3 +++ app/plugins/dirmonitor/__init__.py | 7 +++++-- app/plugins/doubanrank/__init__.py | 3 +++ app/plugins/doubansync/__init__.py | 3 +++ app/plugins/libraryscraper/__init__.py | 9 ++++++--- app/plugins/sitestatistic/__init__.py | 3 +++ app/plugins/speedlimiter/__init__.py | 9 ++++++--- app/plugins/torrentremover/__init__.py | 9 ++++++--- 12 files changed, 51 insertions(+), 11 deletions(-) diff --git a/app/core/plugin.py b/app/core/plugin.py index ad1602a3..05898d36 100644 --- a/app/core/plugin.py +++ b/app/core/plugin.py @@ -185,6 +185,9 @@ class PluginManager(metaclass=Singleton): conf.update({"installed": True}) else: conf.update({"installed": False}) + # 运行状态 + if hasattr(plugin, "get_state"): + conf.update({"state": plugin.get_state()}) # 名称 if hasattr(plugin, "plugin_name"): conf.update({"plugin_name": plugin.plugin_name}) diff --git a/app/plugins/__init__.py b/app/plugins/__init__.py index da46dffe..e6ad9391 100644 --- a/app/plugins/__init__.py +++ b/app/plugins/__init__.py @@ -95,6 +95,13 @@ class _PluginBase(metaclass=ABCMeta): """ pass + @abstractmethod + def get_state(self) -> bool: + """ + 获取插件运行状态 + """ + pass + @abstractmethod def stop_service(self): """ diff --git a/app/plugins/autosignin/__init__.py b/app/plugins/autosignin/__init__.py index 1e397a63..c125fc53 100644 --- a/app/plugins/autosignin/__init__.py +++ b/app/plugins/autosignin/__init__.py @@ -113,6 +113,9 @@ class AutoSignIn(_PluginBase): self._scheduler.print_jobs() self._scheduler.start() + def get_state(self) -> bool: + return self._enabled + @staticmethod def get_command() -> List[Dict[str, Any]]: """ diff --git a/app/plugins/chinesesubfinder/__init__.py b/app/plugins/chinesesubfinder/__init__.py index c183a299..e6dde844 100644 --- a/app/plugins/chinesesubfinder/__init__.py +++ b/app/plugins/chinesesubfinder/__init__.py @@ -173,6 +173,9 @@ class ChineseSubFinder(_PluginBase): "remote_path": "" } + def get_state(self) -> bool: + return self._enabled + def get_page(self) -> List[dict]: pass diff --git a/app/plugins/customhosts/__init__.py b/app/plugins/customhosts/__init__.py index 68edc029..807c3eb4 100644 --- a/app/plugins/customhosts/__init__.py +++ b/app/plugins/customhosts/__init__.py @@ -60,6 +60,9 @@ class CustomHosts(_PluginBase): "enable": self._enabled }) + def get_state(self) -> bool: + return self._enabled + @staticmethod def get_command() -> List[Dict[str, Any]]: pass diff --git a/app/plugins/dirmonitor/__init__.py b/app/plugins/dirmonitor/__init__.py index 873bc1cf..a0a91861 100644 --- a/app/plugins/dirmonitor/__init__.py +++ b/app/plugins/dirmonitor/__init__.py @@ -27,18 +27,21 @@ class DirMonitor(_PluginBase): # 私有属性 _monitor = None - _enable = False + _enabled = False def init_plugin(self, config: dict = None): # 读取配置 if config: - self._enable = config.get("enable") + self._enabled = config.get("enabled") # 停止现有任务 self.stop_service() # TODO 启动任务 + def get_state(self) -> bool: + return self._enabled + @staticmethod def get_command() -> List[Dict[str, Any]]: pass diff --git a/app/plugins/doubanrank/__init__.py b/app/plugins/doubanrank/__init__.py index 31f9d201..275c2d9f 100644 --- a/app/plugins/doubanrank/__init__.py +++ b/app/plugins/doubanrank/__init__.py @@ -102,6 +102,9 @@ class DoubanRank(_PluginBase): self._scheduler.print_jobs() self._scheduler.start() + def get_state(self) -> bool: + return self._enabled + @staticmethod def get_command() -> List[Dict[str, Any]]: pass diff --git a/app/plugins/doubansync/__init__.py b/app/plugins/doubansync/__init__.py index b419920d..46367347 100644 --- a/app/plugins/doubansync/__init__.py +++ b/app/plugins/doubansync/__init__.py @@ -97,6 +97,9 @@ class DoubanSync(_PluginBase): self._scheduler.print_jobs() self._scheduler.start() + def get_state(self) -> bool: + return self._enabled + @staticmethod def get_command() -> List[Dict[str, Any]]: """ diff --git a/app/plugins/libraryscraper/__init__.py b/app/plugins/libraryscraper/__init__.py index bfc8d971..4e4f2868 100644 --- a/app/plugins/libraryscraper/__init__.py +++ b/app/plugins/libraryscraper/__init__.py @@ -36,7 +36,7 @@ class LibraryScraper(_PluginBase): _scheduler = None _scraper = None # 限速开关 - _enable = False + _enabled = False _cron = None _mode = None _scraper_path = None @@ -47,7 +47,7 @@ class LibraryScraper(_PluginBase): def init_plugin(self, config: dict = None): # 读取配置 if config: - self._enable = config.get("enable") + self._enabled = config.get("enabled") self._cron = config.get("cron") self._mode = config.get("mode") self._scraper_path = config.get("scraper_path") @@ -57,7 +57,7 @@ class LibraryScraper(_PluginBase): self.stop_service() # 启动定时任务 & 立即运行一次 - if self._enable: + if self._enabled: self._scheduler = BackgroundScheduler(timezone=settings.TZ) if self._cron: logger.info(f"媒体库刮削服务启动,周期:{self._cron}") @@ -68,6 +68,9 @@ class LibraryScraper(_PluginBase): self._scheduler.print_jobs() self._scheduler.start() + def get_state(self) -> bool: + return self._enabled + @staticmethod def get_command() -> List[Dict[str, Any]]: pass diff --git a/app/plugins/sitestatistic/__init__.py b/app/plugins/sitestatistic/__init__.py index 17295ed9..3fd19cdd 100644 --- a/app/plugins/sitestatistic/__init__.py +++ b/app/plugins/sitestatistic/__init__.py @@ -114,6 +114,9 @@ class SiteStatistic(_PluginBase): self._scheduler.print_jobs() self._scheduler.start() + def get_state(self) -> bool: + return self._enabled + @staticmethod def get_command() -> List[Dict[str, Any]]: """ diff --git a/app/plugins/speedlimiter/__init__.py b/app/plugins/speedlimiter/__init__.py index d9a9f09a..e4cfdb01 100644 --- a/app/plugins/speedlimiter/__init__.py +++ b/app/plugins/speedlimiter/__init__.py @@ -31,7 +31,7 @@ class SpeedLimiter(_PluginBase): # 私有属性 _scheduler = None - _enable: bool = False + _enabled: bool = False _notify: bool = False _bandwidth: int = 0 _interval: int = 60 @@ -39,7 +39,7 @@ class SpeedLimiter(_PluginBase): def init_plugin(self, config: dict = None): # 读取配置 if config: - self._enable = config.get("enable") + self._enabled = config.get("enabled") self._notify = config.get("notify") try: # 总带宽 @@ -52,7 +52,7 @@ class SpeedLimiter(_PluginBase): self.stop_service() # 启动限速任务 - if self._enable: + if self._enabled: self._scheduler = BackgroundScheduler(timezone=settings.TZ) self._scheduler.add_job(func=self.__check_playing_sessions, trigger='interval', @@ -61,6 +61,9 @@ class SpeedLimiter(_PluginBase): self._scheduler.start() logger.info("播放限速服务启动") + def get_state(self) -> bool: + return self._enabled + @staticmethod def get_command() -> List[Dict[str, Any]]: pass diff --git a/app/plugins/torrentremover/__init__.py b/app/plugins/torrentremover/__init__.py index b82834b4..7358176f 100644 --- a/app/plugins/torrentremover/__init__.py +++ b/app/plugins/torrentremover/__init__.py @@ -29,11 +29,14 @@ class TorrentRemover(_PluginBase): # 私有属性 downloader = None - _enable = False + _enabled = False def init_plugin(self, config: dict = None): if config: - self._enable = config.get("enable") + self._enabled = config.get("enabled") + + def get_state(self) -> bool: + return self._enabled @staticmethod def get_command() -> List[Dict[str, Any]]: @@ -56,7 +59,7 @@ class TorrentRemover(_PluginBase): """ 联动删除下载器中的下载任务 """ - if not self._enable: + if not self._enabled: return event_info = event.event_data if not event_info: From 803717d4c38d803915ff6bbf8f9920492baae2ef Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 3 Aug 2023 18:09:55 +0800 Subject: [PATCH 5/5] fix plugin state --- app/core/plugin.py | 7 +++++-- app/schemas/plugin.py | 26 +++++++++++++++----------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/core/plugin.py b/app/core/plugin.py index 05898d36..d8c92276 100644 --- a/app/core/plugin.py +++ b/app/core/plugin.py @@ -186,8 +186,11 @@ class PluginManager(metaclass=Singleton): else: conf.update({"installed": False}) # 运行状态 - if hasattr(plugin, "get_state"): - conf.update({"state": plugin.get_state()}) + if pid in self._running_plugins.keys() and hasattr(plugin, "get_state"): + plugin_obj = self._running_plugins.get(pid) + conf.update({"state": plugin_obj.get_state()}) + else: + conf.update({"state": False}) # 名称 if hasattr(plugin, "plugin_name"): conf.update({"plugin_name": plugin.plugin_name}) diff --git a/app/schemas/plugin.py b/app/schemas/plugin.py index 35d5dc43..f8b4660f 100644 --- a/app/schemas/plugin.py +++ b/app/schemas/plugin.py @@ -1,3 +1,5 @@ +from typing import Optional + from pydantic import BaseModel @@ -7,24 +9,26 @@ class Plugin(BaseModel): """ id: str = None # 插件名称 - plugin_name: str = None + plugin_name: Optional[str] = None # 插件描述 - plugin_desc: str = None + plugin_desc: Optional[str] = None # 插件图标 - plugin_icon: str = None + plugin_icon: Optional[str] = None # 主题色 - plugin_color: str = None + plugin_color: Optional[str] = None # 插件版本 - plugin_version: str = None + plugin_version: Optional[str] = None # 插件作者 - plugin_author: str = None + plugin_author: Optional[str] = None # 作者主页 - author_url: str = None + author_url: Optional[str] = None # 插件配置项ID前缀 - plugin_config_prefix: str = None + plugin_config_prefix: Optional[str] = None # 加载顺序 - plugin_order: int = 0 + plugin_order: Optional[int] = 0 # 可使用的用户级别 - auth_level: int = 0 + auth_level: Optional[int] = 0 # 是否已安装 - installed: bool = False + installed: Optional[bool] = False + # 运行状态 + state: Optional[bool] = False