mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-28 19:47:41 +08:00
sync main
This commit is contained in:
@@ -81,6 +81,10 @@ class Settings(BaseSettings):
|
||||
AUTO_UPDATE_RESOURCE: bool = True
|
||||
# 是否启用DOH解析域名
|
||||
DOH_ENABLE: bool = True
|
||||
# 使用 DOH 解析的域名列表
|
||||
DOH_DOMAINS: str = "api.themoviedb.org,api.tmdb.org,webservice.fanart.tv,api.github.com,github.com,raw.githubusercontent.com,api.telegram.org"
|
||||
# DOH 解析服务器列表
|
||||
DOH_RESOLVERS: str = "1.0.0.1,1.1.1.1,9.9.9.9,149.112.112.112"
|
||||
# 支持的后缀格式
|
||||
RMT_MEDIAEXT: list = ['.mp4', '.mkv', '.ts', '.iso',
|
||||
'.rmvb', '.avi', '.mov', '.mpeg',
|
||||
@@ -150,6 +154,8 @@ class Settings(BaseSettings):
|
||||
GITHUB_PROXY: Optional[str] = ''
|
||||
# pip镜像站点,格式:https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
PIP_PROXY: Optional[str] = ''
|
||||
# 指定的仓库Github token,多个仓库使用,分隔,格式:{user1}/{repo1}:ghp_****,{user2}/{repo2}:github_pat_****
|
||||
REPO_GITHUB_TOKEN: Optional[str] = None
|
||||
# 大内存模式
|
||||
BIG_MEMORY_MODE: bool = False
|
||||
|
||||
@@ -308,6 +314,37 @@ class Settings(BaseSettings):
|
||||
PIP_OPTIONS = ""
|
||||
return PIP_OPTIONS
|
||||
|
||||
def REPO_GITHUB_HEADERS(self, repo: str = None):
|
||||
"""
|
||||
Github指定的仓库请求头
|
||||
:param repo: 指定的仓库名称,格式为 "user/repo"。如果为空,或者没有找到指定仓库请求头,则返回默认的请求头信息
|
||||
:return: Github请求头
|
||||
"""
|
||||
# 如果没有传入指定的仓库名称,或没有配置指定的仓库Token,则返回默认的请求头信息
|
||||
if not repo or not self.REPO_GITHUB_TOKEN:
|
||||
return self.GITHUB_HEADERS
|
||||
headers = {}
|
||||
# 格式:{user1}/{repo1}:ghp_****,{user2}/{repo2}:github_pat_****
|
||||
token_pairs = self.REPO_GITHUB_TOKEN.split(",")
|
||||
for token_pair in token_pairs:
|
||||
try:
|
||||
parts = token_pair.split(":")
|
||||
if len(parts) != 2:
|
||||
print(f"无效的令牌格式: {token_pair}")
|
||||
continue
|
||||
repo_info = parts[0].strip()
|
||||
token = parts[1].strip()
|
||||
if not repo_info or not token:
|
||||
print(f"无效的令牌或仓库信息: {token_pair}")
|
||||
continue
|
||||
headers[repo_info] = {
|
||||
"Authorization": f"Bearer {token}"
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"处理令牌对 '{token_pair}' 时出错: {e}")
|
||||
# 如果传入了指定的仓库名称,则返回该仓库的请求头信息,否则返回默认请求头
|
||||
return headers.get(repo, self.GITHUB_HEADERS)
|
||||
|
||||
@property
|
||||
def VAPID(self):
|
||||
return {
|
||||
|
||||
+2
-2
@@ -347,10 +347,10 @@ class MediaInfo:
|
||||
return [], []
|
||||
directors = []
|
||||
actors = []
|
||||
for cast in _credits.get("cast"):
|
||||
for cast in _credits.get("cast") or []:
|
||||
if cast.get("known_for_department") == "Acting":
|
||||
actors.append(cast)
|
||||
for crew in _credits.get("crew"):
|
||||
for crew in _credits.get("crew") or []:
|
||||
if crew.get("job") in ["Director", "Writer", "Editor", "Producer"]:
|
||||
directors.append(crew)
|
||||
return directors, actors
|
||||
|
||||
@@ -71,7 +71,11 @@ class ReleaseGroupsMatcher(metaclass=Singleton):
|
||||
"ultrahd": [],
|
||||
"others": ['B(?:MDru|eyondHD|TN)', 'C(?:fandora|trlhd|MRG)', 'DON', 'EVO', 'FLUX', 'HONE(?:|yG)',
|
||||
'N(?:oGroup|T(?:b|G))', 'PandaMoon', 'SMURF', 'T(?:EPES|aengoo|rollHD )'],
|
||||
"anime": ['ANi', 'HYSUB', 'KTXP', 'LoliHouse', 'MCE', 'Nekomoe kissaten', '(?:Lilith|NC)-Raws', '织梦字幕组']
|
||||
"anime": ['ANi', 'HYSUB', 'KTXP', 'LoliHouse', 'MCE', 'Nekomoe kissaten', 'SweetSub', 'MingY',
|
||||
'(?:Lilith|NC)-Raws', '织梦字幕组', '枫叶字幕组', '猎户手抄部', '喵萌奶茶屋', '漫猫字幕社',
|
||||
'霜庭云花Sub', '北宇治字幕组', '氢气烤肉架', '云歌字幕组', '萌樱字幕组', '极影字幕社',
|
||||
'悠哈璃羽字幕社',
|
||||
'❀拨雪寻春❀', '沸羊羊(?:制作|字幕组)', '(?:桜|樱)都字幕组']
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
|
||||
+83
-19
@@ -1,11 +1,13 @@
|
||||
import concurrent
|
||||
import concurrent.futures
|
||||
import importlib.util
|
||||
import inspect
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Type
|
||||
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
@@ -19,6 +21,7 @@ from app.helper.module import ModuleHelper
|
||||
from app.helper.plugin import PluginHelper
|
||||
from app.helper.sites import SitesHelper
|
||||
from app.log import logger
|
||||
from app.utils.crypto import RSAUtils
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.utils.object import ObjectUtils
|
||||
from app.utils.singleton import Singleton
|
||||
@@ -158,11 +161,12 @@ class PluginManager(metaclass=Singleton):
|
||||
if pid and plugin_id != pid:
|
||||
continue
|
||||
try:
|
||||
# 如果插件具有认证级别且当前认证级别不足,则不进行实例化
|
||||
if hasattr(plugin, "auth_level"):
|
||||
plugin.auth_level = plugin.auth_level
|
||||
if self.siteshelper.auth_level < plugin.auth_level:
|
||||
continue
|
||||
# 判断插件是否满足认证要求,如不满足则不进行实例化
|
||||
if not self.__set_and_check_auth_level(plugin=plugin):
|
||||
# 如果是插件热更新实例,这里则进行替换
|
||||
if plugin_id in self._plugins:
|
||||
self._plugins[plugin_id] = plugin
|
||||
continue
|
||||
# 存储Class
|
||||
self._plugins[plugin_id] = plugin
|
||||
# 未安装的不加载
|
||||
@@ -220,8 +224,6 @@ class PluginManager(metaclass=Singleton):
|
||||
# 清空指定插件
|
||||
if pid in self._running_plugins:
|
||||
self._running_plugins.pop(pid)
|
||||
if pid in self._plugins:
|
||||
self._plugins.pop(pid)
|
||||
else:
|
||||
# 清空
|
||||
self._plugins = {}
|
||||
@@ -602,11 +604,12 @@ class PluginManager(metaclass=Singleton):
|
||||
if plugin_obj and hasattr(plugin_obj, "get_page"):
|
||||
if ObjectUtils.check_method(plugin_obj.get_page):
|
||||
plugin.has_page = True
|
||||
# 公钥
|
||||
if plugin_info.get("key"):
|
||||
plugin.plugin_public_key = plugin_info.get("key")
|
||||
# 权限
|
||||
if plugin_info.get("level"):
|
||||
plugin.auth_level = plugin_info.get("level")
|
||||
if self.siteshelper.auth_level < plugin.auth_level:
|
||||
continue
|
||||
if not self.__set_and_check_auth_level(plugin=plugin, source=plugin_info):
|
||||
continue
|
||||
# 名称
|
||||
if plugin_info.get("name"):
|
||||
plugin.plugin_name = plugin_info.get("name")
|
||||
@@ -709,11 +712,12 @@ class PluginManager(metaclass=Singleton):
|
||||
plugin.has_page = True
|
||||
else:
|
||||
plugin.has_page = False
|
||||
# 公钥
|
||||
if hasattr(plugin_class, "plugin_public_key"):
|
||||
plugin.plugin_public_key = plugin_class.plugin_public_key
|
||||
# 权限
|
||||
if hasattr(plugin_class, "auth_level"):
|
||||
plugin.auth_level = plugin_class.auth_level
|
||||
if self.siteshelper.auth_level < plugin.auth_level:
|
||||
continue
|
||||
if not self.__set_and_check_auth_level(plugin=plugin, source=plugin_class):
|
||||
continue
|
||||
# 名称
|
||||
if hasattr(plugin_class, "plugin_name"):
|
||||
plugin.plugin_name = plugin_class.plugin_name
|
||||
@@ -748,10 +752,70 @@ class PluginManager(metaclass=Singleton):
|
||||
@staticmethod
|
||||
def is_plugin_exists(pid: str) -> bool:
|
||||
"""
|
||||
判断插件是否在本地文件系统存在
|
||||
判断插件是否在本地包中存在
|
||||
:param pid: 插件ID
|
||||
"""
|
||||
if not pid:
|
||||
return False
|
||||
plugin_dir = settings.ROOT_PATH / "app" / "plugins" / pid.lower()
|
||||
return plugin_dir.exists()
|
||||
try:
|
||||
# 构建包名
|
||||
package_name = f"app.plugins.{pid.lower()}"
|
||||
# 检查包是否存在
|
||||
package_exists = importlib.util.find_spec(package_name) is not None
|
||||
logger.debug(f"{pid} exists: {package_exists}")
|
||||
return package_exists
|
||||
except Exception as e:
|
||||
logger.debug(f"获取插件是否在本地包中存在失败,{e}")
|
||||
return False
|
||||
|
||||
def __set_and_check_auth_level(self, plugin: Union[schemas.Plugin, Type[Any]],
|
||||
source: Optional[Union[dict, Type[Any]]] = None) -> bool:
|
||||
"""
|
||||
设置并检查插件的认证级别
|
||||
:param plugin: 插件对象或包含 auth_level 属性的对象
|
||||
:param source: 可选的字典对象或类对象,可能包含 "level" 或 "auth_level" 键
|
||||
:return: 如果插件的认证级别有效且当前环境的认证级别满足要求,返回 True,否则返回 False
|
||||
"""
|
||||
# 检查并赋值 source 中的 level 或 auth_level
|
||||
if source:
|
||||
if isinstance(source, dict) and "level" in source:
|
||||
plugin.auth_level = source.get("level")
|
||||
elif hasattr(source, "auth_level"):
|
||||
plugin.auth_level = source.auth_level
|
||||
# 如果 source 为空且 plugin 本身没有 auth_level,直接返回 True
|
||||
elif not hasattr(plugin, "auth_level"):
|
||||
return True
|
||||
|
||||
# auth_level 级别说明
|
||||
# 1 - 所有用户可见
|
||||
# 2 - 站点认证用户可见
|
||||
# 3 - 站点&密钥认证可见
|
||||
# 99 - 站点&特殊密钥认证可见
|
||||
# 如果当前站点认证级别大于 1 且插件级别为 99,并存在插件公钥,说明为特殊密钥认证,通过密钥匹配进行认证
|
||||
if self.siteshelper.auth_level > 1 and plugin.auth_level == 99 and hasattr(plugin, "plugin_public_key"):
|
||||
plugin_id = plugin.id if isinstance(plugin, schemas.Plugin) else plugin.__name__
|
||||
public_key = plugin.plugin_public_key
|
||||
if public_key:
|
||||
private_key = PluginManager.__get_plugin_private_key(plugin_id)
|
||||
verify = RSAUtils.verify_rsa_keys(public_key=public_key, private_key=private_key)
|
||||
return verify
|
||||
# 如果当前站点认证级别小于插件级别,则返回 False
|
||||
if self.siteshelper.auth_level < plugin.auth_level:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def __get_plugin_private_key(plugin_id: str) -> Optional[str]:
|
||||
"""
|
||||
根据插件标识获取对应的私钥
|
||||
:param plugin_id: 插件标识
|
||||
:return: 对应的插件私钥,如果未找到则返回 None
|
||||
"""
|
||||
try:
|
||||
# 将插件标识转换为大写并构建环境变量名称
|
||||
env_var_name = f"PLUGIN_{plugin_id.upper()}_PRIVATE_KEY"
|
||||
private_key = os.environ.get(env_var_name)
|
||||
return private_key
|
||||
except Exception as e:
|
||||
logger.debug(f"获取插件 {plugin_id} 的私钥时发生错误:{e}")
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user