From a7d9b4152be1d11f6bb2de16dee328741d13a72b Mon Sep 17 00:00:00 2001 From: thsrite Date: Mon, 6 May 2024 20:43:13 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E7=83=AD=E9=97=A8=E5=AA=92=E4=BD=93?= =?UTF-8?q?=E8=AE=A2=E9=98=85=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + icons/popular.png | Bin 0 -> 11843 bytes package.json | 12 + plugins/popularsubscribe/__init__.py | 456 +++++++++++++++++++++++++++ 4 files changed, 469 insertions(+) create mode 100644 icons/popular.png create mode 100644 plugins/popularsubscribe/__init__.py diff --git a/README.md b/README.md index f05d1ab..1b49a25 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,4 @@ MoviePilot三方插件市场:https://github.com/thsrite/MoviePilot-Plugins/ - 订阅规则自动填充 2.5 - Emby元数据刷新 1.1 - Emby媒体标签 1.1 +- 热门媒体订阅 1.0 diff --git a/icons/popular.png b/icons/popular.png new file mode 100644 index 0000000000000000000000000000000000000000..4b3a763cfef079dd68546b445d6c64afe43163bb GIT binary patch literal 11843 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
  • 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 4e9ae22..4128dcc 100644 --- a/package.json +++ b/package.json @@ -402,5 +402,17 @@ "v1.1": "添加远程交互命令", "v1.0": "自动给媒体库媒体添加标签" } + }, + "PopularSubscribe": { + "name": "热门媒体订阅", + "description": "自定添加热门订阅。", + "labels": "订阅", + "version": "1.0", + "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/popular.png", + "author": "thsrite", + "level": 1, + "history": { + "v1.0": "自定添加热门订阅" + } } } diff --git a/plugins/popularsubscribe/__init__.py b/plugins/popularsubscribe/__init__.py new file mode 100644 index 0000000..2ce3c02 --- /dev/null +++ b/plugins/popularsubscribe/__init__.py @@ -0,0 +1,456 @@ +import time +from datetime import datetime, timedelta + +import pytz + +from app.chain.douban import DoubanChain +from app.chain.tmdb import TmdbChain +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.helper.subscribe import SubscribeHelper +from app.schemas import MediaType +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 + + +class PopularSubscribe(_PluginBase): + # 插件名称 + plugin_name = "热门媒体订阅" + # 插件描述 + plugin_desc = "自定添加热门订阅。" + # 插件图标 + plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/popular.png" + # 插件版本 + plugin_version = "1.0" + # 插件作者 + plugin_author = "thsrite" + # 作者主页 + author_url = "https://github.com/thsrite" + # 插件配置项ID前缀 + plugin_config_prefix = "popularsubscribe_" + # 加载顺序 + plugin_order = 26 + # 可使用的用户级别 + auth_level = 2 + + # 私有属性 + _movie_enabled: bool = False + _tv_enabled: bool = False + # 一页多少条数据 + _movie_page_cnt: int = 30 + _tv_page_cnt: int = 30 + # 流行度最低多少 + _movie_popular_cnt: int = 0 + _tv_popula_cnt: int = 0 + _movie_cron: str = "" + _tv_cron: str = "" + + subscribechain = None + _scheduler: Optional[BackgroundScheduler] = None + + def init_plugin(self, config: dict = None): + self.downloadchain = DownloadChain() + self.subscribechain = SubscribeChain() + # 停止现有任务 + self.stop_service() + + if config: + self._movie_enabled = config.get("movie_enabled") + self._tv_enabled = config.get("tv_enabled") + self._movie_cron = config.get("movie_cron") + self._tv_cron = config.get("tv_cron") + self._movie_page_cnt = config.get("movie_page_cnt") + self._tv_page_cnt = config.get("tv_page_cnt") + self._movie_popular_cnt = config.get("movie_popular_cnt") + self._tv_popula_cnt = config.get("tv_popula_cnt") + + if self._movie_enabled or self._tv_enabled: + # 定时服务 + self._scheduler = BackgroundScheduler(timezone=settings.TZ) + + if self._movie_cron: + try: + self._scheduler.add_job(func=self.__popular_subscribe, + trigger=CronTrigger.from_crontab(self._movie_cron), + name="电影热门订阅", + args=['电影', self._movie_page_cnt, self._movie_popula_cnt]) + except Exception as err: + logger.error(f"电影热门订阅定时任务配置错误:{err}") + # 推送实时消息 + self.systemmessage.put(f"电影热门订阅执行周期配置错误:{err}") + + if self._tv_cron: + try: + self._scheduler.add_job(func=self.__popular_subscribe, + trigger=CronTrigger.from_crontab(self._tv_cron), + name="电视剧热门订阅", + args=['电视剧', self._tv_page_cnt, self._tv_popula_cnt]) + except Exception as err: + logger.error(f"电视剧热门订阅定时任务配置错误:{err}") + # 推送实时消息 + self.systemmessage.put(f"电视剧热门订阅执行周期配置错误:{err}") + # 启动任务 + if self._scheduler.get_jobs(): + self._scheduler.print_jobs() + self._scheduler.start() + + def __popular_subscribe(self, stype, page_cnt, popular_cnt): + """ + 热门订阅 + """ + subscribes = SubscribeHelper().get_statistic(stype=stype, page=1, count=page_cnt) + if not subscribes: + logger.error(f"没有获取到{stype}热门订阅") + return + + history: List[dict] = self.get_data('history') or [] + + # 遍历热门订阅检查流行度是否达到要求 + for sub in subscribes: + media = MediaInfo() + media.type = MediaType(sub.get("type")) + media.title = sub.get("name") + media.year = sub.get("year") + media.tmdb_id = sub.get("tmdbid") + media.douban_id = sub.get("doubanid") + media.bangumi_id = sub.get("bangumiid") + media.tvdb_id = sub.get("tvdbid") + media.imdb_id = sub.get("imdbid") + media.season = sub.get("season") + media.poster_path = sub.get("poster") + + # 元数据 + meta = MetaInfo(media.title) + + # 查询缺失的媒体信息 + exist_flag, _ = self.downloadchain.get_no_exists_info(meta=meta, mediainfo=media) + if exist_flag: + logger.info(f'{media.title_year} 媒体库中已存在') + continue + + # 判断用户是否已经添加订阅 + if self.subscribechain.exists(mediainfo=media): + logger.info(f'{media.title_year} 订阅已存在') + continue + + # 添加订阅 + self.subscribechain.add(title=media.title, + year=media.year, + mtype=media.type, + tmdbid=media.tmdb_id, + doubanid=media.douban_id, + exist_ok=True, + username=settings.SUPERUSER) + logger.info(f'{media.title_year} 添加订阅') + + # 存储历史记录 + history.append({ + "title": media.title, + "type": media.type.value, + "year": media.year, + "poster": media.get_poster_image(), + "overview": media.overview, + "tmdbid": media.tmdb_id, + "doubanid": media.douban_id, + "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + }) + + # 保存历史记录 + self.save_data('history', history) + + def get_state(self) -> bool: + return self._movie_enabled or self._tv_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': 3 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'movie_enabled', + 'label': '电影热门订阅', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'movie_cron', + 'label': '电影订阅周期', + 'placeholder': '5位cron表达式,留空自动' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'movie_page_cnt', + 'label': '电影获取条数', + 'placeholder': '30' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'movie_popular_cnt', + 'label': '电影流行指数', + 'placeholder': '0' + } + } + ] + }, + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'tv_enabled', + 'label': '电视剧热门订阅', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'tv_cron', + 'label': '电视剧订阅周期', + 'placeholder': '5位cron表达式,留空自动' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'tv_page_cnt', + 'label': '电视剧获取条数', + 'placeholder': '30' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'tv_popular_cnt', + 'label': '电视剧流行指数', + 'placeholder': '0' + } + } + ] + }, + ] + }, + ] + } + ], { + "movie_enabled": False, + "tv_enabled": False, + "movie_cron": "5 1 * * *", + "tv_cron": "5 1 * * *", + "movie_page_cnt": "", + "tv_page_cnt": "", + "movie_popular_cnt": "", + "tv_popular_cnt": "", + } + + def get_page(self) -> List[dict]: + """ + 拼装插件详情页面,需要返回页面配置,同时附带数据 + """ + # 查询历史记录 + 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): + """ + 退出插件 + """ + 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))