From e1f8262076f777f42c28b27e82b6136c2afe4db4 Mon Sep 17 00:00:00 2001 From: thsrite Date: Mon, 4 Dec 2023 16:47:57 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E8=AE=A2=E9=98=85=E6=8F=90=E9=86=92?= =?UTF-8?q?=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- icons/subscribe_reminder.png | Bin 0 -> 4805 bytes package.json | 8 + plugins/subscribereminder/__init__.py | 284 ++++++++++++++++++++++++++ plugins_record/SubscribeReminder.md | 5 + 5 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 icons/subscribe_reminder.png create mode 100644 plugins/subscribereminder/__init__.py create mode 100644 plugins_record/SubscribeReminder.md diff --git a/README.md b/README.md index 95dc5e8..32eca1d 100644 --- a/README.md +++ b/README.md @@ -21,5 +21,5 @@ MoviePilot三方插件市场:https://github.com/thsrite/MoviePilot-Plugins/ - [群辉Webhook通知 1.1](plugins_record%2FSynologyNotify.md) - [同步CookieCloud 1.1](plugins_record%2FSyncCookieCloud.md) - [日程提醒 1.0](plugins_record%2FScheduleReminder.md) - +- [订阅提醒 1.0](plugins_record%2FSubscribeReminder.md) diff --git a/icons/subscribe_reminder.png b/icons/subscribe_reminder.png new file mode 100644 index 0000000000000000000000000000000000000000..65135792bc5fdadd32de5b1399988e7fe4861a78 GIT binary patch literal 4805 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 Tm{djbzZ9UZ@&rXxdKUa25PZbx literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 5c8ab44..2595d8b 100644 --- a/package.json +++ b/package.json @@ -94,5 +94,13 @@ "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/reminder.png", "author": "thsrite", "level": 1 + }, + "SubscribeReminder": { + "name": "订阅提醒", + "description": "推送当天订阅更新内容。", + "version": "1.0", + "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/subscribe_reminder.png", + "author": "thsrite", + "level": 1 } } diff --git a/plugins/subscribereminder/__init__.py b/plugins/subscribereminder/__init__.py new file mode 100644 index 0000000..0b06676 --- /dev/null +++ b/plugins/subscribereminder/__init__.py @@ -0,0 +1,284 @@ +from datetime import datetime, timedelta + +import pytz +from app.chain.media import MediaChain +from app.chain.tmdb import TmdbChain +from app.core.config import settings +from app.db.subscribe_oper import SubscribeOper +from app.plugins import _PluginBase +from typing import Any, List, Dict, Tuple, Optional +from app.log import logger +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.triggers.cron import CronTrigger + +from app.schemas import NotificationType, MediaType + + +class SubscribeReminder(_PluginBase): + # 插件名称 + plugin_name = "订阅提醒" + # 插件描述 + plugin_desc = "推送当天订阅更新内容。" + # 插件图标 + plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/subscribe_reminder.png" + # 插件版本 + plugin_version = "1.0" + # 插件作者 + plugin_author = "thsrite" + # 作者主页 + author_url = "https://github.com/thsrite" + # 插件配置项ID前缀 + plugin_config_prefix = "subscribereminder_" + # 加载顺序 + plugin_order = 33 + # 可使用的用户级别 + auth_level = 1 + + # 私有属性 + _enabled: bool = False + _onlyonce: bool = False + _time = None + tmdb = None + media = None + subscribe_oper = None + _scheduler: Optional[BackgroundScheduler] = None + + def init_plugin(self, config: dict = None): + self.subscribe_oper = SubscribeOper() + self.tmdb = TmdbChain() + self.media = MediaChain() + + # 停止现有任务 + self.stop_service() + + if config: + self._enabled = config.get("enabled") + self._onlyonce = config.get("onlyonce") + self._time = config.get("time") + + if self._enabled or self._onlyonce: + # 周期运行 + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + + if self._time and str(self._time).isdigit(): + cron = f"0 {int(self._time)} * * *" + try: + self._scheduler.add_job(func=self.__send_notify, + trigger=CronTrigger.from_crontab(cron), + name="订阅提醒") + except Exception as err: + logger.error(f"定时任务配置错误:{err}") + # 推送实时消息 + self.systemmessage.put(f"执行周期配置错误:{err}") + + # 立即运行一次 + if self._onlyonce: + logger.info(f"订阅提醒服务启动,立即运行一次") + self._scheduler.add_job(self.__send_notify, 'date', + run_date=datetime.now( + tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3), + name="订阅提醒") + # 关闭一次性开关 + self._onlyonce = False + + # 保存配置 + self.__update_config() + + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() + + def __update_config(self): + self.update_config({ + "enabled": self._enabled, + "onlyonce": self._onlyonce, + "time": self._time + }) + + def __send_notify(self): + # 查询所有订阅 + subscribes = self.subscribe_oper.list() + if not subscribes: + logger.error("当前没有订阅,跳过处理") + return + + # 当前日期 + current_date = datetime.now().date().strftime("%Y-%m-%d") + + current_tv_subscribe = [] + current_movie_subscribe = [] + # 遍历订阅,查询tmdb + for subscribe in subscribes: + # 电视剧 + if subscribe.type == "电视剧": + if not subscribe.tmdbid or not subscribe.season: + continue + + # 电视剧某季所有集 + episodes_info = self.tmdb.tmdb_episodes(tmdbid=subscribe.tmdbid, season=subscribe.season) + if not episodes_info: + continue + + episodes = [] + # 遍历集,筛选当前日期发布的剧集 + for episode in episodes_info: + if episode and episode.air_date and str(episode.air_date) == current_date: + episodes.append(episode.episode_number) + + if episodes: + current_tv_subscribe.append({ + 'name': f"{subscribe.name} ({subscribe.year})", + 'season': f"S{str(subscribe.season).rjust(2, '0')}", + 'episode': f"E{str(episodes[0]).rjust(2, '0')}-E{str(episodes[-1]).rjust(2, '0')}" if len( + episodes) > 1 else f"E{str(episodes[0]).rjust(2, '0')}" + }) + + # 电影 + else: + if not subscribe.tmdbid: + continue + mediainfo = self.media.recognize_media(tmdbid=subscribe.tmdbid, mtype=MediaType.MOVIE) + if not mediainfo: + continue + if str(mediainfo.release_date) == current_date: + current_movie_subscribe.append({ + 'name': f"{subscribe.name} ({subscribe.year})" + }) + + # 如当前日期匹配到订阅,则发送通知 + text = "" + for sub in current_tv_subscribe: + text += sub.get("name") + "\n" + text += sub.get("season") + sub.get("episode") + "\n" + text += "\n" + + for sub in current_movie_subscribe: + text += sub.get("name") + "\n" + text += "\n" + + if text: + self.post_message(mtype=NotificationType.Subscribe, + title=f"{current_date}订阅提醒", + text=text) + + def get_state(self) -> bool: + return self._enabled + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + pass + + def get_api(self) -> List[Dict[str, Any]]: + pass + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + """ + 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 + """ + 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': 'onlyonce', + 'label': '立即运行一次', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'time', + 'label': '时间', + 'placeholder': '默认9点' + } + } + ] + }, + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': '默认每天9点推送,需开启(订阅)通知类型。' + } + } + ] + } + ] + } + ] + } + ], { + "enabled": False, + "onlyonce": False, + "time": 9, + } + + 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)) diff --git a/plugins_record/SubscribeReminder.md b/plugins_record/SubscribeReminder.md new file mode 100644 index 0000000..37e8fd1 --- /dev/null +++ b/plugins_record/SubscribeReminder.md @@ -0,0 +1,5 @@ +# 订阅提醒 + +### 更新记录 + +- 1.0 推送当天订阅更新内容