diff --git a/Dockerfile b/Dockerfile index 6a0631ae..e1905e41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,8 +26,7 @@ ENV LANG="C.UTF-8" \ QB_PASSWORD="adminadmin" \ MEDIASERVER="emby" \ EMBY_HOST="http://127.0.0.1:8096" \ - EMBY_API_KEY="" \ - DOUBAN_USER_IDS="" + EMBY_API_KEY="" WORKDIR "/app" COPY . . RUN apt-get update \ diff --git a/README.md b/README.md index 3ee3f524..3114f161 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,6 @@ docker pull jxxghp/moviepilot:latest - **TORRENT_TAG:** 种子标签,默认为`MOVIEPILOT`,设置后只有MoviePilot添加的下载才会处理,留空所有下载器中的任务均会处理 - **LIBRARY_PATH:** 媒体库目录,**注意:需要将`moviepilot`的映射路径与宿主机`真实路径`保持一致**,多个目录使用`,`分隔 - **LIBRARY_CATEGORY:** 媒体库二级分类开关,`true`/`false`,默认`false`,开启后会根据配置`category.yaml`自动在媒体库目录下建立二级目录分类 -- **DOUBAN_USER_IDS:** 豆瓣用户ID,用于同步豆瓣标记的`想看`数据,自动添加订阅,多个用户使用,分隔 - **TRANSFER_TYPE:** 转移方式,支持`link`/`copy`/`move`/`softlink` - **COOKIECLOUD_HOST:** CookieCloud服务器地址,格式:`http://ip:port`,必须配置,否则无法添加站点 - **COOKIECLOUD_KEY:** CookieCloud用户KEY diff --git a/app/chain/douban.py b/app/chain/douban.py index 7efa4a3e..2ff2cb17 100644 --- a/app/chain/douban.py +++ b/app/chain/douban.py @@ -1,18 +1,11 @@ -from pathlib import Path from typing import Optional, List -from typing import Union from app.chain import ChainBase -from app.chain.download import DownloadChain -from app.chain.search import SearchChain -from app.chain.subscribe import SubscribeChain -from app.core.config import settings from app.core.context import Context from app.core.context import MediaInfo from app.core.metainfo import MetaInfo -from app.helper.rss import RssHelper from app.log import logger -from app.schemas import MediaType, Notification, MessageChannel +from app.schemas import MediaType class DoubanChain(ChainBase): @@ -20,17 +13,6 @@ class DoubanChain(ChainBase): 豆瓣处理链 """ - _interests_url: str = "https://www.douban.com/feed/people/%s/interests" - - _cache_path: Path = settings.TEMP_PATH / "__doubansync_cache__" - - def __init__(self): - super().__init__() - self.rsshelper = RssHelper() - self.downloadchain = DownloadChain() - self.searchchain = SearchChain() - self.subscribechain = SubscribeChain() - def recognize_by_doubanid(self, doubanid: str) -> Optional[Context]: """ 根据豆瓣ID识别媒体信息 @@ -96,99 +78,3 @@ class DoubanChain(ChainBase): """ return self.run_module("douban_discover", mtype=mtype, sort=sort, tags=tags, page=page, count=count) - - def remote_sync(self, channel: MessageChannel, userid: Union[int, str]): - """ - 同步豆瓣想看数据,发送消息 - """ - self.post_message(Notification(channel=channel, - title="开始同步豆瓣想看 ...", userid=userid)) - self.sync() - self.post_message(Notification(channel=channel, - title="同步豆瓣想看数据完成!", userid=userid)) - - def sync(self): - """ - 通过用户RSS同步豆瓣想看数据 - """ - if not settings.DOUBAN_USER_IDS: - return - # 读取缓存 - caches = self._cache_path.read_text().split("\n") if self._cache_path.exists() else [] - for user_id in settings.DOUBAN_USER_IDS.split(","): - # 同步每个用户的豆瓣数据 - if not user_id: - continue - logger.info(f"开始同步用户 {user_id} 的豆瓣想看数据 ...") - url = self._interests_url % user_id - results = self.rsshelper.parse(url) - if not results: - logger.error(f"未获取到用户 {user_id} 豆瓣RSS数据:{url}") - return - # 解析数据 - for result in results: - dtype = result.get("title", "")[:2] - title = result.get("title", "")[2:] - if dtype not in ["想看"]: - continue - if not result.get("link"): - continue - douban_id = result.get("link", "").split("/")[-2] - if not douban_id or douban_id in caches: - continue - # 根据豆瓣ID获取豆瓣数据 - doubaninfo: Optional[dict] = self.douban_info(doubanid=douban_id) - if not doubaninfo: - logger.warn(f'未获取到豆瓣信息,标题:{title},豆瓣ID:{douban_id}') - continue - logger.info(f'获取到豆瓣信息,标题:{title},豆瓣ID:{douban_id}') - # 识别媒体信息 - meta = MetaInfo(doubaninfo.get("original_title") or doubaninfo.get("title")) - if doubaninfo.get("year"): - meta.year = doubaninfo.get("year") - mediainfo: MediaInfo = self.recognize_media(meta=meta) - 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: - logger.info(f'{mediainfo.title_year} 媒体库中已存在') - continue - logger.info(f'{mediainfo.title_year} 媒体库中不存在,开始搜索 ...') - # 搜索 - contexts = self.searchchain.process(mediainfo=mediainfo, - no_exists=no_exists) - if not contexts: - logger.warn(f'{mediainfo.title_year} 未搜索到资源') - # 添加订阅 - self.subscribechain.add(title=mediainfo.title, - year=mediainfo.year, - mtype=mediainfo.type, - tmdbid=mediainfo.tmdb_id, - season=meta.begin_season, - exist_ok=True, - username="豆瓣想看") - continue - # 自动下载 - downloads, lefts = self.downloadchain.batch_download(contexts=contexts, no_exists=no_exists) - if downloads and not lefts: - # 全部下载完成 - logger.info(f'{mediainfo.title_year} 下载完成') - else: - # 未完成下载 - logger.info(f'{mediainfo.title_year} 未下载未完整,添加订阅 ...') - # 添加订阅 - self.subscribechain.add(title=mediainfo.title, - year=mediainfo.year, - mtype=mediainfo.type, - tmdbid=mediainfo.tmdb_id, - season=meta.begin_season, - exist_ok=True, - username="豆瓣想看") - - logger.info(f"用户 {user_id} 豆瓣想看同步完成") - # 保存缓存 - self._cache_path.write_text("\n".join(caches)) diff --git a/app/command.py b/app/command.py index bea1fb70..61067bec 100644 --- a/app/command.py +++ b/app/command.py @@ -4,19 +4,18 @@ from typing import Any, Union from app.chain import ChainBase from app.chain.cookiecloud import CookieCloudChain -from app.chain.douban import DoubanChain from app.chain.download import DownloadChain from app.chain.mediaserver import MediaServerChain from app.chain.site import SiteChain from app.chain.subscribe import SubscribeChain from app.chain.transfer import TransferChain +from app.core.event import Event as ManagerEvent from app.core.event import eventmanager, EventManager from app.core.plugin import PluginManager -from app.core.event import Event as ManagerEvent from app.log import logger +from app.schemas.types import EventType, MessageChannel from app.utils.object import ObjectUtils from app.utils.singleton import Singleton -from app.schemas.types import EventType, MessageChannel class CommandChian(ChainBase): @@ -70,11 +69,6 @@ class Command(metaclass=Singleton): "description": "禁用站点", "data": {} }, - "/douban_sync": { - "func": DoubanChain().remote_sync, - "description": "同步豆瓣想看", - "data": {} - }, "/mediaserver_sync": { "func": MediaServerChain().remote_sync, "description": "同步媒体服务器", diff --git a/app/core/config.py b/app/core/config.py index 4778b78a..9332c0dd 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -143,8 +143,6 @@ class Settings(BaseSettings): LIBRARY_PATH: str = None # 二级分类 LIBRARY_CATEGORY: bool = True - # 豆瓣用户ID,用于同步豆瓣数据,使用,分隔 - DOUBAN_USER_IDS: str = "" # 电影重命名格式 MOVIE_RENAME_FORMAT: str = "{{title}}{% if year %} ({{year}}){% endif %}" \ "/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}" \ diff --git a/app/plugins/__init__.py b/app/plugins/__init__.py index 1f20b8ae..73fb44e0 100644 --- a/app/plugins/__init__.py +++ b/app/plugins/__init__.py @@ -7,6 +7,8 @@ from app.core.config import settings from app.db.models import Base from app.db.plugindata_oper import PluginDataOper from app.db.systemconfig_oper import SystemConfigOper +from app.helper.message import MessageHelper +from app.schemas import Notification, NotificationType, MessageChannel class PluginChian(ChainBase): @@ -34,9 +36,14 @@ class _PluginBase(metaclass=ABCMeta): plugin_desc: str = "" def __init__(self): + # 插件数据 self.plugindata = PluginDataOper() + # 处理链 self.chain = PluginChian() + # 系统配置 self.systemconfig = SystemConfigOper() + # 系统消息 + self.systemmessage = MessageHelper() @abstractmethod def init_plugin(self, config: dict = None): @@ -139,3 +146,13 @@ class _PluginBase(metaclass=ABCMeta): :param key: 数据key """ return self.plugindata.get_data(key) + + def post_message(self, channel: MessageChannel = None, mtype: NotificationType = None, title: str = None, + text: str = None, image: str = None, link: str = None, userid: str = None): + """ + 发送消息 + """ + self.chain.post_message(Notification( + channel=channel, mtype=mtype, title=title, text=text, + image=image, link=link, userid=userid + )) diff --git a/app/plugins/autosignin/__init__.py b/app/plugins/autosignin/__init__.py index 945b1ff2..2fef1aa8 100644 --- a/app/plugins/autosignin/__init__.py +++ b/app/plugins/autosignin/__init__.py @@ -2,8 +2,7 @@ import traceback from datetime import datetime from multiprocessing.dummy import Pool as ThreadPool from multiprocessing.pool import ThreadPool -from threading import Event -from typing import Any, List, Dict, Tuple +from typing import Any, List, Dict, Tuple, Optional from urllib.parse import urljoin from apscheduler.schedulers.background import BackgroundScheduler @@ -11,20 +10,19 @@ from apscheduler.triggers.cron import CronTrigger from ruamel.yaml import CommentedMap from app import schemas -from app.core.event import EventManager, eventmanager from app.core.config import settings +from app.core.event import EventManager, eventmanager, Event from app.helper.browser import PlaywrightHelper from app.helper.cloudflare import under_challenge from app.helper.module import ModuleHelper from app.helper.sites import SitesHelper from app.log import logger from app.plugins import _PluginBase -from app.schemas import Notification +from app.schemas.types import EventType, NotificationType from app.utils.http import RequestUtils from app.utils.site import SiteUtils from app.utils.string import StringUtils from app.utils.timer import TimerUtils -from app.schemas.types import EventType class AutoSignIn(_PluginBase): @@ -54,7 +52,7 @@ class AutoSignIn(_PluginBase): # 事件管理器 event: EventManager = None # 定时器 - _scheduler = None + _scheduler: Optional[BackgroundScheduler] = None # 加载的模块 _site_schema: list = [] @@ -92,9 +90,12 @@ class AutoSignIn(_PluginBase): if self._cron: try: self._scheduler.add_job(func=self.sign_in, - trigger=CronTrigger.from_crontab(self._cron)) + trigger=CronTrigger.from_crontab(self._cron), + name="站点自动签到") except Exception as err: logger.error(f"定时任务配置错误:{err}") + # 推送实时消息 + self.systemmessage.put(f"执行周期配置错误:{err}") else: # 随机时间 triggers = TimerUtils.random_scheduler(num_executions=2, @@ -104,7 +105,8 @@ class AutoSignIn(_PluginBase): min_interval=6 * 60) for trigger in triggers: self._scheduler.add_job(self.sign_in, "cron", - hour=trigger.hour, minute=trigger.minute) + hour=trigger.hour, minute=trigger.minute, + name="站点自动签到") # 启动任务 if self._scheduler.get_jobs(): @@ -183,7 +185,7 @@ class AutoSignIn(_PluginBase): 'component': 'VSwitch', 'props': { 'model': 'notify', - 'label': '签到通知', + 'label': '发送通知', } } ] @@ -205,7 +207,7 @@ class AutoSignIn(_PluginBase): 'props': { 'model': 'cron', 'label': '执行周期', - 'placeholder': '0 9,18 * * *' + 'placeholder': '5位cron表达式,留空自动' } } ] @@ -253,7 +255,7 @@ class AutoSignIn(_PluginBase): ], { "enabled": False, "notify": True, - "cron": "1 9,18 * * *", + "cron": "", "queue_cnt": 5, "sign_sites": [] } @@ -270,7 +272,10 @@ class AutoSignIn(_PluginBase): 自动签到 """ if event: - logger.info("收到远程签到命令,开始执行签到任务 ...") + logger.info("收到命令,开始站点签到 ...") + self.post_message(channel=event.event_data.get("channel"), + title="开始站点签到 ...", + userid=event.event_data.get("user")) # 查询签到站点 sign_sites = [site for site in self.sites.get_indexers() if not site.get("public")] # 过滤掉没有选中的站点 @@ -297,10 +302,17 @@ class AutoSignIn(_PluginBase): } for s in status]) # 发送通知 if self._notify: - self.chain.post_message(Notification(title="站点自动签到", - text="\n".join([f'【{s[0]}】{s[1]}' for s in status if s]))) + self.post_message(title="站点自动签到", + mtype=NotificationType.SiteMessage, + text="\n".join([f'【{s[0]}】{s[1]}' for s in status if s])) + if event: + self.post_message(channel=event.event_data.get("channel"), + title="站点签到完成!", userid=event.event_data.get("user")) else: logger.error("站点签到任务失败!") + if event: + self.post_message(channel=event.event_data.get("channel"), + title="站点签到任务失败!", userid=event.event_data.get("user")) def __build_class(self, url) -> Any: for site_schema in self._site_schema: diff --git a/app/plugins/doubansync/__init__.py b/app/plugins/doubansync/__init__.py new file mode 100644 index 00000000..9bdb17b9 --- /dev/null +++ b/app/plugins/doubansync/__init__.py @@ -0,0 +1,356 @@ +from pathlib import Path +from threading import Lock +from typing import Optional, Any, List, Dict, Tuple + +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.triggers.cron import CronTrigger + +from app.chain.download import DownloadChain +from app.chain.search import SearchChain +from app.chain.subscribe import SubscribeChain +from app.core.config import settings +from app.core.context import MediaInfo +from app.core.event import Event +from app.core.event import eventmanager +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.types import EventType + +lock = Lock() + + +class DoubanSync(_PluginBase): + # 插件名称 + plugin_name = "豆瓣想看" + # 插件描述 + plugin_desc = "同步豆瓣想看数据,自动添加订阅。" + # 插件图标 + plugin_icon = "douban.png" + # 主题色 + plugin_color = "#05B711" + # 插件版本 + plugin_version = "1.0" + # 插件作者 + plugin_author = "jxxghp" + # 作者主页 + author_url = "https://github.com/jxxghp" + # 插件配置项ID前缀 + plugin_config_prefix = "doubansync_" + # 加载顺序 + plugin_order = 3 + # 可使用的用户级别 + auth_level = 2 + + # 私有变量 + _interests_url: str = "https://www.douban.com/feed/people/%s/interests" + _scheduler: Optional[BackgroundScheduler] = None + _cache_path: Optional[Path] = None + rsshelper = None + downloadchain = None + searchchain = None + subscribechain = None + + # 配置属性 + _enabled: bool = False + _cron: str = "" + _notify: bool = False + _days: int = 7 + _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() + self.subscribechain = SubscribeChain() + + # 停止现有任务 + self.stop_service() + + # 配置 + if config: + self._enabled = config.get("enabled") + self._cron = config.get("cron") + self._notify = config.get("notify") + self._days = config.get("days") + self._users = config.get("users") + + if self._enabled: + + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + if self._cron: + try: + self._scheduler.add_job(func=self.sync, + trigger=CronTrigger.from_crontab(self._cron), + name="豆瓣想看") + except Exception as err: + logger.error(f"定时任务配置错误:{err}") + # 推送实时消息 + self.systemmessage.put(f"执行周期配置错误:{err}") + else: + self._scheduler.add_job(self.sync, "interval", minutes=30, name="豆瓣想看") + + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + """ + 定义远程控制命令 + :return: 命令关键字、事件、描述、附带数据 + """ + return [{ + "cmd": "/douban_sync", + "event": EventType.DoubanSync, + "desc": "同步豆瓣想看", + "data": {} + }] + + def get_api(self) -> List[Dict[str, Any]]: + """ + 获取插件API + [{ + "path": "/xx", + "endpoint": self.xxx, + "methods": ["GET", "POST"], + "summary": "API说明" + }] + """ + pass + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + """ + 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 + """ + return [ + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enabled', + 'label': '启用插件', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'notify', + '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': 'days', + 'label': '同步天数' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'users', + 'label': '用户列表', + 'placeholder': '豆瓣用户ID,多个用英文逗号分隔' + } + } + ] + } + ] + } + ] + } + ], { + "enabled": False, + "notify": True, + "cron": "*/30 * * * *", + "days": 7, + "users": "", + } + + def get_page(self) -> List[dict]: + """ + 拼装插件详情页面,需要返回页面配置,同时附带数据 + """ + pass + + def stop_service(self): + """ + 退出插件 + """ + try: + if self._scheduler: + self._scheduler.remove_all_jobs() + if self._scheduler.running: + self._scheduler.shutdown() + self._scheduler = None + except Exception as e: + logger.error("退出插件失败:%s" % str(e)) + + def sync(self): + """ + 通过用户RSS同步豆瓣想看数据 + """ + if not self._users: + return + # 读取缓存 + caches = self._cache_path.read_text().split("\n") if self._cache_path.exists() else [] + for user_id in self._users.split(","): + # 同步每个用户的豆瓣数据 + if not user_id: + continue + logger.info(f"开始同步用户 {user_id} 的豆瓣想看数据 ...") + url = self._interests_url % user_id + results = self.rsshelper.parse(url) + if not results: + logger.error(f"未获取到用户 {user_id} 豆瓣RSS数据:{url}") + return + # 解析数据 + for result in results: + dtype = result.get("title", "")[:2] + title = result.get("title", "")[2:] + if dtype not in ["想看"]: + continue + if not result.get("link"): + continue + # TODO 判断是否在天数范围 + douban_id = result.get("link", "").split("/")[-2] + if not douban_id or douban_id in caches: + continue + # 根据豆瓣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}') + # 识别媒体信息 + meta = MetaInfo(doubaninfo.get("original_title") or doubaninfo.get("title")) + if doubaninfo.get("year"): + meta.year = doubaninfo.get("year") + mediainfo: MediaInfo = self.chain.recognize_media(meta=meta) + 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: + logger.info(f'{mediainfo.title_year} 媒体库中已存在') + continue + logger.info(f'{mediainfo.title_year} 媒体库中不存在,开始搜索 ...') + # 搜索 + contexts = self.searchchain.process(mediainfo=mediainfo, + no_exists=no_exists) + if not contexts: + logger.warn(f'{mediainfo.title_year} 未搜索到资源') + # 添加订阅 + self.subscribechain.add(title=mediainfo.title, + year=mediainfo.year, + mtype=mediainfo.type, + tmdbid=mediainfo.tmdb_id, + season=meta.begin_season, + exist_ok=True, + username="豆瓣想看") + continue + # 自动下载 + downloads, lefts = self.downloadchain.batch_download(contexts=contexts, no_exists=no_exists) + if downloads and not lefts: + # 全部下载完成 + logger.info(f'{mediainfo.title_year} 下载完成') + else: + # 未完成下载 + logger.info(f'{mediainfo.title_year} 未下载未完整,添加订阅 ...') + # 添加订阅 + self.subscribechain.add(title=mediainfo.title, + year=mediainfo.year, + mtype=mediainfo.type, + tmdbid=mediainfo.tmdb_id, + season=meta.begin_season, + exist_ok=True, + username="豆瓣想看") + + logger.info(f"用户 {user_id} 豆瓣想看同步完成") + # 保存缓存 + self._cache_path.write_text("\n".join(caches)) + + @eventmanager.register(EventType.DoubanSync) + def remote_sync(self, event: Event): + """ + 刷新站点数据 + """ + if event: + logger.info("收到命令,开始执行豆瓣想看同步 ...") + self.post_message(channel=event.event_data.get("channel"), + title="开始同步豆瓣想看 ...", + userid=event.event_data.get("user")) + self.sync() + + if event: + self.post_message(channel=event.event_data.get("channel"), + title="同步豆瓣想看数据完成!", userid=event.event_data.get("user")) diff --git a/app/plugins/sitestatistic/__init__.py b/app/plugins/sitestatistic/__init__.py index 114eb050..f862ef59 100644 --- a/app/plugins/sitestatistic/__init__.py +++ b/app/plugins/sitestatistic/__init__.py @@ -1,3 +1,4 @@ +import warnings from datetime import datetime from multiprocessing.dummy import Pool as ThreadPool from threading import Lock @@ -10,23 +11,19 @@ from ruamel.yaml import CommentedMap from app import schemas from app.core.config import settings -from app.core.event import eventmanager from app.core.event import Event +from app.core.event import eventmanager from app.helper.browser import PlaywrightHelper from app.helper.module import ModuleHelper from app.helper.sites import SitesHelper from app.log import logger from app.plugins import _PluginBase from app.plugins.sitestatistic.siteuserinfo import ISiteUserInfo -from app.schemas import Notification +from app.schemas.types import EventType, NotificationType from app.utils.http import RequestUtils from app.utils.string import StringUtils from app.utils.timer import TimerUtils -import warnings - -from app.schemas.types import EventType - warnings.filterwarnings("ignore", category=FutureWarning) lock = Lock() @@ -56,7 +53,7 @@ class SiteStatistic(_PluginBase): # 私有属性 sites = None - _scheduler = None + _scheduler: Optional[BackgroundScheduler] = None _last_update_time: Optional[datetime] = None _sites_data: dict = {} _site_schema: List[ISiteUserInfo] = None @@ -95,9 +92,12 @@ class SiteStatistic(_PluginBase): if self._cron: try: self._scheduler.add_job(func=self.refresh_all_site_data, - trigger=CronTrigger.from_crontab(self._cron)) + trigger=CronTrigger.from_crontab(self._cron), + name="站点数据统计") except Exception as err: logger.error(f"定时任务配置错误:{err}") + # 推送实时消息 + self.systemmessage.put(f"执行周期配置错误:{err}") else: triggers = TimerUtils.random_scheduler(num_executions=1, begin_hour=0, @@ -106,7 +106,8 @@ class SiteStatistic(_PluginBase): max_interval=60) for trigger in triggers: self._scheduler.add_job(self.refresh_all_site_data, "cron", - hour=trigger.hour, minute=trigger.minute) + hour=trigger.hour, minute=trigger.minute, + name="站点数据统计") # 启动任务 if self._scheduler.get_jobs(): @@ -207,7 +208,7 @@ class SiteStatistic(_PluginBase): 'props': { 'model': 'cron', 'label': '执行周期', - 'placeholder': '0 9,18 * * *' + 'placeholder': '5位cron表达式,留空自动' } } ] @@ -467,10 +468,11 @@ class SiteStatistic(_PluginBase): for head, date, content in site_user_info.message_unread_contents: msg_title = f"【站点 {site_user_info.site_name} 消息】" msg_text = f"时间:{date}\n标题:{head}\n内容:\n{content}" - self.chain.post_message(Notification(title=msg_title, text=msg_text)) + self.post_message(mtype=NotificationType.SiteMessage, title=msg_title, text=msg_text) else: - self.chain.post_message(Notification(title=f"站点 {site_user_info.site_name} 收到 " - f"{site_user_info.message_unread} 条新消息,请登陆查看")) + self.post_message(mtype=NotificationType.SiteMessage, + title=f"站点 {site_user_info.site_name} 收到 " + f"{site_user_info.message_unread} 条新消息,请登陆查看") @eventmanager.register(EventType.SiteStatistic) def refresh(self, event: Event): @@ -478,8 +480,14 @@ class SiteStatistic(_PluginBase): 刷新站点数据 """ if event: - logger.info("收到命令,开始执行站点数据刷新 ...") + logger.info("收到命令,开始刷新站点数据 ...") + self.post_message(channel=event.event_data.get("channel"), + title="开始刷新站点数据 ...", + userid=event.event_data.get("user")) self.refresh_all_site_data(force=True) + if event: + self.post_message(channel=event.event_data.get("channel"), + title="站点数据刷新完成!", userid=event.event_data.get("user")) def refresh_all_site_data(self, force: bool = False, specify_sites: list = None): """ @@ -555,6 +563,7 @@ class SiteStatistic(_PluginBase): f"总上传:{StringUtils.str_filesize(incUploads)}\n" f"总下载:{StringUtils.str_filesize(incDownloads)}\n" f"————————————") - self.chain.post_message(Notification(title="站点数据统计", text="\n".join(messages))) + self.post_message(mtype=NotificationType.SiteMessage, + title="站点数据统计", text="\n".join(messages)) logger.info("站点数据刷新完成") diff --git a/app/scheduler.py b/app/scheduler.py index 3c1e414c..c539f6fb 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -7,7 +7,6 @@ from apscheduler.schedulers.background import BackgroundScheduler from app.chain import ChainBase from app.chain.cookiecloud import CookieCloudChain -from app.chain.douban import DoubanChain from app.chain.mediaserver import MediaServerChain from app.chain.subscribe import SubscribeChain from app.chain.transfer import TransferChain @@ -70,9 +69,6 @@ class Scheduler(metaclass=Singleton): self._scheduler.add_job(SubscribeChain().refresh, "cron", hour=trigger.hour, minute=trigger.minute, name="订阅刷新") - # 豆瓣同步(每30分钟) - self._scheduler.add_job(DoubanChain().sync, "interval", minutes=30, name="同步豆瓣想看") - # 下载器文件转移(每5分钟) self._scheduler.add_job(TransferChain().process, "interval", minutes=5, name="下载文件整理") diff --git a/app/schemas/types.py b/app/schemas/types.py index 965e9f75..9bcb19e6 100644 --- a/app/schemas/types.py +++ b/app/schemas/types.py @@ -22,6 +22,8 @@ class EventType(Enum): SiteSignin = "site.signin" # 站点数据统计 SiteStatistic = "site.statistic" + # 豆瓣想看 + DoubanSync = "douban.sync" # Webhook消息 WebhookMessage = "webhook.message" # 转移完成