diff --git a/app/chain/dashboard.py b/app/chain/dashboard.py index 033c0f03..8b7ddd86 100644 --- a/app/chain/dashboard.py +++ b/app/chain/dashboard.py @@ -9,14 +9,14 @@ class DashboardChain(ChainBase, metaclass=Singleton): """ 各类仪表板统计处理链 """ - def media_statistic(self) -> Optional[List[schemas.Statistic]]: + def media_statistic(self, server: str = None) -> Optional[List[schemas.Statistic]]: """ 媒体数量统计 """ - return self.run_module("media_statistic") + return self.run_module("media_statistic", server=server) - def downloader_info(self) -> Optional[List[schemas.DownloaderInfo]]: + def downloader_info(self, downloader: str = None) -> Optional[List[schemas.DownloaderInfo]]: """ 下载器信息 """ - return self.run_module("downloader_info") + return self.run_module("downloader_info", downloader=downloader) diff --git a/app/helper/downloader.py b/app/helper/downloader.py index 0c5c17e2..e7bf14fc 100644 --- a/app/helper/downloader.py +++ b/app/helper/downloader.py @@ -1,4 +1,7 @@ +from typing import List + from app.db.systemconfig_oper import SystemConfigOper +from app.schemas.system import DownloaderConf from app.schemas.types import SystemConfigKey @@ -10,11 +13,11 @@ class DownloaderHelper: def __init__(self): self.systemconfig = SystemConfigOper() - def get_downloaders(self) -> dict: + def get_downloaders(self) -> List[DownloaderConf]: """ 获取下载器 """ - downloader_conf: dict = self.systemconfig.get(SystemConfigKey.Downloaders) - if not downloader_conf: - return {} - return downloader_conf + downloader_confs: List[dict] = self.systemconfig.get(SystemConfigKey.Downloaders) + if not downloader_confs: + return [] + return [DownloaderConf(**conf) for conf in downloader_confs] diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index c3e4c81b..43b00ae1 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -21,7 +21,6 @@ class EmbyModule(_ModuleBase): mediaservers = MediaServerHelper().get_mediaservers() if not mediaservers: return - # 读取Emby配置 for server in mediaservers: if server.type == "emby": self._servers[server.name] = Emby(**server.config) @@ -142,16 +141,24 @@ class EmbyModule(_ModuleBase): ) return None - def media_statistic(self) -> List[schemas.Statistic]: + def media_statistic(self, server: str = None) -> Optional[List[schemas.Statistic]]: """ 媒体数量统计 """ + if server: + server_obj = self.get_server(server) + if not server_obj: + return None + servers = [server_obj] + else: + servers = self._servers.values() media_statistics = [] - for server in self._servers.values(): + for server in servers: media_statistic = server.get_medias_count() + if not media_statistics: + continue media_statistic.user_count = server.get_user_count() - if media_statistic: - media_statistics.append(media_statistic) + media_statistics.append(media_statistic) return media_statistics def mediaserver_librarys(self, server: str, diff --git a/app/modules/emby/emby.py b/app/modules/emby/emby.py index c7e5fc1b..fc8500c1 100644 --- a/app/modules/emby/emby.py +++ b/app/modules/emby/emby.py @@ -15,7 +15,10 @@ from app.utils.http import RequestUtils class Emby: - def __init__(self, host: str = None, play_host: str = None, apikey: str = None): + def __init__(self, host: str, apikey: str, play_host: str = None, **kwargs): + if not host or not apikey: + logger.error("Emby服务器配置不完整!") + return self._host = host if self._host: self._host = RequestUtils.standardize_base_url(self._host) diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index ba590f3e..bc94f09b 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -21,7 +21,6 @@ class JellyfinModule(_ModuleBase): mediaservers = MediaServerHelper().get_mediaservers() if not mediaservers: return - # 读取Jelly配置 for server in mediaservers: if server.type == "jellyfin": self._servers[server.name] = Jellyfin(**server.config) @@ -140,16 +139,24 @@ class JellyfinModule(_ModuleBase): ) return None - def media_statistic(self) -> List[schemas.Statistic]: + def media_statistic(self, server: str = None) -> Optional[List[schemas.Statistic]]: """ 媒体数量统计 """ + if server: + server_obj = self.get_server(server) + if not server_obj: + return None + servers = [server_obj] + else: + servers = self._servers.values() media_statistics = [] - for server in self._servers.values(): + for server in servers: media_statistic = server.get_medias_count() + if not media_statistics: + continue media_statistic.user_count = server.get_user_count() - if media_statistic: - media_statistics.append(media_statistic) + media_statistics.append(media_statistic) return media_statistics def mediaserver_librarys(self, server: str = None, diff --git a/app/modules/jellyfin/jellyfin.py b/app/modules/jellyfin/jellyfin.py index ab0495ba..d97d4ff3 100644 --- a/app/modules/jellyfin/jellyfin.py +++ b/app/modules/jellyfin/jellyfin.py @@ -12,7 +12,10 @@ from app.utils.http import RequestUtils class Jellyfin: - def __init__(self, host: str = None, play_host: str = None, apikey: str = None): + def __init__(self, host: str, apikey: str, play_host: str = None, **kwargs): + if not host or not apikey: + logger.error("Jellyfin服务器配置不完整!!") + return self._host = host if self._host: self._host = RequestUtils.standardize_base_url(self._host) diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index 8a4a1890..bbdb480f 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -21,7 +21,6 @@ class PlexModule(_ModuleBase): mediaservers = MediaServerHelper().get_mediaservers() if not mediaservers: return - # 读取Emby配置 for server in mediaservers: if server.type == "plex": self._servers[server.name] = Plex(**server.config) @@ -130,16 +129,23 @@ class PlexModule(_ModuleBase): ) return None - def media_statistic(self) -> List[schemas.Statistic]: + def media_statistic(self, server: str = None) -> Optional[List[schemas.Statistic]]: """ 媒体数量统计 """ + if server: + server_obj = self.get_server(server) + if not server_obj: + return None + servers = [server_obj] + else: + servers = self._servers.values() media_statistics = [] - for server in self._servers.values(): + for server in servers: media_statistic = server.get_medias_count() - media_statistic.user_count = 1 - if media_statistic: - media_statistics.append(media_statistic) + if not media_statistics: + continue + media_statistics.append(media_statistic) return media_statistics def mediaserver_librarys(self, server: str = None, **kwargs) -> Optional[List[schemas.MediaServerLibrary]]: diff --git a/app/modules/plex/plex.py b/app/modules/plex/plex.py index 3b2bd576..9bcbc41b 100644 --- a/app/modules/plex/plex.py +++ b/app/modules/plex/plex.py @@ -19,7 +19,10 @@ class Plex: _plex = None _session = None - def __init__(self, host: str = None, play_host: str = None, token: str = None): + def __init__(self, host: str, token: str, play_host: str = None, **kwargs): + if not host or not token: + logger.error("Plex服务器配置不完整!") + return self._host = host if self._host: self._host = RequestUtils.standardize_base_url(self._host) diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index a8821a7c..7de90a89 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -1,6 +1,6 @@ import shutil from pathlib import Path -from typing import Set, Tuple, Optional, Union, List +from typing import Set, Tuple, Optional, Union, List, Dict from qbittorrentapi import TorrentFilesList from torrentool.torrent import Torrent @@ -8,6 +8,7 @@ from torrentool.torrent import Torrent from app import schemas from app.core.config import settings from app.core.metainfo import MetaInfo +from app.helper.downloader import DownloaderHelper from app.log import logger from app.modules import _ModuleBase from app.modules.qbittorrent.qbittorrent import Qbittorrent @@ -18,15 +19,36 @@ from app.utils.system import SystemUtils class QbittorrentModule(_ModuleBase): - qbittorrent: Qbittorrent = None + _servers: Dict[str, Qbittorrent] = {} + _default_server: Qbittorrent = None def init_module(self) -> None: - self.qbittorrent = Qbittorrent() + """ + 初始化模块 + """ + # 读取下载器配置 + self._servers = {} + downloaders = DownloaderHelper().get_downloaders() + if not downloaders: + return + for server in downloaders: + if server.type == "qbittorrent": + self._servers[server.name] = Qbittorrent(**server.config) + if server.default: + self._default_server = self._servers[server.name] @staticmethod def get_name() -> str: return "Qbittorrent" + def get_server(self, name: str = None) -> Optional[Qbittorrent]: + """ + 获取服务器,name为空则返回默认服务器 + """ + if name: + return self._servers.get(name) + return self._default_server + def stop(self): pass @@ -34,26 +56,28 @@ class QbittorrentModule(_ModuleBase): """ 测试模块连接性 """ - if self.qbittorrent.is_inactive(): - self.qbittorrent.reconnect() - if not self.qbittorrent.transfer_info(): - return False, "无法获取Qbittorrent状态,请检查参数配置" + for name, server in self._servers.items(): + if server.is_inactive(): + server.reconnect() + if not server.transfer_info(): + return False, f"无法连接Qbittorrent下载器:{name}" return True, "" def init_setting(self) -> Tuple[str, Union[str, bool]]: - return "DOWNLOADER", "qbittorrent" + pass def scheduler_job(self) -> None: """ 定时任务,每10分钟调用一次 """ - # 定时重连 - if self.qbittorrent.is_inactive(): - self.qbittorrent.reconnect() + for name, server in self._servers.items(): + if server.is_inactive(): + logger.info(f"Qbittorrent下载器 {name} 连接断开,尝试重连 ...") + server.reconnect() def download(self, content: Union[Path, str], download_dir: Path, cookie: str, episodes: Set[int] = None, category: str = None, - downloader: str = settings.DEFAULT_DOWNLOADER) -> Optional[Tuple[Optional[str], str]]: + downloader: str = None) -> Optional[Tuple[Optional[str], str]]: """ 根据种子文件,选择并添加下载任务 :param content: 种子文件地址或者磁力链接 @@ -79,15 +103,17 @@ class QbittorrentModule(_ModuleBase): logger.error(f"获取种子名称失败:{e}") return "", 0 - # 不是默认下载器不处理 - if downloader != "qbittorrent": - return None - if not content: - return None + return None, "下载内容为空" if isinstance(content, Path) and not content.exists(): + logger.error(f"种子文件不存在:{content}") return None, f"种子文件不存在:{content}" + # 获取下载器 + server = self.get_server(downloader) + if not server: + return None + # 生成随机Tag tag = StringUtils.generate_random_str(10) if settings.TORRENT_TAG: @@ -97,7 +123,7 @@ class QbittorrentModule(_ModuleBase): # 如果要选择文件则先暂停 is_paused = True if episodes else False # 添加任务 - state = self.qbittorrent.add_torrent( + state = server.add_torrent( content=content.read_bytes() if isinstance(content, Path) else content, download_dir=str(download_dir), is_paused=is_paused, @@ -111,7 +137,7 @@ class QbittorrentModule(_ModuleBase): if not torrent_name: return None, f"添加种子任务失败:无法读取种子文件" # 查询所有下载器的种子 - torrents, error = self.qbittorrent.get_torrents() + torrents, error = server.get_torrents() if error: return None, "无法连接qbittorrent下载器" if torrents: @@ -123,21 +149,21 @@ class QbittorrentModule(_ModuleBase): logger.warn(f"下载器中已存在该种子任务:{torrent_hash} - {torrent.get('name')}") # 给种子打上标签 if "已整理" in torrent_tags: - self.qbittorrent.remove_torrents_tag(ids=torrent_hash, tag=['已整理']) + server.remove_torrents_tag(ids=torrent_hash, tag=['已整理']) if settings.TORRENT_TAG and settings.TORRENT_TAG not in torrent_tags: logger.info(f"给种子 {torrent_hash} 打上标签:{settings.TORRENT_TAG}") - self.qbittorrent.set_torrents_tag(ids=torrent_hash, tags=[settings.TORRENT_TAG]) + server.set_torrents_tag(ids=torrent_hash, tags=[settings.TORRENT_TAG]) return torrent_hash, f"下载任务已存在" return None, f"添加种子任务失败:{content}" else: # 获取种子Hash - torrent_hash = self.qbittorrent.get_torrent_id_by_tag(tags=tag) + torrent_hash = server.get_torrent_id_by_tag(tags=tag) if not torrent_hash: return None, f"下载任务添加成功,但获取Qbittorrent任务信息失败:{content}" else: if is_paused: # 种子文件 - torrent_files = self.qbittorrent.get_files(torrent_hash) + torrent_files = server.get_files(torrent_hash) if not torrent_files: return torrent_hash, "获取种子文件失败,下载任务可能在暂停状态" @@ -157,17 +183,17 @@ class QbittorrentModule(_ModuleBase): sucess_epidised = list(set(sucess_epidised).union(set(meta_info.episode_list))) if sucess_epidised and file_ids: # 选择文件 - self.qbittorrent.set_files(torrent_hash=torrent_hash, file_ids=file_ids, priority=0) + server.set_files(torrent_hash=torrent_hash, file_ids=file_ids, priority=0) # 开始任务 if settings.QB_FORCE_RESUME: # 强制继续 - self.qbittorrent.torrents_set_force_start(torrent_hash) + server.torrents_set_force_start(torrent_hash) else: - self.qbittorrent.start_torrents(torrent_hash) + server.start_torrents(torrent_hash) return torrent_hash, f"添加下载成功,已选择集数:{sucess_epidised}" else: if settings.QB_FORCE_RESUME: - self.qbittorrent.torrents_set_force_start(torrent_hash) + server.torrents_set_force_start(torrent_hash) return torrent_hash, "添加下载成功" def list_torrents(self, status: TorrentStatus = None, @@ -181,12 +207,15 @@ class QbittorrentModule(_ModuleBase): :param downloader: 下载器 :return: 下载器中符合状态的种子列表 """ - if downloader != "qbittorrent": + # 获取下载器 + server = self.get_server(downloader) + if not server: return None + ret_torrents = [] if hashs: # 按Hash获取 - torrents, _ = self.qbittorrent.get_torrents(ids=hashs, tags=settings.TORRENT_TAG) + torrents, _ = server.get_torrents(ids=hashs, tags=settings.TORRENT_TAG) for torrent in torrents or []: content_path = torrent.get("content_path") if content_path: @@ -202,7 +231,7 @@ class QbittorrentModule(_ModuleBase): )) elif status == TorrentStatus.TRANSFER: # 获取已完成且未整理的 - torrents = self.qbittorrent.get_completed_torrents(tags=settings.TORRENT_TAG) + torrents = server.get_completed_torrents(tags=settings.TORRENT_TAG) for torrent in torrents or []: tags = torrent.get("tags") or [] if "已整理" in tags: @@ -221,7 +250,7 @@ class QbittorrentModule(_ModuleBase): )) elif status == TorrentStatus.DOWNLOADING: # 获取正在下载的任务 - torrents = self.qbittorrent.get_downloading_torrents(tags=settings.TORRENT_TAG) + torrents = server.get_downloading_torrents(tags=settings.TORRENT_TAG) for torrent in torrents or []: meta = MetaInfo(torrent.get('name')) ret_torrents.append(DownloadingTorrent( @@ -251,9 +280,10 @@ class QbittorrentModule(_ModuleBase): :param path: 源目录 :param downloader: 下载器 """ - if downloader != "qbittorrent": - return - self.qbittorrent.set_torrents_tag(ids=hashs, tags=['已整理']) + server = self.get_server(downloader) + if not server: + return None + server.set_torrents_tag(ids=hashs, tags=['已整理']) # 移动模式删除种子 if settings.TRANSFER_TYPE in ["move", "rclone_move"]: if self.remove_torrents(hashs): @@ -274,9 +304,10 @@ class QbittorrentModule(_ModuleBase): :param downloader: 下载器 :return: bool """ - if downloader != "qbittorrent": + server = self.get_server(downloader) + if not server: return None - return self.qbittorrent.delete_torrents(delete_file=delete_file, ids=hashs) + return server.delete_torrents(delete_file=delete_file, ids=hashs) def start_torrents(self, hashs: Union[list, str], downloader: str = settings.DEFAULT_DOWNLOADER) -> Optional[bool]: @@ -286,9 +317,10 @@ class QbittorrentModule(_ModuleBase): :param downloader: 下载器 :return: bool """ - if downloader != "qbittorrent": + server = self.get_server(downloader) + if not server: return None - return self.qbittorrent.start_torrents(ids=hashs) + return server.start_torrents(ids=hashs) def stop_torrents(self, hashs: Union[list, str], downloader: str = settings.DEFAULT_DOWNLOADER) -> Optional[bool]: """ @@ -297,29 +329,41 @@ class QbittorrentModule(_ModuleBase): :param downloader: 下载器 :return: bool """ - if downloader != "qbittorrent": + server = self.get_server(downloader) + if not server: return None - return self.qbittorrent.stop_torrents(ids=hashs) + return server.stop_torrents(ids=hashs) def torrent_files(self, tid: str, downloader: str = settings.DEFAULT_DOWNLOADER) -> Optional[TorrentFilesList]: """ 获取种子文件列表 """ - if downloader != "qbittorrent": + server = self.get_server(downloader) + if not server: return None - return self.qbittorrent.get_files(tid=tid) + return server.get_files(tid=tid) - def downloader_info(self) -> [schemas.DownloaderInfo]: + def downloader_info(self, downloader: str = None) -> Optional[List[schemas.DownloaderInfo]]: """ 下载器信息 """ + if downloader: + server = self.get_server(downloader) + if not server: + return None + servers = [server] + else: + servers = self._servers.values() # 调用Qbittorrent API查询实时信息 - info = self.qbittorrent.transfer_info() - if not info: - return [schemas.DownloaderInfo()] - return [schemas.DownloaderInfo( - download_speed=info.get("dl_info_speed"), - upload_speed=info.get("up_info_speed"), - download_size=info.get("dl_info_data"), - upload_size=info.get("up_info_data") - )] + ret_info = [] + for server in servers: + info = server.transfer_info() + if not info: + continue + ret_info.append(schemas.DownloaderInfo( + download_speed=info.get("dl_info_speed"), + upload_speed=info.get("up_info_speed"), + download_size=info.get("dl_info_data"), + upload_size=info.get("up_info_data") + )) + return ret_info diff --git a/app/modules/qbittorrent/qbittorrent.py b/app/modules/qbittorrent/qbittorrent.py index 7941e848..aba0cf8f 100644 --- a/app/modules/qbittorrent/qbittorrent.py +++ b/app/modules/qbittorrent/qbittorrent.py @@ -18,7 +18,7 @@ class Qbittorrent: qbc: Client = None - def __init__(self, host: str = None, port: int = None, username: str = None, password: str = None): + def __init__(self, host: str = None, port: int = None, username: str = None, password: str = None, **kwargs): """ 若不设置参数,则创建配置文件设置的下载器 """ diff --git a/app/modules/slack/slack.py b/app/modules/slack/slack.py index 3ed1bb5c..055fd6c8 100644 --- a/app/modules/slack/slack.py +++ b/app/modules/slack/slack.py @@ -24,7 +24,11 @@ class Slack: _ds_url = f"http://127.0.0.1:{settings.PORT}/api/v1/message?token={settings.API_TOKEN}" _channel = "" - def __init__(self, oauth_token: str, app_token: str, channel: str = ""): + def __init__(self, oauth_token: str, app_token: str, channel: str = "", **kwargs): + + if not oauth_token or not app_token: + logger.error("Slack 配置不完整!") + return try: slack_app = App(token=oauth_token, diff --git a/app/modules/synologychat/synologychat.py b/app/modules/synologychat/synologychat.py index 84af58ba..7e98b128 100644 --- a/app/modules/synologychat/synologychat.py +++ b/app/modules/synologychat/synologychat.py @@ -1,10 +1,9 @@ import json import re +from threading import Lock from typing import Optional, List from urllib.parse import quote -from threading import Lock -from app.core.config import settings from app.core.context import MediaInfo, Context from app.core.metainfo import MetaInfo from app.log import logger @@ -15,10 +14,13 @@ lock = Lock() class SynologyChat: - def __init__(self): + def __init__(self, webhook: str, token: str, **kwargs): + if not webhook or not token: + logger.error("SynologyChat配置不完整!") + return self._req = RequestUtils(content_type="application/x-www-form-urlencoded") - self._webhook_url = settings.SYNOLOGYCHAT_WEBHOOK - self._token = settings.SYNOLOGYCHAT_TOKEN + self._webhook_url = webhook + self._token = token if self._webhook_url: self._domain = StringUtils.get_base_url(self._webhook_url) diff --git a/app/modules/telegram/telegram.py b/app/modules/telegram/telegram.py index 1e0847b2..405904c8 100644 --- a/app/modules/telegram/telegram.py +++ b/app/modules/telegram/telegram.py @@ -24,10 +24,13 @@ class Telegram: _event = Event() _bot: telebot.TeleBot = None - def __init__(self, token: str = None, chat_id: str = None): + def __init__(self, token: str, chat_id: str, **kwargs): """ 初始化参数 """ + if not token or not chat_id: + logger.error("Telegram配置不完整!") + return # Token self._telegram_token = token # Chat Id diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index 444dbee3..8a3ec90e 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -1,6 +1,6 @@ import shutil from pathlib import Path -from typing import Set, Tuple, Optional, Union, List +from typing import Set, Tuple, Optional, Union, List, Dict from torrentool.torrent import Torrent from transmission_rpc import File @@ -8,6 +8,7 @@ from transmission_rpc import File from app import schemas from app.core.config import settings from app.core.metainfo import MetaInfo +from app.helper.downloader import DownloaderHelper from app.log import logger from app.modules import _ModuleBase from app.modules.transmission.transmission import Transmission @@ -18,14 +19,32 @@ from app.utils.system import SystemUtils class TransmissionModule(_ModuleBase): - transmission: Transmission = None + _servers: Dict[str, Transmission] = {} + _default_server: Transmission = None def init_module(self) -> None: - self.transmission = Transmission() + # 读取下载器配置 + self._servers = {} + downloaders = DownloaderHelper().get_downloaders() + if not downloaders: + return + for server in downloaders: + if server.type == "transmission": + self._servers[server.name] = Transmission(**server.config) + if server.default: + self._default_server = self._servers[server.name] @staticmethod def get_name() -> str: return "Transmission" + + def get_server(self, name: str = None) -> Optional[Transmission]: + """ + 获取服务器,name为空则返回默认服务器 + """ + if name: + return self._servers.get(name) + return self._default_server def stop(self): pass @@ -34,22 +53,27 @@ class TransmissionModule(_ModuleBase): """ 测试模块连接性 """ - if self.transmission.is_inactive(): - self.transmission.reconnect() - if not self.transmission.transfer_info(): - return False, "无法获取Transmission状态,请检查参数配置" + if not self._servers: + return False, "未配置Transmission下载器" + for name, server in self._servers.items(): + if server.is_inactive(): + server.reconnect() + if not server.transfer_info(): + return False, f"无法连接Transmission下载器:{name}" return True, "" def init_setting(self) -> Tuple[str, Union[str, bool]]: - return "DOWNLOADER", "transmission" + pass def scheduler_job(self) -> None: """ 定时任务,每10分钟调用一次 """ # 定时重连 - if self.transmission.is_inactive(): - self.transmission.reconnect() + for name, server in self._servers.items(): + if server.is_inactive(): + logger.info(f"Transmission下载器 {name} 连接断开,尝试重连 ...") + server.reconnect() def download(self, content: Union[Path, str], download_dir: Path, cookie: str, episodes: Set[int] = None, category: str = None, @@ -79,15 +103,16 @@ class TransmissionModule(_ModuleBase): logger.error(f"获取种子名称失败:{e}") return "", 0 - # 不是默认下载器不处理 - if downloader != "transmission": - return None - if not content: - return None + return None, "下载内容为空" if isinstance(content, Path) and not content.exists(): return None, f"种子文件不存在:{content}" + # 获取下载器 + server = self.get_server(downloader) + if not server: + return None + # 如果要选择文件则先暂停 is_paused = True if episodes else False # 标签 @@ -96,7 +121,7 @@ class TransmissionModule(_ModuleBase): else: labels = None # 添加任务 - torrent = self.transmission.add_torrent( + torrent = server.add_torrent( content=content.read_bytes() if isinstance(content, Path) else content, download_dir=str(download_dir), is_paused=is_paused, @@ -109,7 +134,7 @@ class TransmissionModule(_ModuleBase): if not torrent_name: return None, f"添加种子任务失败:无法读取种子文件" # 查询所有下载器的种子 - torrents, error = self.transmission.get_torrents() + torrents, error = server.get_torrents() if error: return None, "无法连接transmission下载器" if torrents: @@ -126,17 +151,17 @@ class TransmissionModule(_ModuleBase): for tag in torrent.labels] if hasattr(torrent, "labels") else [] if "已整理" in labels: labels.remove("已整理") - self.transmission.set_torrent_tag(ids=torrent_hash, tags=labels) + server.set_torrent_tag(ids=torrent_hash, tags=labels) if settings.TORRENT_TAG and settings.TORRENT_TAG not in labels: labels.append(settings.TORRENT_TAG) - self.transmission.set_torrent_tag(ids=torrent_hash, tags=labels) + server.set_torrent_tag(ids=torrent_hash, tags=labels) return torrent_hash, f"下载任务已存在" return None, f"添加种子任务失败:{content}" else: torrent_hash = torrent.hashString if is_paused: # 选择文件 - torrent_files = self.transmission.get_files(torrent_hash) + torrent_files = server.get_files(torrent_hash) if not torrent_files: return torrent_hash, "获取种子文件失败,下载任务可能在暂停状态" # 需要的文件信息 @@ -155,10 +180,10 @@ class TransmissionModule(_ModuleBase): continue file_ids.append(file_id) # 选择文件 - self.transmission.set_files(torrent_hash, file_ids) - self.transmission.set_unwanted_files(torrent_hash, unwanted_file_ids) + server.set_files(torrent_hash, file_ids) + server.set_unwanted_files(torrent_hash, unwanted_file_ids) # 开始任务 - self.transmission.start_torrents(torrent_hash) + server.start_torrents(torrent_hash) return torrent_hash, "添加下载任务成功" else: return torrent_hash, "添加下载任务成功" @@ -174,12 +199,14 @@ class TransmissionModule(_ModuleBase): :param downloader: 下载器 :return: 下载器中符合状态的种子列表 """ - if downloader != "transmission": + # 获取下载器 + server = self.get_server(downloader) + if not server: return None ret_torrents = [] if hashs: # 按Hash获取 - torrents, _ = self.transmission.get_torrents(ids=hashs, tags=settings.TORRENT_TAG) + torrents, _ = server.get_torrents(ids=hashs, tags=settings.TORRENT_TAG) for torrent in torrents or []: ret_torrents.append(TransferTorrent( title=torrent.name, @@ -190,7 +217,7 @@ class TransmissionModule(_ModuleBase): )) elif status == TorrentStatus.TRANSFER: # 获取已完成且未整理的 - torrents = self.transmission.get_completed_torrents(tags=settings.TORRENT_TAG) + torrents = server.get_completed_torrents(tags=settings.TORRENT_TAG) for torrent in torrents or []: # 含"已整理"tag的不处理 if "已整理" in torrent.labels or []: @@ -209,7 +236,7 @@ class TransmissionModule(_ModuleBase): )) elif status == TorrentStatus.DOWNLOADING: # 获取正在下载的任务 - torrents = self.transmission.get_downloading_torrents(tags=settings.TORRENT_TAG) + torrents = server.get_downloading_torrents(tags=settings.TORRENT_TAG) for torrent in torrents or []: meta = MetaInfo(torrent.name) dlspeed = torrent.rate_download if hasattr(torrent, "rate_download") else torrent.rateDownload @@ -240,16 +267,18 @@ class TransmissionModule(_ModuleBase): :param downloader: 下载器 :return: None """ - if downloader != "transmission": + # 获取下载器 + server = self.get_server(downloader) + if not server: return None # 获取原标签 - org_tags = self.transmission.get_torrent_tags(ids=hashs) + org_tags = server.get_torrent_tags(ids=hashs) # 种子打上已整理标签 if org_tags: tags = org_tags + ['已整理'] else: tags = ['已整理'] - self.transmission.set_torrent_tag(ids=hashs, tags=tags) + server.set_torrent_tag(ids=hashs, tags=tags) # 移动模式删除种子 if settings.TRANSFER_TYPE in ["move", "rclone_move"]: if self.remove_torrents(hashs): @@ -270,9 +299,11 @@ class TransmissionModule(_ModuleBase): :param downloader: 下载器 :return: bool """ - if downloader != "transmission": + # 获取下载器 + server = self.get_server(downloader) + if not server: return None - return self.transmission.delete_torrents(delete_file=delete_file, ids=hashs) + return server.delete_torrents(delete_file=delete_file, ids=hashs) def start_torrents(self, hashs: Union[list, str], downloader: str = settings.DEFAULT_DOWNLOADER) -> Optional[bool]: @@ -282,9 +313,11 @@ class TransmissionModule(_ModuleBase): :param downloader: 下载器 :return: bool """ - if downloader != "transmission": + # 获取下载器 + server = self.get_server(downloader) + if not server: return None - return self.transmission.start_torrents(ids=hashs) + return server.start_torrents(ids=hashs) def stop_torrents(self, hashs: Union[list, str], downloader: str = settings.DEFAULT_DOWNLOADER) -> Optional[bool]: @@ -294,28 +327,43 @@ class TransmissionModule(_ModuleBase): :param downloader: 下载器 :return: bool """ - if downloader != "transmission": + # 获取下载器 + server = self.get_server(downloader) + if not server: return None - return self.transmission.start_torrents(ids=hashs) + return server.start_torrents(ids=hashs) def torrent_files(self, tid: str, downloader: str = settings.DEFAULT_DOWNLOADER) -> Optional[List[File]]: """ 获取种子文件列表 """ - if downloader != "transmission": + # 获取下载器 + server = self.get_server(downloader) + if not server: return None - return self.transmission.get_files(tid=tid) + return server.get_files(tid=tid) - def downloader_info(self) -> [schemas.DownloaderInfo]: + def downloader_info(self, downloader: str = None) -> Optional[List[schemas.DownloaderInfo]]: """ 下载器信息 """ - info = self.transmission.transfer_info() - if not info: - return [schemas.DownloaderInfo()] - return [schemas.DownloaderInfo( - download_speed=info.download_speed, - upload_speed=info.upload_speed, - download_size=info.current_stats.downloaded_bytes, - upload_size=info.current_stats.uploaded_bytes - )] + if downloader: + server = self.get_server(downloader) + if not server: + return None + servers = [server] + else: + servers = self._servers.values() + # 调用Qbittorrent API查询实时信息 + ret_info = [] + for server in servers: + info = server.transfer_info() + if not info: + continue + ret_info.append(schemas.DownloaderInfo( + download_speed=info.download_speed, + upload_speed=info.upload_speed, + download_size=info.current_stats.downloaded_bytes, + upload_size=info.current_stats.uploaded_bytes + )) + return ret_info diff --git a/app/modules/transmission/transmission.py b/app/modules/transmission/transmission.py index 56a025ce..cda91b95 100644 --- a/app/modules/transmission/transmission.py +++ b/app/modules/transmission/transmission.py @@ -22,10 +22,13 @@ class Transmission: "peersGettingFromUs", "peersSendingToUs", "uploadRatio", "uploadedEver", "downloadedEver", "downloadDir", "error", "errorString", "doneDate", "queuePosition", "activityDate", "trackers"] - def __init__(self, host: str = None, port: int = None, username: str = None, password: str = None): + def __init__(self, host: str, port: int, username: str = None, password: str = None, **kwargs): """ 若不设置参数,则创建配置文件设置的下载器 """ + if not host or not port: + logger.error("Transmission配置不完整!") + return self._host = host self._port = port self._username = username diff --git a/app/modules/vocechat/vocechat.py b/app/modules/vocechat/vocechat.py index e7929f38..6f887aa2 100644 --- a/app/modules/vocechat/vocechat.py +++ b/app/modules/vocechat/vocechat.py @@ -22,10 +22,13 @@ class VoceChat: # 请求对象 _client = None - def __init__(self, host: str = None, apikey: str = None, channel_id: str = None): + def __init__(self, host: str, apikey: str, channel_id: str, **kwargs): """ 初始化 """ + if not host or not apikey or not channel_id: + logger.error("VoceChat配置不完整!") + return self._host = host if self._host: if not self._host.endswith("/"): diff --git a/app/modules/wechat/wechat.py b/app/modules/wechat/wechat.py index fe870c2e..a9d40b19 100644 --- a/app/modules/wechat/wechat.py +++ b/app/modules/wechat/wechat.py @@ -37,10 +37,13 @@ class WeChat: # 企业微信创新菜单URL _create_menu_url = "/cgi-bin/menu/create?access_token=%s&agentid=%s" - def __init__(self, corpid: str = None, appsecret: str = None, appid: str = None, proxy: str = None): + def __init__(self, corpid: str, appsecret: str, appid: str, proxy: str = None, **kwargs): """ 初始化 """ + if not corpid or not appsecret or not appid: + logger.error("企业微信配置不完整!") + return self._corpid = corpid self._appsecret = appsecret self._appid = appid diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py index eb421d1b..603001aa 100644 --- a/app/schemas/__init__.py +++ b/app/schemas/__init__.py @@ -13,5 +13,5 @@ from .mediaserver import * from .message import * from .tmdb import * from .transfer import * -from .file import * from .filetransfer import * +from .mediaserver import * diff --git a/app/schemas/system.py b/app/schemas/system.py new file mode 100644 index 00000000..1eb6f3a9 --- /dev/null +++ b/app/schemas/system.py @@ -0,0 +1,29 @@ +from typing import Optional + +from pydantic import BaseModel + + +class MediaServerConf(BaseModel): + """ + 媒体服务器配置 + """ + # 名称 + name: Optional[str] = None + # 类型 emby/jellyfin/plex + type: Optional[str] = None + # 配置 + config: Optional[dict] = {} + + +class DownloaderConf(BaseModel): + """ + 下载器配置 + """ + # 名称 + name: Optional[str] = None + # 类型 qbittorrent/transmission + type: Optional[str] = None + # 是否默认 + default: Optional[bool] = False + # 配置 + config: Optional[dict] = {}