From a719aa7f55e86298d8a5d9ab6fb7f6fed0366913 Mon Sep 17 00:00:00 2001 From: thsrite Date: Fri, 16 Aug 2024 15:30:06 +0800 Subject: [PATCH] =?UTF-8?q?feat=20Emby=E6=9C=89=E5=A3=B0=E4=B9=A6=E6=95=B4?= =?UTF-8?q?=E7=90=86=20v1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + icons/audiobook.png | Bin 0 -> 6168 bytes package.json | 12 + plugins/embyaudiobook/__init__.py | 402 ++++++++++++++++++++++++++++++ 4 files changed, 415 insertions(+) create mode 100644 icons/audiobook.png create mode 100644 plugins/embyaudiobook/__init__.py diff --git a/README.md b/README.md index 8f1fb14..874f55f 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,4 @@ MoviePilot三方插件市场:https://github.com/thsrite/MoviePilot-Plugins/ - Emby合集媒体排序 v1.1 - 影视将映订阅 v1.1 - Emby视频类型检查 v1.0 +- Emby有声书整理 v1.0 diff --git a/icons/audiobook.png b/icons/audiobook.png new file mode 100644 index 0000000000000000000000000000000000000000..a1f622acada2c444340e6444e22d8e4f7843466e GIT binary patch literal 6168 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 J bool: + return self._enabled + + def __get_items(self, parent_id) -> list: + """ + 获取有声书剧集 + """ + if not self._EMBY_HOST or not self._EMBY_APIKEY: + return [] + req_url = f"%semby/Users/%s/Items?ParentId=%s&api_key=%s" % ( + self._EMBY_HOST, self._EMBY_USER, parent_id, self._EMBY_APIKEY) + try: + with RequestUtils().get_res(req_url) as res: + if res: + return res.json().get("Items") + else: + logger.info(f"获取有声书剧集失败,无法连接Emby!") + return [] + except Exception as e: + logger.error(f"连接有声书Items出错:" + str(e)) + return [] + + def __get_audiobook_item_info(self, item_id) -> dict: + """ + 获取有声书剧集详情 + """ + if not self._EMBY_HOST or not self._EMBY_APIKEY: + return {} + req_url = f"%semby/Users/%s/Items/%s?fields=ShareLevel&ExcludeFields=Chapters,Overview,People,MediaStreams,Subviews&api_key=%s" % ( + self._EMBY_HOST, self._EMBY_USER, item_id, self._EMBY_APIKEY) + try: + with RequestUtils().get_res(req_url) as res: + if res: + return res.json() + else: + logger.info(f"获取有声书剧集详情失败,无法连接Emby!") + return {} + except Exception as e: + logger.error(f"连接有声书详情Items出错:" + str(e)) + return {} + + def __update_item_info(self, item_id, data): + headers = { + 'accept': '*/*', + 'Content-Type': 'application/json' + } + res = RequestUtils(headers=headers).post( + f"{self._EMBY_HOST}/emby/Items/{item_id}?api_key={self._EMBY_APIKEY}", + data=json.dumps(data)) + if res and res.status_code == 204: + return True + return False + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + return [ + { + "cmd": "/ab", + "event": EventType.PluginAction, + "desc": "emby有声书整理", + "category": "", + "data": { + "action": "audiobook" + } + } + ] + + def get_api(self) -> List[Dict[str, Any]]: + pass + + def get_service(self) -> List[Dict[str, Any]]: + pass + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + 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': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enabled', + 'label': '启用插件', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'notify', + 'label': '开启通知', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'rename', + 'label': '重命名有声书', + } + } + ] + }, + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'multiple': False, + 'chips': True, + 'model': 'msgtype', + 'label': '消息类型', + 'items': MsgTypeOptions + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'library_id', + 'label': '有声书文件夹ID', + 'placeholder': '媒体库有声书-->文件夹-->看URL里的ParentId' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': '仅支持交互命令运行: /ab 书名 正确信息集数。' + } + } + ] + } + ] + } + ] + } + ], { + "enabled": False, + "notify": False, + "rename": False, + "extend": "", + "library_id": "", + } + + def get_page(self) -> List[dict]: + pass + + def stop_service(self): + pass