diff --git a/app/api/endpoints/system.py b/app/api/endpoints/system.py index 17ef2b83..b194cac0 100644 --- a/app/api/endpoints/system.py +++ b/app/api/endpoints/system.py @@ -197,33 +197,6 @@ def get_logging(token: str, length: int = 50, logfile: str = "moviepilot.log"): return StreamingResponse(log_generator(), media_type="text/event-stream") -@router.get("/nettest", summary="测试网络连通性") -def nettest(url: str, - proxy: bool, - _: schemas.TokenPayload = Depends(verify_token)): - """ - 测试网络连通性 - """ - # 记录开始的毫秒数 - start_time = datetime.now() - url = url.replace("{TMDBAPIKEY}", settings.TMDB_API_KEY) - result = RequestUtils(proxies=settings.PROXY if proxy else None, - ua=settings.USER_AGENT).get_res(url) - # 计时结束的毫秒数 - end_time = datetime.now() - # 计算相关秒数 - if result and result.status_code == 200: - return schemas.Response(success=True, data={ - "time": round((end_time - start_time).microseconds / 1000) - }) - elif result: - return schemas.Response(success=False, message=f"错误码:{result.status_code}", data={ - "time": round((end_time - start_time).microseconds / 1000) - }) - else: - return schemas.Response(success=False, message="网络连接失败!") - - @router.get("/versions", summary="查询Github所有Release版本", response_model=schemas.Response) def latest_version(_: schemas.TokenPayload = Depends(verify_token)): """ @@ -269,6 +242,53 @@ def ruletest(title: str, }) +@router.get("/nettest", summary="测试网络连通性") +def nettest(url: str, + proxy: bool, + _: schemas.TokenPayload = Depends(verify_token)): + """ + 测试网络连通性 + """ + # 记录开始的毫秒数 + start_time = datetime.now() + url = url.replace("{TMDBAPIKEY}", settings.TMDB_API_KEY) + result = RequestUtils(proxies=settings.PROXY if proxy else None, + ua=settings.USER_AGENT).get_res(url) + # 计时结束的毫秒数 + end_time = datetime.now() + # 计算相关秒数 + if result and result.status_code == 200: + return schemas.Response(success=True, data={ + "time": round((end_time - start_time).microseconds / 1000) + }) + elif result: + return schemas.Response(success=False, message=f"错误码:{result.status_code}", data={ + "time": round((end_time - start_time).microseconds / 1000) + }) + else: + return schemas.Response(success=False, message="网络连接失败!") + + +@router.get("/modulelist", summary="查询已加载的模块ID列表", response_model=schemas.Response) +def modulelist(_: schemas.TokenPayload = Depends(verify_token)): + """ + 查询已加载的模块ID列表 + """ + module_ids = [module.__name__ for module in ModuleManager().get_modules("test")] + return schemas.Response(success=True, data={ + "ids": module_ids + }) + + +@router.get("/moduletest/{moduleid}", summary="模块可用性测试", response_model=schemas.Response) +def moduletest(moduleid: str, _: schemas.TokenPayload = Depends(verify_token)): + """ + 模块可用性测试接口 + """ + state, errmsg = ModuleManager().test(moduleid) + return schemas.Response(success=state, message=errmsg) + + @router.get("/restart", summary="重启系统", response_model=schemas.Response) def restart_system(_: schemas.TokenPayload = Depends(verify_token)): """ diff --git a/app/core/module.py b/app/core/module.py index 92f703d5..1f4e4c47 100644 --- a/app/core/module.py +++ b/app/core/module.py @@ -1,4 +1,4 @@ -from typing import Generator, Optional +from typing import Generator, Optional, Tuple from app.core.config import settings from app.helper.module import ModuleHelper @@ -51,6 +51,18 @@ class ModuleManager(metaclass=Singleton): if hasattr(module, "stop"): module.stop() + def test(self, modleid: str) -> Tuple[bool, str]: + """ + 测试模块 + """ + if modleid not in self._running_modules: + return False, "模块未加载,请检查参数设置" + module = self._running_modules[modleid] + if hasattr(module, "test") \ + and ObjectUtils.check_method(getattr(module, "test")): + return module.test() + return True, "模块不支持测试" + @staticmethod def check_setting(setting: Optional[tuple]) -> bool: """ diff --git a/app/modules/__init__.py b/app/modules/__init__.py index 7116fd5b..ae8fa8bf 100644 --- a/app/modules/__init__.py +++ b/app/modules/__init__.py @@ -35,6 +35,13 @@ class _ModuleBase(metaclass=ABCMeta): """ pass + @abstractmethod + def test(self) -> Tuple[bool, str]: + """ + 模块测试, 返回测试结果和错误信息 + """ + pass + def checkMessage(channel_type: MessageChannel): """ diff --git a/app/modules/douban/__init__.py b/app/modules/douban/__init__.py index 9ac74071..ee7639c5 100644 --- a/app/modules/douban/__init__.py +++ b/app/modules/douban/__init__.py @@ -13,6 +13,7 @@ from app.modules.douban.douban_cache import DoubanCache from app.modules.douban.scraper import DoubanScraper from app.schemas.types import MediaType from app.utils.common import retry +from app.utils.http import RequestUtils from app.utils.system import SystemUtils @@ -29,6 +30,17 @@ class DoubanModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + ret = RequestUtils().get_res("https://movie.douban.com/") + if ret and ret.status_code == 200: + return True, "" + elif ret: + return False, f"无法连接豆瓣,错误码:{ret.status_code}" + return False, "豆瓣网络连接失败" + def init_setting(self) -> Tuple[str, Union[str, bool]]: pass diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index 22241e53..7adcd086 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -17,6 +17,16 @@ class EmbyModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + if self.emby.is_inactive(): + self.emby.reconnect() + if self.emby.is_inactive(): + return False, "无法连接Emby,请检查参数配置" + return True, "" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "MEDIASERVER", "emby" diff --git a/app/modules/fanart/__init__.py b/app/modules/fanart/__init__.py index 2640d1a0..28ba766d 100644 --- a/app/modules/fanart/__init__.py +++ b/app/modules/fanart/__init__.py @@ -317,6 +317,17 @@ class FanartModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + ret = RequestUtils().get_res("https://webservice.fanart.tv") + if ret and ret.status_code == 200: + return True, "" + elif ret: + return False, f"无法连接fanart,错误码:{ret.status_code}" + return False, "fanart网络连接失败" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "FANART_API_KEY", True diff --git a/app/modules/filetransfer/__init__.py b/app/modules/filetransfer/__init__.py index d9c3c234..6c8a56b5 100644 --- a/app/modules/filetransfer/__init__.py +++ b/app/modules/filetransfer/__init__.py @@ -27,6 +27,9 @@ class FileTransferModule(_ModuleBase): def stop(self): pass + def test(self): + pass + def init_setting(self) -> Tuple[str, Union[str, bool]]: pass diff --git a/app/modules/filter/__init__.py b/app/modules/filter/__init__.py index 8989c8e2..358773f5 100644 --- a/app/modules/filter/__init__.py +++ b/app/modules/filter/__init__.py @@ -132,6 +132,9 @@ class FilterModule(_ModuleBase): def stop(self): pass + def test(self): + pass + def init_setting(self) -> Tuple[str, Union[str, bool]]: pass diff --git a/app/modules/indexer/__init__.py b/app/modules/indexer/__init__.py index 9574c790..03130e55 100644 --- a/app/modules/indexer/__init__.py +++ b/app/modules/indexer/__init__.py @@ -4,6 +4,7 @@ from typing import List, Optional, Tuple, Union from ruamel.yaml import CommentedMap from app.core.context import TorrentInfo +from app.helper.sites import SitesHelper from app.log import logger from app.modules import _ModuleBase from app.modules.indexer.mtorrent import MTorrentSpider @@ -25,6 +26,15 @@ class IndexerModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + sites = SitesHelper().get_indexers() + if not sites: + return False, "未配置站点或未通过用户认证" + return True, "" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "INDEXER", "builtin" diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index e2b88279..13cd4b95 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -28,6 +28,16 @@ class JellyfinModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + if self.jellyfin.is_inactive(): + self.jellyfin.reconnect() + if self.jellyfin.is_inactive(): + return False, "无法连接Jellyfin,请检查参数配置" + return True, "" + def user_authenticate(self, name: str, password: str) -> Optional[str]: """ 使用Emby用户辅助完成用户认证 diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index cd146a0a..63b3a514 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -17,6 +17,16 @@ class PlexModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + if self.plex.is_inactive(): + self.plex.reconnect() + if self.plex.is_inactive(): + return False, "无法连接Plex,请检查参数配置" + return True, "" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "MEDIASERVER", "plex" diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index fafd0f5a..1e12b218 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -26,6 +26,16 @@ class QbittorrentModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + if self.qbittorrent.is_inactive(): + self.qbittorrent.reconnect() + if self.qbittorrent.is_inactive(): + return False, "无法连接Qbittorrent,请检查参数配置" + return True, "" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "DOWNLOADER", "qbittorrent" diff --git a/app/modules/slack/__init__.py b/app/modules/slack/__init__.py index 3e5f4da2..86e47a60 100644 --- a/app/modules/slack/__init__.py +++ b/app/modules/slack/__init__.py @@ -19,6 +19,15 @@ class SlackModule(_ModuleBase): def stop(self): self.slack.stop() + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + state = self.slack.get_state() + if state: + return True, "" + return False, "Slack未就续,请检查参数设置和网络连接" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "MESSAGER", "slack" diff --git a/app/modules/slack/slack.py b/app/modules/slack/slack.py index 3e81fb17..ef25ec4b 100644 --- a/app/modules/slack/slack.py +++ b/app/modules/slack/slack.py @@ -87,6 +87,12 @@ class Slack: except Exception as err: logger.error("Slack消息接收服务停止失败: %s" % str(err)) + def get_state(self) -> bool: + """ + 获取状态 + """ + return True if self._client else False + def send_msg(self, title: str, text: str = "", image: str = "", url: str = "", userid: str = ""): """ 发送Telegram消息 diff --git a/app/modules/subtitle/__init__.py b/app/modules/subtitle/__init__.py index 06397826..79364c01 100644 --- a/app/modules/subtitle/__init__.py +++ b/app/modules/subtitle/__init__.py @@ -34,6 +34,9 @@ class SubtitleModule(_ModuleBase): def stop(self) -> None: pass + def test(self): + pass + def download_added(self, context: Context, download_dir: Path, torrent_path: Path = None) -> None: """ 添加下载任务成功后,从站点下载字幕,保存到下载目录 diff --git a/app/modules/synologychat/__init__.py b/app/modules/synologychat/__init__.py index f95f5842..b628ed15 100644 --- a/app/modules/synologychat/__init__.py +++ b/app/modules/synologychat/__init__.py @@ -16,6 +16,15 @@ class SynologyChatModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + state = self.synologychat.get_state() + if state: + return True, "" + return False, "SynologyChat未就续,请检查参数设置、网络连接以及机器人是否可见" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "MESSAGER", "synologychat" diff --git a/app/modules/synologychat/synologychat.py b/app/modules/synologychat/synologychat.py index c62b0e4f..7cd988bd 100644 --- a/app/modules/synologychat/synologychat.py +++ b/app/modules/synologychat/synologychat.py @@ -25,6 +25,17 @@ class SynologyChat: def check_token(self, token: str) -> bool: return True if token == self._token else False + def get_state(self) -> bool: + """ + 获取状态 + """ + if not self._webhook_url or not self._token: + return False + ret = self.__get_bot_users() + if ret: + return True + return False + def send_msg(self, title: str, text: str = "", image: str = "", userid: str = "") -> Optional[bool]: """ 发送Telegram消息 diff --git a/app/modules/telegram/__init__.py b/app/modules/telegram/__init__.py index 03563c9e..0abad711 100644 --- a/app/modules/telegram/__init__.py +++ b/app/modules/telegram/__init__.py @@ -18,6 +18,15 @@ class TelegramModule(_ModuleBase): def stop(self): self.telegram.stop() + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + state = self.telegram.get_state() + if state: + return True, "" + return False, "Telegram未就续,请检查参数设置和网络连接" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "MESSAGER", "telegram" diff --git a/app/modules/telegram/telegram.py b/app/modules/telegram/telegram.py index 029d616c..8cbdc0a3 100644 --- a/app/modules/telegram/telegram.py +++ b/app/modules/telegram/telegram.py @@ -61,6 +61,12 @@ class Telegram: self._polling_thread.start() logger.info("Telegram消息接收服务启动") + def get_state(self) -> bool: + """ + 获取状态 + """ + return self._bot is not None + def send_msg(self, title: str, text: str = "", image: str = "", userid: str = "") -> Optional[bool]: """ 发送Telegram消息 diff --git a/app/modules/themoviedb/__init__.py b/app/modules/themoviedb/__init__.py index dbca0e76..c23f44a0 100644 --- a/app/modules/themoviedb/__init__.py +++ b/app/modules/themoviedb/__init__.py @@ -12,6 +12,7 @@ from app.modules.themoviedb.scraper import TmdbScraper from app.modules.themoviedb.tmdb_cache import TmdbCache from app.modules.themoviedb.tmdbapi import TmdbHelper from app.schemas.types import MediaType, MediaImageType +from app.utils.http import RequestUtils from app.utils.system import SystemUtils @@ -38,6 +39,17 @@ class TheMovieDbModule(_ModuleBase): def stop(self): self.cache.save() + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + ret = RequestUtils().get_res(f"https://{settings.TMDB_API_DOMAIN}/3/movie/550?api_key={settings.TMDB_API_KEY}") + if ret and ret.status_code == 200: + return True, "" + elif ret: + return False, f"无法连接 {settings.TMDB_API_DOMAIN},错误码:{ret.status_code}" + return False, f"{settings.TMDB_API_DOMAIN} 网络连接失败" + def init_setting(self) -> Tuple[str, Union[str, bool]]: pass diff --git a/app/modules/thetvdb/__init__.py b/app/modules/thetvdb/__init__.py index 26f92e80..750dc5db 100644 --- a/app/modules/thetvdb/__init__.py +++ b/app/modules/thetvdb/__init__.py @@ -5,6 +5,7 @@ from app.core.config import settings from app.log import logger from app.modules import _ModuleBase from app.modules.thetvdb import tvdbapi +from app.utils.http import RequestUtils class TheTvDbModule(_ModuleBase): @@ -20,6 +21,17 @@ class TheTvDbModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + ret = RequestUtils().get_res("https://api.thetvdb.com/series/81189") + if ret and ret.status_code == 200: + return True, "" + elif ret: + return False, f"无法连接 api.thetvdb.com,错误码:{ret.status_code}" + return False, "api.thetvdb.com 网络连接失败" + def init_setting(self) -> Tuple[str, Union[str, bool]]: pass diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index 3cc5c452..357b6e67 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -26,6 +26,16 @@ class TransmissionModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + if self.transmission.is_inactive(): + self.transmission.reconnect() + if self.transmission.is_inactive(): + return False, "无法连接Transmission,请检查参数配置" + return True, "" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "DOWNLOADER", "transmission" diff --git a/app/modules/wechat/__init__.py b/app/modules/wechat/__init__.py index 52045a33..2f7407a1 100644 --- a/app/modules/wechat/__init__.py +++ b/app/modules/wechat/__init__.py @@ -20,6 +20,15 @@ class WechatModule(_ModuleBase): def stop(self): pass + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + state = self.wechat.get_state() + if state: + return True, "" + return False, "获取微信token失败" + def init_setting(self) -> Tuple[str, Union[str, bool]]: return "MESSAGER", "wechat" diff --git a/app/modules/wechat/wechat.py b/app/modules/wechat/wechat.py index a97735dc..275ed91f 100644 --- a/app/modules/wechat/wechat.py +++ b/app/modules/wechat/wechat.py @@ -47,6 +47,12 @@ class WeChat: if self._corpid and self._appsecret and self._appid: self.__get_access_token() + def get_state(self): + """ + 获取状态 + """ + return True if self.__get_access_token else False + @retry(Exception, logger=logger) def __get_access_token(self, force=False): """