diff --git a/app/api/endpoints/message.py b/app/api/endpoints/message.py index 5d600fb0..e2f205de 100644 --- a/app/api/endpoints/message.py +++ b/app/api/endpoints/message.py @@ -15,6 +15,7 @@ from app.db import get_db from app.db.models import User from app.db.models.message import Message from app.db.user_oper import get_current_active_superuser +from app.helper.notification import NotificationHelper from app.log import logger from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt from app.schemas.types import MessageChannel @@ -75,26 +76,31 @@ def get_web_message(_: schemas.TokenPayload = Depends(verify_token), return ret_messages -def wechat_verify(echostr: str, msg_signature: str, - timestamp: Union[str, int], nonce: str) -> Any: +def wechat_verify(echostr: str, msg_signature: str, timestamp: Union[str, int], nonce: str, + source: str = None) -> Any: """ 微信验证响应 """ - try: - wxcpt = WXBizMsgCrypt(sToken=settings.WECHAT_TOKEN, - sEncodingAESKey=settings.WECHAT_ENCODING_AESKEY, - sReceiveId=settings.WECHAT_CORPID) - except Exception as err: - logger.error(f"微信请求验证失败: {str(err)}") - return str(err) - ret, sEchoStr = wxcpt.VerifyURL(sMsgSignature=msg_signature, - sTimeStamp=timestamp, - sNonce=nonce, - sEchoStr=echostr) - if ret != 0: - logger.error("微信请求验证失败 VerifyURL ret: %s" % str(ret)) - # 验证URL成功,将sEchoStr返回给企业号 - return PlainTextResponse(sEchoStr) + clients = NotificationHelper().get_clients() + if not clients: + return + for client in clients: + if client.type == "wechat" and client.enabled and client.name == source: + try: + wxcpt = WXBizMsgCrypt(sToken=client.config.get('WECHAT_TOKEN'), + sEncodingAESKey=client.config.get('WECHAT_ENCODING_AESKEY'), + sReceiveId=client.config.get('WECHAT_CORPID')) + ret, sEchoStr = wxcpt.VerifyURL(sMsgSignature=msg_signature, + sTimeStamp=timestamp, + sNonce=nonce, + sEchoStr=echostr) + if ret == 0: + # 验证URL成功,将sEchoStr返回给企业号 + return PlainTextResponse(sEchoStr) + except Exception as err: + logger.error(f"微信请求验证失败: {str(err)}") + return str(err) + return "未找到对应的消息配置" def vocechat_verify(token: str) -> Any: diff --git a/app/chain/mediaserver.py b/app/chain/mediaserver.py index f9ae8645..e0441534 100644 --- a/app/chain/mediaserver.py +++ b/app/chain/mediaserver.py @@ -81,11 +81,11 @@ class MediaServerChain(ChainBase): if not mediaserver: continue server_name = mediaserver.name - sync_blacklist = mediaserver.sync_libraries or [] + sync_libraries = mediaserver.sync_libraries or [] logger.info(f"开始同步媒体库 {server_name} 的数据 ...") for library in self.librarys(server_name): # 同步黑名单 跳过 - if library.id in sync_blacklist: + if library.id not in sync_libraries: continue logger.info(f"正在同步 {server_name} 媒体库 {library.name} ...") library_count = 0 diff --git a/app/chain/transfer.py b/app/chain/transfer.py index c9ce1c92..f2a69a3f 100644 --- a/app/chain/transfer.py +++ b/app/chain/transfer.py @@ -63,6 +63,16 @@ class TransferChain(ChainBase): # 全局锁,避免重复处理 with lock: + # 获取下载器监控目录 + download_dirs = self.directoryhelper.get_download_dirs() + # 如果没有下载器监控的目录则不处理 + downloader_monitor = False + for dir_info in download_dirs: + if dir_info.monitor_type == "downloader": + downloader_monitor = True + break + if not downloader_monitor: + return True logger.info("开始整理下载器中已经完成下载的文件 ...") # 从下载器获取种子列表 torrents: Optional[List[TransferTorrent]] = self.list_torrents(status=TorrentStatus.TRANSFER) @@ -74,7 +84,6 @@ class TransferChain(ChainBase): # 检查是否为下载器监控目录中的文件 need_handle = False - download_dirs = self.directoryhelper.get_download_dirs() for torrent in torrents: # 文件路径 file_path = Path(torrent.path) @@ -157,7 +166,7 @@ class TransferChain(ChainBase): 返回:成功标识,错误信息 """ if not transfer_type: - transfer_type = settings.TRANSFER_TYPE + transfer_type = 'link' # 自定义格式 formaterHandler = FormatParser(eformat=epformat.format, diff --git a/app/core/config.py b/app/core/config.py index c2225ce1..48638f72 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -120,14 +120,8 @@ class Settings(BaseSettings): '.flac', '.midi', '.opus', '.sfalc'] # 下载器临时文件后缀 DOWNLOAD_TMPEXT: list = ['.!qB', '.part'] - # 传输类型 - TRANSFER_TYPE: str = "" - # 下载器监视 - DOWNLOADER_MONITOR: bool = True # 下载器监视间隔(小时) MEDIASERVER_SYNC_INTERVAL: int = 1 - # 下载器监视黑名单 - MEDIASERVER_SYNC_BLACKLIST: list = [] # 订阅模式 SUBSCRIBE_MODE: str = "spider" # RSS订阅模式刷新时间间隔(分钟) @@ -192,21 +186,6 @@ class Settings(BaseSettings): # 全局图片缓存,将媒体图片缓存到本地 GLOBAL_IMAGE_CACHE: bool = False - # 微信代理 - WECHAT_PROXY: str = "" - # 微信token - WECHAT_TOKEN: str = "" - # 微信encoding_aes_key - WECHAT_ENCODING_AESKEY: str = "" - # 微信corpid - WECHAT_CORPID: str = "" - # 微信管理员 - WECHAT_ADMINS: str = "" - # plex地址 - PLEX_PLAY_HOST: str = "" - # plex token - PLEX_TOKEN: str = "" - @validator("SUBSCRIBE_RSS_INTERVAL", "COOKIECLOUD_INTERVAL", "META_CACHE_EXPIRE", diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index 26fe89a4..35bda1b8 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -22,7 +22,7 @@ class EmbyModule(_ModuleBase, _MediaServerBase): return for server in mediaservers: if server.type == "emby" and server.enabled: - self._servers[server.name] = Emby(**server.config) + self._servers[server.name] = Emby(**server.config, sync_libraries=server.sync_libraries) @staticmethod def get_name() -> str: diff --git a/app/modules/emby/emby.py b/app/modules/emby/emby.py index c95cd79f..48ed99e4 100644 --- a/app/modules/emby/emby.py +++ b/app/modules/emby/emby.py @@ -18,9 +18,11 @@ class Emby: _host: str = None _playhost: str = None _apikey: str = None + _sync_libraries: List[str] = [] user: Optional[Union[str, int]] = None - def __init__(self, host: str = None, apikey: str = None, play_host: str = None, **kwargs): + def __init__(self, host: str = None, apikey: str = None, play_host: str = None, + sync_libraries: list = None, **kwargs): if not host or not apikey: logger.error("Emby服务器配置不完整!") return @@ -34,6 +36,7 @@ class Emby: self.user = self.get_user(settings.SUPERUSER) self.folders = self.get_emby_folders() self.serverid = self.get_server_id() + self._sync_libraries = sync_libraries or [] def is_inactive(self) -> bool: """ @@ -132,9 +135,8 @@ class Emby: if not self._host or not self._apikey: return [] libraries = [] - black_list = (settings.MEDIASERVER_SYNC_BLACKLIST or '').split(",") for library in self.__get_emby_librarys(username) or []: - if library.get("Name") in black_list: + if self._sync_libraries and library.get("Id") not in self._sync_libraries: continue match library.get("CollectionType"): case "movies": @@ -1137,9 +1139,8 @@ class Emby: if not self._host or not self._apikey: return [] library_folders = [] - black_list = (settings.MEDIASERVER_SYNC_BLACKLIST or '').split(",") for library in self.get_emby_virtual_folders() or []: - if library.get("Name") in black_list: + if self._sync_libraries and library.get("Id") not in self._sync_libraries: continue library_folders += [folder for folder in library.get("Path")] return library_folders diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index 02c5f870..3190f034 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -22,7 +22,7 @@ class JellyfinModule(_ModuleBase, _MediaServerBase): return for server in mediaservers: if server.type == "jellyfin" and server.enabled: - self._servers[server.name] = Jellyfin(**server.config) + self._servers[server.name] = Jellyfin(**server.config, sync_libraries=server.sync_libraries) @staticmethod def get_name() -> str: diff --git a/app/modules/jellyfin/jellyfin.py b/app/modules/jellyfin/jellyfin.py index 709c5ee8..2aa30360 100644 --- a/app/modules/jellyfin/jellyfin.py +++ b/app/modules/jellyfin/jellyfin.py @@ -15,9 +15,11 @@ class Jellyfin: _host: str = None _apikey: str = None _playhost: str = None + _sync_libraries: List[str] = [] user: Optional[Union[str, int]] = None - def __init__(self, host: str = None, apikey: str = None, play_host: str = None, **kwargs): + def __init__(self, host: str = None, apikey: str = None, play_host: str = None, + sync_libraries: list = None, **kwargs): if not host or not apikey: logger.error("Jellyfin服务器配置不完整!!") return @@ -30,6 +32,7 @@ class Jellyfin: self._apikey = apikey self.user = self.get_user(settings.SUPERUSER) self.serverid = self.get_server_id() + self._sync_libraries = sync_libraries or [] def is_inactive(self) -> bool: """ @@ -128,9 +131,8 @@ class Jellyfin: if not self._host or not self._apikey: return [] libraries = [] - black_list = (settings.MEDIASERVER_SYNC_BLACKLIST or '').split(",") for library in self.__get_jellyfin_librarys(username) or []: - if library.get("Name") in black_list: + if self._sync_libraries and library.get("Id") not in self._sync_libraries: continue match library.get("CollectionType"): case "movies": @@ -871,9 +873,8 @@ class Jellyfin: if not self._host or not self._apikey: return [] library_folders = [] - black_list = (settings.MEDIASERVER_SYNC_BLACKLIST or '').split(",") for library in self.get_jellyfin_virtual_folders() or []: - if library.get("Name") in black_list: + if self._sync_libraries and library.get("Id") not in self._sync_libraries: continue library_folders += [folder for folder in library.get("Path")] return library_folders diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index 2b845bf8..370e9261 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -22,7 +22,7 @@ class PlexModule(_ModuleBase, _MediaServerBase): return for server in mediaservers: if server.type == "plex" and server.enabled: - self._servers[server.name] = Plex(**server.config) + self._servers[server.name] = Plex(**server.config, sync_libraries=server.sync_libraries) @staticmethod def get_name() -> str: diff --git a/app/modules/plex/plex.py b/app/modules/plex/plex.py index f7c2346c..c19b8bd1 100644 --- a/app/modules/plex/plex.py +++ b/app/modules/plex/plex.py @@ -19,8 +19,10 @@ from app.utils.url import UrlUtils class Plex: _plex = None _session = None + _sync_libraries: List[str] = [] - def __init__(self, host: str = None, token: str = None, play_host: str = None, **kwargs): + def __init__(self, host: str = None, token: str = None, play_host: str = None, + sync_libraries: list = None, **kwargs): if not host or not token: logger.error("Plex服务器配置不完整!") return @@ -39,6 +41,7 @@ class Plex: self._plex = None logger.error(f"Plex服务器连接失败:{str(e)}") self._session = self.__adapt_plex_session() + self._sync_libraries = sync_libraries or [] def is_inactive(self) -> bool: """ @@ -109,9 +112,8 @@ class Plex: logger.error(f"获取媒体服务器所有媒体库列表出错:{str(err)}") return [] libraries = [] - black_list = (settings.MEDIASERVER_SYNC_BLACKLIST or '').split(",") for library in self._libraries: - if library.title in black_list: + if self._sync_libraries and library.key not in self._sync_libraries: continue match library.type: case "movie": @@ -287,18 +289,18 @@ class Plex: return None # 如果配置了外网播放地址以及Token,则默认从Plex媒体服务器获取图片,否则返回有外网地址的图片资源 if self._playhost and self._token: - query = {"X-Plex-Token": settings.PLEX_TOKEN} + query = {"X-Plex-Token": self._token} if image_type == "Poster": if item.thumb: - image_url = RequestUtils.combine_url(host=settings.PLEX_PLAY_HOST, path=item.thumb, query=query) + image_url = RequestUtils.combine_url(host=self._playhost, path=item.thumb, query=query) else: # 默认使用art也就是Backdrop进行处理 if item.art: - image_url = RequestUtils.combine_url(host=settings.PLEX_PLAY_HOST, path=item.art, query=query) + image_url = RequestUtils.combine_url(host=self._playhost, path=item.art, query=query) # 这里对episode进行特殊处理,实际上episode的Backdrop是Poster # 也有个别情况,比如机智的凡人小子episode就是Poster,因此这里把episode的优先级降低,默认还是取art if not image_url and item.TYPE == "episode" and item.thumb: - image_url = RequestUtils.combine_url(host=settings.PLEX_PLAY_HOST, path=item.thumb, query=query) + image_url = RequestUtils.combine_url(host=self._playhost, path=item.thumb, query=query) else: if image_type == "Poster": images = self._plex.fetchItems(ekey=f"{ekey}/posters", @@ -811,23 +813,21 @@ class Plex: logger.error(f"连接Plex出错:" + str(e)) return None - @staticmethod - def __get_request_headers() -> dict: + def __get_request_headers(self) -> dict: """获取请求头""" return { - "X-Plex-Token": settings.PLEX_TOKEN, + "X-Plex-Token": self._token, "Accept": "application/json", "Content-Type": "application/json" } - @staticmethod - def __adapt_plex_session() -> Session: + def __adapt_plex_session(self) -> Session: """ 创建并配置一个针对Plex服务的requests.Session实例 这个会话包括特定的头部信息,用于处理所有的Plex请求 """ # 设置请求头部,通常包括验证令牌和接受/内容类型头部 - headers = Plex.__get_request_headers() + headers = self.__get_request_headers() session = Session() session.headers = headers return session diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index 20046e0b..dab640ea 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -275,7 +275,8 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase): if not server: return None server.set_torrents_tag(ids=hashs, tags=['已整理']) - # 移动模式删除种子 + # FIXME 移动模式删除种子 + """ if settings.TRANSFER_TYPE in ["move"]: if self.remove_torrents(hashs): logger.info(f"移动模式删除种子成功:{hashs} ") @@ -285,6 +286,7 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase): if not files: logger.warn(f"删除残留文件夹:{path}") shutil.rmtree(path, ignore_errors=True) + """ def remove_torrents(self, hashs: Union[str, list], delete_file: bool = True, downloader: str = None) -> Optional[bool]: diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index 6cd0d8b1..53aebbe4 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -270,7 +270,8 @@ class TransmissionModule(_ModuleBase, _DownloaderBase): else: tags = ['已整理'] server.set_torrent_tag(ids=hashs, tags=tags) - # 移动模式删除种子 + # FIXME 移动模式删除种子 + """ if settings.TRANSFER_TYPE in ["move"]: if self.remove_torrents(hashs): logger.info(f"移动模式删除种子成功:{hashs} ") @@ -280,6 +281,7 @@ class TransmissionModule(_ModuleBase, _DownloaderBase): if not files: logger.warn(f"删除残留文件夹:{path}") shutil.rmtree(path, ignore_errors=True) + """ def remove_torrents(self, hashs: Union[str, list], delete_file: bool = True, downloader: str = None) -> Optional[bool]: diff --git a/app/modules/wechat/__init__.py b/app/modules/wechat/__init__.py index 0464e9e7..32f44e1a 100644 --- a/app/modules/wechat/__init__.py +++ b/app/modules/wechat/__init__.py @@ -1,7 +1,6 @@ import xml.dom.minidom from typing import Optional, Union, List, Tuple, Any, Dict -from app.core.config import settings from app.core.context import Context, MediaInfo from app.helper.notification import NotificationHelper from app.log import logger @@ -66,6 +65,9 @@ class WechatModule(_ModuleBase, _MessageBase): client: WeChat = self.get_client(source) if not client: return None + client_config = self.get_config(source) + if not client_config: + return None # URL参数 sVerifyMsgSig = args.get("msg_signature") sVerifyTimeStamp = args.get("timestamp") @@ -74,9 +76,9 @@ class WechatModule(_ModuleBase, _MessageBase): logger.debug(f"微信请求参数错误:{args}") return None # 解密模块 - wxcpt = WXBizMsgCrypt(sToken=settings.WECHAT_TOKEN, - sEncodingAESKey=settings.WECHAT_ENCODING_AESKEY, - sReceiveId=settings.WECHAT_CORPID) + wxcpt = WXBizMsgCrypt(sToken=client_config.config.get('WECHAT_TOKEN'), + sEncodingAESKey=client_config.config.get('WECHAT_ENCODING_AESKEY'), + sReceiveId=client_config.config.get('WECHAT_CORPID')) # 报文数据 if not body: logger.debug(f"微信请求数据为空") @@ -126,8 +128,8 @@ class WechatModule(_ModuleBase, _MessageBase): # 解析消息内容 if msg_type == "event" and event == "click": # 校验用户有权限执行交互命令 - if settings.WECHAT_ADMINS: - wechat_admins = settings.WECHAT_ADMINS.split(',') + if client_config.config.get('WECHAT_ADMINS'): + wechat_admins = client_config.config.get('WECHAT_ADMINS').split(',') if wechat_admins and not any( user_id == admin_user for admin_user in wechat_admins): client.send_msg(title="用户无权限执行菜单命令", userid=user_id) diff --git a/app/scheduler.py b/app/scheduler.py index 21b39dca..4f151f60 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -287,17 +287,16 @@ class Scheduler(metaclass=Singleton): ) # 下载器文件转移(每5分钟) - if settings.DOWNLOADER_MONITOR: - self._scheduler.add_job( - self.start, - "interval", - id="transfer", - name="下载文件整理", - minutes=5, - kwargs={ - 'job_id': 'transfer' - } - ) + self._scheduler.add_job( + self.start, + "interval", + id="transfer", + name="下载文件整理", + minutes=5, + kwargs={ + 'job_id': 'transfer' + } + ) # 后台刷新TMDB壁纸 self._scheduler.add_job(