from pathlib import Path from app.chain.dashboard import DashboardChain from app.core.config import settings from app.db.subscribe_oper import SubscribeOper from app.helper.directory import DirectoryHelper from app.plugins import _PluginBase from typing import Any, List, Dict, Tuple, Optional from app.schemas import NotificationType from app import schemas from app.utils.string import StringUtils from app.utils.system import SystemUtils class HomePage(_PluginBase): # 插件名称 plugin_name = "HomePage" # 插件描述 plugin_desc = "HomePage自定义API。" # 插件图标 plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/homepage.png" # 插件版本 plugin_version = "1.2" # 插件作者 plugin_author = "thsrite" # 作者主页 author_url = "https://github.com/thsrite" # 插件配置项ID前缀 plugin_config_prefix = "homepage_" # 加载顺序 plugin_order = 30 # 可使用的用户级别 auth_level = 1 # 任务执行间隔 _enabled = False def init_plugin(self, config: dict = None): if config: self._enabled = config.get("enabled") def get_state(self) -> bool: return self._enabled def statistic(self, apikey: str) -> Any: """ 订阅、剩余空间等信息 """ if apikey != settings.API_TOKEN: return schemas.Response(success=False, message="API密钥错误") # 媒体统计 movie_count = 0 tv_count = 0 episode_count = 0 user_count = 0 media_statistics: Optional[List[schemas.Statistic]] = DashboardChain().media_statistic() if media_statistics: # 汇总各媒体库统计信息 for media_statistic in media_statistics: movie_count += media_statistic.movie_count tv_count += media_statistic.tv_count episode_count += media_statistic.episode_count user_count += media_statistic.user_count # 磁盘统计 library_dirs = DirectoryHelper().get_library_dirs() total_storage, free_storage = SystemUtils.space_usage([Path(d.path) for d in library_dirs if d.path]) # 订阅统计 movie_subscribes = 0 tv_subscribes = 0 subscribes = SubscribeOper().list() for subscribe in subscribes: if str(subscribe.type) == '电影': movie_subscribes += 1 else: tv_subscribes += 1 return { 'movie_count': movie_count, 'tv_count': tv_count, 'episode_count': episode_count, 'user_count': user_count, 'total_storage': StringUtils.str_filesize(total_storage), 'free_storage': StringUtils.str_filesize(free_storage), 'used_storage': StringUtils.str_filesize(total_storage - free_storage), 'movie_subscribes': movie_subscribes, 'tv_subscribes': tv_subscribes, } @staticmethod def get_command() -> List[Dict[str, Any]]: pass def get_api(self) -> List[Dict[str, Any]]: """ 获取插件API [{ "path": "/xx", "endpoint": self.xxx, "methods": ["GET", "POST"], "summary": "API说明" }] """ return [{ "path": "/statistic", "endpoint": self.statistic, "methods": ["GET"], "summary": "数据统计", "description": "订阅数量等统计数量", }] def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 """ # 编历 NotificationType 枚举,生成消息类型选项 MsgTypeOptions = [] for item in NotificationType: MsgTypeOptions.append({ "title": item.value, "value": item.name }) 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': 'VAlert', 'props': { 'type': 'success', 'variant': 'tonal' }, 'content': [ { 'component': 'span', 'text': '配置教程请参考:' }, { 'component': 'a', 'props': { 'href': 'https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/docs/HomePage.md', 'target': '_blank' }, 'text': 'https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/docs/HomePage.md' } ] } ] } ] }, { 'component': 'VRow', 'content': [ { 'component': 'VCol', 'props': { 'cols': 12, }, 'content': [ { 'component': 'VAlert', 'props': { 'type': 'info', 'variant': 'tonal', 'text': '如安装完启用插件后,HomePage提示404,重启MoviePilot即可。' } } ] } ] } ] } ], { "enabled": False, } def get_page(self) -> List[dict]: dict = self.statistic(settings.API_TOKEN) # 拼装页面 return [ { 'component': 'VRow', 'content': [ { 'component': 'VCol', 'props': { 'cols': 12, 'md': 3, 'sm': 6 }, 'content': [ { 'component': 'VCard', 'props': { 'variant': 'tonal', }, 'content': [ { 'component': 'VCardText', 'props': { 'class': 'd-flex align-center', }, 'content': [ { 'component': 'div', 'content': [ { 'component': 'span', 'props': { 'class': 'text-caption' }, 'text': '电影订阅' }, { 'component': 'div', 'props': { 'class': 'd-flex align-center flex-wrap' }, 'content': [ { 'component': 'span', 'props': { 'class': 'text-h6' }, 'text': dict.get('movie_subscribes') } ] } ] } ] } ] }, ] }, { 'component': 'VCol', 'props': { 'cols': 12, 'md': 3, 'sm': 6 }, 'content': [ { 'component': 'VCard', 'props': { 'variant': 'tonal', }, 'content': [ { 'component': 'VCardText', 'props': { 'class': 'd-flex align-center', }, 'content': [ { 'component': 'div', 'content': [ { 'component': 'span', 'props': { 'class': 'text-caption' }, 'text': '电视剧订阅' }, { 'component': 'div', 'props': { 'class': 'd-flex align-center flex-wrap' }, 'content': [ { 'component': 'span', 'props': { 'class': 'text-h6' }, 'text': dict.get('tv_subscribes') } ] } ] } ] } ] }, ] }, { 'component': 'VCol', 'props': { 'cols': 12, 'md': 3, 'sm': 6 }, 'content': [ { 'component': 'VCard', 'props': { 'variant': 'tonal', }, 'content': [ { 'component': 'VCardText', 'props': { 'class': 'd-flex align-center', }, 'content': [ { 'component': 'div', 'content': [ { 'component': 'span', 'props': { 'class': 'text-caption' }, 'text': '总空间' }, { 'component': 'div', 'props': { 'class': 'd-flex align-center flex-wrap' }, 'content': [ { 'component': 'span', 'props': { 'class': 'text-h6' }, 'text': dict.get('total_storage') } ] } ] } ] } ] }, ] }, { 'component': 'VCol', 'props': { 'cols': 12, 'md': 3, 'sm': 6 }, 'content': [ { 'component': 'VCard', 'props': { 'variant': 'tonal', }, 'content': [ { 'component': 'VCardText', 'props': { 'class': 'd-flex align-center', }, 'content': [ { 'component': 'div', 'content': [ { 'component': 'span', 'props': { 'class': 'text-caption' }, 'text': '剩余空间' }, { 'component': 'div', 'props': { 'class': 'd-flex align-center flex-wrap' }, 'content': [ { 'component': 'span', 'props': { 'class': 'text-h6' }, 'text': dict.get('free_storage') } ] } ] } ] } ] }, ] }, { 'component': 'VCol', 'props': { 'cols': 12, 'md': 3, 'sm': 6 }, 'content': [ { 'component': 'VCard', 'props': { 'variant': 'tonal', }, 'content': [ { 'component': 'VCardText', 'props': { 'class': 'd-flex align-center', }, 'content': [ { 'component': 'div', 'content': [ { 'component': 'span', 'props': { 'class': 'text-caption' }, 'text': '电影数量' }, { 'component': 'div', 'props': { 'class': 'd-flex align-center flex-wrap' }, 'content': [ { 'component': 'span', 'props': { 'class': 'text-h6' }, 'text': dict.get('movie_count') } ] } ] } ] } ] }, ] }, { 'component': 'VCol', 'props': { 'cols': 12, 'md': 3, 'sm': 6 }, 'content': [ { 'component': 'VCard', 'props': { 'variant': 'tonal', }, 'content': [ { 'component': 'VCardText', 'props': { 'class': 'd-flex align-center', }, 'content': [ { 'component': 'div', 'content': [ { 'component': 'span', 'props': { 'class': 'text-caption' }, 'text': '电视剧数量' }, { 'component': 'div', 'props': { 'class': 'd-flex align-center flex-wrap' }, 'content': [ { 'component': 'span', 'props': { 'class': 'text-h6' }, 'text': dict.get('tv_count') } ] } ] } ] } ] } ] }, { 'component': 'VCol', 'props': { 'cols': 12, 'md': 3, 'sm': 6 }, 'content': [ { 'component': 'VCard', 'props': { 'variant': 'tonal', }, 'content': [ { 'component': 'VCardText', 'props': { 'class': 'd-flex align-center', }, 'content': [ { 'component': 'div', 'content': [ { 'component': 'span', 'props': { 'class': 'text-caption' }, 'text': '电影剧集数量' }, { 'component': 'div', 'props': { 'class': 'd-flex align-center flex-wrap' }, 'content': [ { 'component': 'span', 'props': { 'class': 'text-h6' }, 'text': dict.get('episode_count') } ] } ] } ] } ] } ] }, { 'component': 'VCol', 'props': { 'cols': 12, 'md': 3, 'sm': 6 }, 'content': [ { 'component': 'VCard', 'props': { 'variant': 'tonal', }, 'content': [ { 'component': 'VCardText', 'props': { 'class': 'd-flex align-center', }, 'content': [ { 'component': 'div', 'content': [ { 'component': 'span', 'props': { 'class': 'text-caption' }, 'text': '用户数量' }, { 'component': 'div', 'props': { 'class': 'd-flex align-center flex-wrap' }, 'content': [ { 'component': 'span', 'props': { 'class': 'text-h6' }, 'text': dict.get('user_count') } ] } ] } ] } ] } ] } ] }] def stop_service(self): """ 退出插件 """ pass