diff --git a/app/chain/search.py b/app/chain/search.py index 78e71d40..02f9f03b 100644 --- a/app/chain/search.py +++ b/app/chain/search.py @@ -1,5 +1,4 @@ import pickle -import traceback from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime from typing import Dict @@ -23,6 +22,8 @@ class SearchChain(ChainBase): """ 站点资源搜索处理链 """ + + __result_temp_file = "__search_result__" def __init__(self): super().__init__() @@ -53,9 +54,9 @@ class SearchChain(ChainBase): } } results = self.process(mediainfo=mediainfo, area=area, no_exists=no_exists) - # 保存结果 + # 保存到本地文件 bytes_results = pickle.dumps(results) - self.systemconfig.set(SystemConfigKey.SearchResults, bytes_results) + self.save_cache(bytes_results, self.__result_temp_file) return results def search_by_title(self, title: str, page: int = 0, site: int = None) -> List[Context]: @@ -77,23 +78,20 @@ class SearchChain(ChainBase): # 组装上下文 contexts = [Context(meta_info=MetaInfo(title=torrent.title, subtitle=torrent.description), torrent_info=torrent) for torrent in torrents] - # 保存结果 + # 保存到本地文件 bytes_results = pickle.dumps(contexts) - self.systemconfig.set(SystemConfigKey.SearchResults, bytes_results) + self.save_cache(bytes_results, self.__result_temp_file) return contexts def last_search_results(self) -> List[Context]: """ 获取上次搜索结果 """ - results = self.systemconfig.get(SystemConfigKey.SearchResults) + # 读取本地文件缓存 + results = self.load_cache(self.__result_temp_file) if not results: return [] - try: - return pickle.loads(results) - except Exception as e: - logger.error(f'加载搜索结果失败:{str(e)} - {traceback.format_exc()}') - return [] + return results def process(self, mediainfo: MediaInfo, keyword: str = None, diff --git a/app/core/config.py b/app/core/config.py index d71aacb3..b0e012e6 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -95,100 +95,10 @@ class Settings(BaseSettings): AUTH_SITE: str = "" # 交互搜索自动下载用户ID,使用,分割 AUTO_DOWNLOAD_USER: Optional[str] = None - # 消息通知渠道 telegram/wechat/slack/synologychat/vocechat/webpush,多个通知渠道用,分隔 - MESSAGER: str = "webpush" - # WeChat企业ID - WECHAT_CORPID: Optional[str] = None - # WeChat应用Secret - WECHAT_APP_SECRET: Optional[str] = None - # WeChat应用ID - WECHAT_APP_ID: Optional[str] = None - # WeChat代理服务器 - WECHAT_PROXY: str = "https://qyapi.weixin.qq.com" - # WeChat Token - WECHAT_TOKEN: Optional[str] = None - # WeChat EncodingAESKey - WECHAT_ENCODING_AESKEY: Optional[str] = None - # WeChat 管理员 - WECHAT_ADMINS: Optional[str] = None - # Telegram Bot Token - TELEGRAM_TOKEN: Optional[str] = None - # Telegram Chat ID - TELEGRAM_CHAT_ID: Optional[str] = None - # Telegram 用户ID,使用,分隔 - TELEGRAM_USERS: str = "" - # Telegram 管理员ID,使用,分隔 - TELEGRAM_ADMINS: str = "" - # Slack Bot User OAuth Token - SLACK_OAUTH_TOKEN: str = "" - # Slack App-Level Token - SLACK_APP_TOKEN: str = "" - # Slack 频道名称 - SLACK_CHANNEL: str = "" - # SynologyChat Webhook - SYNOLOGYCHAT_WEBHOOK: str = "" - # SynologyChat Token - SYNOLOGYCHAT_TOKEN: str = "" - # VoceChat地址 - VOCECHAT_HOST: str = "" - # VoceChat ApiKey - VOCECHAT_API_KEY: str = "" - # VoceChat 频道ID - VOCECHAT_CHANNEL_ID: str = "" - # 下载器 qbittorrent/transmission,启用多个下载器时使用,分隔,只有第一个会被默认使用 - DOWNLOADER: str = "qbittorrent" - # 下载器监控开关 - DOWNLOADER_MONITOR: bool = True - # Qbittorrent地址,IP:PORT - QB_HOST: Optional[str] = None - # Qbittorrent用户名 - QB_USER: Optional[str] = None - # Qbittorrent密码 - QB_PASSWORD: Optional[str] = None - # Qbittorrent分类自动管理 - QB_CATEGORY: bool = False - # Qbittorrent按顺序下载 - QB_SEQUENTIAL: bool = True - # Qbittorrent忽略队列限制,强制继续 - QB_FORCE_RESUME: bool = False - # Transmission地址,IP:PORT - TR_HOST: Optional[str] = None - # Transmission用户名 - TR_USER: Optional[str] = None - # Transmission密码 - TR_PASSWORD: Optional[str] = None # 种子标签 TORRENT_TAG: str = "MOVIEPILOT" # 下载站点字幕 DOWNLOAD_SUBTITLE: bool = True - # 媒体服务器 emby/jellyfin/plex,多个媒体服务器,分割 - MEDIASERVER: str = "emby" - # 媒体服务器同步间隔(小时) - MEDIASERVER_SYNC_INTERVAL: Optional[int] = 6 - # 媒体服务器同步黑名单,多个媒体库名称,分割 - MEDIASERVER_SYNC_BLACKLIST: Optional[str] = None - # EMBY服务器地址,IP:PORT - EMBY_HOST: Optional[str] = None - # EMBY外网地址,http(s)://DOMAIN:PORT,未设置时使用EMBY_HOST - EMBY_PLAY_HOST: Optional[str] = None - # EMBY Api Key - EMBY_API_KEY: Optional[str] = None - # Jellyfin服务器地址,IP:PORT - JELLYFIN_HOST: Optional[str] = None - # Jellyfin外网地址,http(s)://DOMAIN:PORT,未设置时使用JELLYFIN_HOST - JELLYFIN_PLAY_HOST: Optional[str] = None - # Jellyfin Api Key - JELLYFIN_API_KEY: Optional[str] = None - # Plex服务器地址,IP:PORT - PLEX_HOST: Optional[str] = None - # Plex外网地址,http(s)://DOMAIN:PORT,未设置时使用PLEX_HOST - PLEX_PLAY_HOST: Optional[str] = None - # Plex Token - PLEX_TOKEN: Optional[str] = None - # 转移方式 link/copy/move/softlink - TRANSFER_TYPE: str = "copy" - # 是否同盘优先 - TRANSFER_SAME_DISK: bool = True # CookieCloud是否启动本地服务 COOKIECLOUD_ENABLE_LOCAL: Optional[bool] = False # CookieCloud服务器地址 @@ -243,7 +153,6 @@ class Settings(BaseSettings): @validator("SUBSCRIBE_RSS_INTERVAL", "COOKIECLOUD_INTERVAL", - "MEDIASERVER_SYNC_INTERVAL", "META_CACHE_EXPIRE", pre=True, always=True) def convert_int(cls, value): @@ -335,24 +244,6 @@ class Settings(BaseSettings): } return {} - @property - def DEFAULT_DOWNLOADER(self): - """ - 默认下载器 - """ - if not self.DOWNLOADER: - return None - return next((d for d in settings.DOWNLOADER.split(",") if d), None) - - @property - def DOWNLOADERS(self): - """ - 下载器列表 - """ - if not self.DOWNLOADER: - return [] - return [d for d in settings.DOWNLOADER.split(",") if d] - @property def VAPID(self): return { diff --git a/app/helper/downloader.py b/app/helper/downloader.py new file mode 100644 index 00000000..0c5c17e2 --- /dev/null +++ b/app/helper/downloader.py @@ -0,0 +1,20 @@ +from app.db.systemconfig_oper import SystemConfigOper +from app.schemas.types import SystemConfigKey + + +class DownloaderHelper: + """ + 下载器帮助类 + """ + + def __init__(self): + self.systemconfig = SystemConfigOper() + + def get_downloaders(self) -> dict: + """ + 获取下载器 + """ + downloader_conf: dict = self.systemconfig.get(SystemConfigKey.Downloaders) + if not downloader_conf: + return {} + return downloader_conf diff --git a/app/helper/mediaserver.py b/app/helper/mediaserver.py new file mode 100644 index 00000000..88b475fb --- /dev/null +++ b/app/helper/mediaserver.py @@ -0,0 +1,20 @@ +from app.db.systemconfig_oper import SystemConfigOper +from app.schemas.types import SystemConfigKey + + +class MediaServerHelper: + """ + 媒体服务器帮助类 + """ + + def __init__(self): + self.systemconfig = SystemConfigOper() + + def get_mediaservers(self) -> dict: + """ + 获取媒体服务器 + """ + mediaserver_conf: dict = self.systemconfig.get(SystemConfigKey.MediaServers) + if not mediaserver_conf: + return {} + return mediaserver_conf diff --git a/app/helper/notification.py b/app/helper/notification.py new file mode 100644 index 00000000..a898c762 --- /dev/null +++ b/app/helper/notification.py @@ -0,0 +1,20 @@ +from app.db.systemconfig_oper import SystemConfigOper +from app.schemas.types import SystemConfigKey + + +class NotificationHelper: + """ + 消息通知渠道帮助类 + """ + + def __init__(self): + self.systemconfig = SystemConfigOper() + + def get_notifications(self) -> dict: + """ + 获取消息通知渠道 + """ + notification_conf: dict = self.systemconfig.get(SystemConfigKey.Notifications) + if not notification_conf: + return {} + return notification_conf diff --git a/app/modules/emby/emby.py b/app/modules/emby/emby.py index 18886e4e..c7e5fc1b 100644 --- a/app/modules/emby/emby.py +++ b/app/modules/emby/emby.py @@ -15,14 +15,14 @@ from app.utils.http import RequestUtils class Emby: - def __init__(self): - self._host = settings.EMBY_HOST + def __init__(self, host: str = None, play_host: str = None, apikey: str = None): + self._host = host if self._host: self._host = RequestUtils.standardize_base_url(self._host) - self._playhost = settings.EMBY_PLAY_HOST + self._playhost = play_host if self._playhost: self._playhost = RequestUtils.standardize_base_url(self._playhost) - self._apikey = settings.EMBY_API_KEY + self._apikey = apikey self.user = self.get_user(settings.SUPERUSER) self.folders = self.get_emby_folders() self.serverid = self.get_server_id() diff --git a/app/modules/jellyfin/jellyfin.py b/app/modules/jellyfin/jellyfin.py index 1a4bb279..ab0495ba 100644 --- a/app/modules/jellyfin/jellyfin.py +++ b/app/modules/jellyfin/jellyfin.py @@ -12,14 +12,14 @@ from app.utils.http import RequestUtils class Jellyfin: - def __init__(self): - self._host = settings.JELLYFIN_HOST + def __init__(self, host: str = None, play_host: str = None, apikey: str = None): + self._host = host if self._host: self._host = RequestUtils.standardize_base_url(self._host) - self._playhost = settings.JELLYFIN_PLAY_HOST + self._playhost = play_host if self._playhost: self._playhost = RequestUtils.standardize_base_url(self._playhost) - self._apikey = settings.JELLYFIN_API_KEY + self._apikey = apikey self.user = self.get_user(settings.SUPERUSER) self.serverid = self.get_server_id() diff --git a/app/modules/plex/plex.py b/app/modules/plex/plex.py index d7a7f7b5..3b2bd576 100644 --- a/app/modules/plex/plex.py +++ b/app/modules/plex/plex.py @@ -19,14 +19,14 @@ class Plex: _plex = None _session = None - def __init__(self): - self._host = settings.PLEX_HOST + def __init__(self, host: str = None, play_host: str = None, token: str = None): + self._host = host if self._host: self._host = RequestUtils.standardize_base_url(self._host) - self._playhost = settings.PLEX_PLAY_HOST + self._playhost = play_host if self._playhost: self._playhost = RequestUtils.standardize_base_url(self._playhost) - self._token = settings.PLEX_TOKEN + self._token = token if self._host and self._token: try: self._plex = PlexServer(self._host, self._token) diff --git a/app/modules/qbittorrent/qbittorrent.py b/app/modules/qbittorrent/qbittorrent.py index 84267274..7941e848 100644 --- a/app/modules/qbittorrent/qbittorrent.py +++ b/app/modules/qbittorrent/qbittorrent.py @@ -8,7 +8,6 @@ from qbittorrentapi.transfer import TransferInfoDictionary from app.core.config import settings from app.log import logger -from app.utils.string import StringUtils class Qbittorrent: @@ -23,12 +22,10 @@ class Qbittorrent: """ 若不设置参数,则创建配置文件设置的下载器 """ - if host and port: - self._host, self._port = host, port - else: - self._host, self._port = StringUtils.get_domain_address(address=settings.QB_HOST, prefix=True) - self._username = username if username else settings.QB_USER - self._password = password if password else settings.QB_PASSWORD + self._host = host + self._port = port + self._username = username + self._password = password if self._host and self._port: self.qbc = self.__login_qbittorrent() diff --git a/app/modules/slack/slack.py b/app/modules/slack/slack.py index 8bf8ccbe..3ed1bb5c 100644 --- a/app/modules/slack/slack.py +++ b/app/modules/slack/slack.py @@ -21,22 +21,21 @@ class Slack: _client: WebClient = None _service: SocketModeHandler = None - _ds_url = f"http://127.0.0.1:{settings.PORT}/api/v1/message?token={settings.API_TOKEN}" + _channel = "" - def __init__(self): + def __init__(self, oauth_token: str, app_token: str, channel: str = ""): - if not settings.SLACK_OAUTH_TOKEN or not settings.SLACK_APP_TOKEN: - return - try: - slack_app = App(token=settings.SLACK_OAUTH_TOKEN, + slack_app = App(token=oauth_token, ssl_check_enabled=False, url_verification_enabled=False) except Exception as err: logger.error(f"Slack初始化失败: {str(err)}") return + self._client = slack_app.client + self._channel = channel # 注册消息响应 @slack_app.event("message") @@ -72,7 +71,7 @@ class Slack: try: self._service = SocketModeHandler( slack_app, - settings.SLACK_APP_TOKEN + app_token ) self._service.connect() logger.info("Slack消息接收服务启动") @@ -337,7 +336,7 @@ class Slack: if conversation_id: break for channel in result["channels"]: - if channel.get("name") == (settings.SLACK_CHANNEL or "全体"): + if channel.get("name") == (self._channel or "全体"): conversation_id = channel.get("id") break except Exception as e: diff --git a/app/modules/telegram/telegram.py b/app/modules/telegram/telegram.py index c6e15364..1e0847b2 100644 --- a/app/modules/telegram/telegram.py +++ b/app/modules/telegram/telegram.py @@ -24,14 +24,14 @@ class Telegram: _event = Event() _bot: telebot.TeleBot = None - def __init__(self): + def __init__(self, token: str = None, chat_id: str = None): """ 初始化参数 """ # Token - self._telegram_token = settings.TELEGRAM_TOKEN + self._telegram_token = token # Chat Id - self._telegram_chat_id = settings.TELEGRAM_CHAT_ID + self._telegram_chat_id = chat_id # 初始化机器人 if self._telegram_token and self._telegram_chat_id: # bot diff --git a/app/modules/transmission/transmission.py b/app/modules/transmission/transmission.py index 87447a3e..56a025ce 100644 --- a/app/modules/transmission/transmission.py +++ b/app/modules/transmission/transmission.py @@ -1,12 +1,10 @@ -from typing import Optional, Union, Tuple, List, Dict +from typing import Optional, Union, Tuple, List import transmission_rpc from transmission_rpc import Client, Torrent, File from transmission_rpc.session import SessionStats, Session -from app.core.config import settings from app.log import logger -from app.utils.string import StringUtils class Transmission: @@ -28,12 +26,10 @@ class Transmission: """ 若不设置参数,则创建配置文件设置的下载器 """ - if host and port: - self._host, self._port = host, port - else: - self._host, self._port = StringUtils.get_domain_address(address=settings.TR_HOST, prefix=False) - self._username = username if username else settings.TR_USER - self._password = password if password else settings.TR_PASSWORD + self._host = host + self._port = port + self._username = username + self._password = password if self._host and self._port: self.trc = self.__login_transmission() diff --git a/app/modules/vocechat/vocechat.py b/app/modules/vocechat/vocechat.py index 2ba47e67..e7929f38 100644 --- a/app/modules/vocechat/vocechat.py +++ b/app/modules/vocechat/vocechat.py @@ -2,7 +2,6 @@ import re import threading from typing import Optional, List -from app.core.config import settings from app.core.context import MediaInfo, Context from app.core.metainfo import MetaInfo from app.log import logger @@ -23,18 +22,18 @@ class VoceChat: # 请求对象 _client = None - def __init__(self): + def __init__(self, host: str = None, apikey: str = None, channel_id: str = None): """ 初始化 """ - self._host = settings.VOCECHAT_HOST + self._host = host if self._host: if not self._host.endswith("/"): self._host += "/" if not self._host.startswith("http"): self._playhost = "http://" + self._host - self._apikey = settings.VOCECHAT_API_KEY - self._channel_id = settings.VOCECHAT_CHANNEL_ID + self._apikey = apikey + self._channel_id = channel_id if self._apikey and self._host and self._channel_id: self._client = RequestUtils(headers={ "content-type": "text/markdown", diff --git a/app/modules/wechat/wechat.py b/app/modules/wechat/wechat.py index 92e7eda7..fe870c2e 100644 --- a/app/modules/wechat/wechat.py +++ b/app/modules/wechat/wechat.py @@ -4,7 +4,6 @@ import threading from datetime import datetime from typing import Optional, List, Dict -from app.core.config import settings from app.core.context import MediaInfo, Context from app.core.metainfo import MetaInfo from app.log import logger @@ -28,25 +27,34 @@ class WeChat: _appsecret = None # 企业微信AppID _appid = None + # 代理 + _proxy = None # 企业微信发送消息URL - _send_msg_url = f"{settings.WECHAT_PROXY}/cgi-bin/message/send?access_token=%s" + _send_msg_url = "/cgi-bin/message/send?access_token=%s" # 企业微信获取TokenURL - _token_url = f"{settings.WECHAT_PROXY}/cgi-bin/gettoken?corpid=%s&corpsecret=%s" + _token_url = "/cgi-bin/gettoken?corpid=%s&corpsecret=%s" # 企业微信创新菜单URL - _create_menu_url = f"{settings.WECHAT_PROXY}/cgi-bin/menu/create?access_token=%s&agentid=%s" + _create_menu_url = "/cgi-bin/menu/create?access_token=%s&agentid=%s" - def __init__(self): + def __init__(self, corpid: str = None, appsecret: str = None, appid: str = None, proxy: str = None): """ 初始化 """ - self._corpid = settings.WECHAT_CORPID - self._appsecret = settings.WECHAT_APP_SECRET - self._appid = settings.WECHAT_APP_ID + self._corpid = corpid + self._appsecret = appsecret + self._appid = appid + self._proxy = proxy or "https://qyapi.weixin.qq.com" if self._corpid and self._appsecret and self._appid: self.__get_access_token() + if self._proxy: + self._proxy = self._proxy.rstrip("/") + self._send_msg_url = f"{self._proxy}/{self._send_msg_url}" + self._token_url = f"{self._proxy}/{self._token_url}" + self._create_menu_url = f"{self._proxy}/{self._create_menu_url}" + def get_state(self): """ 获取状态 diff --git a/app/schemas/types.py b/app/schemas/types.py index f9b8d06d..9de58e2c 100644 --- a/app/schemas/types.py +++ b/app/schemas/types.py @@ -58,16 +58,24 @@ class EventType(Enum): # 系统配置Key字典 class SystemConfigKey(Enum): - # 用户已安装的插件 - UserInstalledPlugins = "UserInstalledPlugins" - # 搜索结果 - SearchResults = "SearchResults" + # 下载器配置 + Downloaders = "Downloaders" + # 媒体服务器配置 + MediaServers = "MediaServers" + # 消息通知配置 + Notifications = "Notifications" + # 下载目录配置 + DownloadDirectories = "DownloadDirectories" + # 媒体库目录配置 + LibraryDirectories = "LibraryDirectories" + # 阿里云盘认证参数 + UserAliyunParams = "UserAliyunParams" + # 115网盘认证参数 + User115Params = "User115Params" # 搜索站点范围 IndexerSites = "IndexerSites" # 订阅站点范围 RssSites = "RssSites" - # 种子优先级规则 - TorrentsPriority = "TorrentsPriority" # 通知消息渠道设置 NotificationChannels = "NotificationChannels" # 自定义制作组/字幕组 @@ -76,6 +84,10 @@ class SystemConfigKey(Enum): Customization = "Customization" # 自定义识别词 CustomIdentifiers = "CustomIdentifiers" + # 转移屏蔽词 + TransferExcludeWords = "TransferExcludeWords" + # 种子优先级规则 + TorrentsPriority = "TorrentsPriority" # 搜索优先级规则 SearchFilterRules = "SearchFilterRules" # 订阅优先级规则 @@ -86,22 +98,14 @@ class SystemConfigKey(Enum): DefaultFilterRules = "DefaultFilterRules" # 默认搜索过滤规则 DefaultSearchFilterRules = "DefaultSearchFilterRules" - # 转移屏蔽词 - TransferExcludeWords = "TransferExcludeWords" - # 插件安装统计 - PluginInstallReport = "PluginInstallReport" # 订阅统计 SubscribeReport = "SubscribeReport" # 用户自定义CSS UserCustomCSS = "UserCustomCSS" - # 下载目录定义 - DownloadDirectories = "DownloadDirectories" - # 媒体库目录定义 - LibraryDirectories = "LibraryDirectories" - # 阿里云盘认证参数 - UserAliyunParams = "UserAliyunParams" - # 115网盘认证参数 - User115Params = "User115Params" + # 用户已安装的插件 + UserInstalledPlugins = "UserInstalledPlugins" + # 插件安装统计 + PluginInstallReport = "PluginInstallReport" # 处理进度Key字典