From ae5b770fd6c03f0997b8598e18bb4a178404871f Mon Sep 17 00:00:00 2001 From: thsrite Date: Wed, 17 Jul 2024 13:38:12 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E6=BC=94=E5=91=98=E4=BD=9C=E5=93=81?= =?UTF-8?q?=E8=AE=A2=E9=98=85v1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + icons/actorsubscribeplus.png | Bin 0 -> 7136 bytes package.json | 12 + plugins/actorsubscribeplus/__init__.py | 574 +++++++++++++++++++++++++ 4 files changed, 587 insertions(+) create mode 100644 icons/actorsubscribeplus.png create mode 100644 plugins/actorsubscribeplus/__init__.py diff --git a/README.md b/README.md index f6845e6..1a5efad 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,4 @@ MoviePilot三方插件市场:https://github.com/thsrite/MoviePilot-Plugins/ - 软连接重定向 v1.1 - 云盘同步删除 v1.4 - 媒体库重复媒体检测 v1.9 +- 演员作品订阅 v1.0 diff --git a/icons/actorsubscribeplus.png b/icons/actorsubscribeplus.png new file mode 100644 index 0000000000000000000000000000000000000000..0bdac45559a8c182b43e58ac771bb34ed3533dbe GIT binary patch literal 7136 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 List[dict]: + """ + 获取豆瓣演员信息 + """ + sleep_time = 3 + int(time.time()) % 7 + logger.debug(f"随机休眠 {sleep_time}秒 ...") + time.sleep(sleep_time) + if mediainfo.douban_id: + doubanitem = DoubanChain().douban_info(mediainfo.douban_id) or {} + else: + # 匹配豆瓣信息 + doubaninfo = DoubanChain().match_doubaninfo(name=mediainfo.title, + imdbid=mediainfo.imdb_id, + mtype=mediainfo.type, + year=mediainfo.year, + season=season) + # 豆瓣演员 + if doubaninfo: + mediainfo.douban_id = doubaninfo.get("id") + doubanitem = DoubanChain().douban_info(doubaninfo.get("id")) or {} + else: + doubanitem = None + + if doubanitem: + actors = (doubanitem.get("actors") or []) + (doubanitem.get("directors") or []) + return [actor.get("name") for actor in actors] + else: + logger.debug(f"未找到豆瓣信息:{mediainfo.title_year}") + return [] + + def __update_config(self): + self.update_config({ + "enabled": self._enabled, + "onlyonce": self._onlyonce, + "cron": self._cron, + "actors": self._actors, + "clear": self._clear, + "clear_already_handle": self._clear_already_handle, + "mtype": self._mtype, + }) + + def delete_history(self, key: str, apikey: str): + """ + 删除同步历史记录 + """ + if apikey != settings.API_TOKEN: + return schemas.Response(success=False, message="API密钥错误") + # 历史记录 + historys = self.get_data('history') + if not historys: + return schemas.Response(success=False, message="未找到历史记录") + # 删除指定记录 + historys = [h for h in historys if h.get("unique") != key] + self.save_data('history', historys) + return schemas.Response(success=True, message="删除成功") + + 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]]: + return [ + { + "path": "/delete_history", + "endpoint": self.delete_history, + "methods": ["GET"], + "summary": "删除订阅历史记录" + } + ] + + 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': 'enabled', + 'label': '启用插件', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'onlyonce', + 'label': '立即运行一次', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'clear', + 'label': '清理订阅记录', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'clear_already_handle', + 'label': '清理已处理记录', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'cron', + 'label': '执行周期', + 'placeholder': '5位cron表达式,留空自动' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'multiple': True, + 'chips': True, + 'model': 'mtype', + 'label': '订阅类型', + 'items': [ + {'title': '电影', 'value': '电影'}, + {'title': '电视剧', 'value': '电视剧'}, + ] + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'actors', + 'label': '明星', + 'placeholder': '多个英文逗号分割' + } + } + ] + }, + ] + } + ] + } + ], { + "enabled": False, + "onlyonce": False, + "cron": "5 1 * * *", + "actors": "", + "clear": False, + "clear_already_handle": False, + "mtype": ['电影', '电视剧'], + } + + 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") + tmdbid = history.get("tmdbid") + doubanid = history.get("doubanid") + contents.append( + { + 'component': 'VCard', + 'content': [ + { + "component": "VDialogCloseBtn", + "props": { + 'innerClass': 'absolute top-0 right-0', + }, + 'events': { + 'click': { + 'api': 'plugin/ActorSubscribePlus/delete_history', + 'method': 'get', + 'params': { + 'key': f"actorsubscribeplus: {title} (DB:{tmdbid})", + 'apikey': settings.API_TOKEN + } + } + }, + }, + { + '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))