mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-27 11:11:07 +08:00
feat: 新增 LLM_MAX_TOOLS 配置项,支持按需启用 LLMToolSelectorMiddleware
This commit is contained in:
@@ -90,18 +90,28 @@ class MoviePilotAgent:
|
|||||||
tools = self._initialize_tools()
|
tools = self._initialize_tools()
|
||||||
|
|
||||||
# 中间件
|
# 中间件
|
||||||
middlewares = [
|
middlewares = []
|
||||||
# 工具选择
|
|
||||||
# LLMToolSelectorMiddleware(model=llm, max_tools=20),
|
# 工具选择(LLM_MAX_TOOLS > 0 时启用)
|
||||||
# 记忆管理
|
if settings.LLM_MAX_TOOLS > 0:
|
||||||
MemoryMiddleware(
|
middlewares.append(
|
||||||
sources=[str(settings.CONFIG_PATH / "agent" / "MEMORY.md")]
|
LLMToolSelectorMiddleware(
|
||||||
),
|
model=llm, max_tools=settings.LLM_MAX_TOOLS
|
||||||
# 上下文压缩
|
)
|
||||||
SummarizationMiddleware(model=llm, trigger=("fraction", 0.85)),
|
)
|
||||||
# 错误工具调用修复
|
|
||||||
PatchToolCallsMiddleware(),
|
middlewares.extend(
|
||||||
]
|
[
|
||||||
|
# 记忆管理
|
||||||
|
MemoryMiddleware(
|
||||||
|
sources=[str(settings.CONFIG_PATH / "agent" / "MEMORY.md")]
|
||||||
|
),
|
||||||
|
# 上下文压缩
|
||||||
|
SummarizationMiddleware(model=llm, trigger=("fraction", 0.85)),
|
||||||
|
# 错误工具调用修复
|
||||||
|
PatchToolCallsMiddleware(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
return create_agent(
|
return create_agent(
|
||||||
model=llm,
|
model=llm,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class SystemConfModel(BaseModel):
|
|||||||
"""
|
"""
|
||||||
系统关键资源大小配置
|
系统关键资源大小配置
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 缓存种子数量
|
# 缓存种子数量
|
||||||
torrents: int = 0
|
torrents: int = 0
|
||||||
# 订阅刷新处理数量
|
# 订阅刷新处理数量
|
||||||
@@ -160,14 +161,16 @@ class ConfigModel(BaseModel):
|
|||||||
# 是否启用DOH解析域名
|
# 是否启用DOH解析域名
|
||||||
DOH_ENABLE: bool = False
|
DOH_ENABLE: bool = False
|
||||||
# 使用 DOH 解析的域名列表
|
# 使用 DOH 解析的域名列表
|
||||||
DOH_DOMAINS: str = ("api.themoviedb.org,"
|
DOH_DOMAINS: str = (
|
||||||
"api.tmdb.org,"
|
"api.themoviedb.org,"
|
||||||
"webservice.fanart.tv,"
|
"api.tmdb.org,"
|
||||||
"api.github.com,"
|
"webservice.fanart.tv,"
|
||||||
"github.com,"
|
"api.github.com,"
|
||||||
"raw.githubusercontent.com,"
|
"github.com,"
|
||||||
"codeload.github.com,"
|
"raw.githubusercontent.com,"
|
||||||
"api.telegram.org")
|
"codeload.github.com,"
|
||||||
|
"api.telegram.org"
|
||||||
|
)
|
||||||
# DOH 解析服务器列表
|
# DOH 解析服务器列表
|
||||||
DOH_RESOLVERS: str = "1.0.0.1,1.1.1.1,9.9.9.9,149.112.112.112"
|
DOH_RESOLVERS: str = "1.0.0.1,1.1.1.1,9.9.9.9,149.112.112.112"
|
||||||
|
|
||||||
@@ -216,30 +219,77 @@ class ConfigModel(BaseModel):
|
|||||||
|
|
||||||
# ==================== 系统升级配置 ====================
|
# ==================== 系统升级配置 ====================
|
||||||
# 重启自动升级
|
# 重启自动升级
|
||||||
MOVIEPILOT_AUTO_UPDATE: str = 'release'
|
MOVIEPILOT_AUTO_UPDATE: str = "release"
|
||||||
# 自动检查和更新站点资源包(站点索引、认证等)
|
# 自动检查和更新站点资源包(站点索引、认证等)
|
||||||
AUTO_UPDATE_RESOURCE: bool = True
|
AUTO_UPDATE_RESOURCE: bool = True
|
||||||
|
|
||||||
# ==================== 媒体文件格式配置 ====================
|
# ==================== 媒体文件格式配置 ====================
|
||||||
# 支持的视频文件后缀格式
|
# 支持的视频文件后缀格式
|
||||||
RMT_MEDIAEXT: list = Field(
|
RMT_MEDIAEXT: list = Field(
|
||||||
default_factory=lambda: ['.mp4', '.mkv', '.ts', '.iso',
|
default_factory=lambda: [
|
||||||
'.rmvb', '.avi', '.mov', '.mpeg',
|
".mp4",
|
||||||
'.mpg', '.wmv', '.3gp', '.asf',
|
".mkv",
|
||||||
'.m4v', '.flv', '.m2ts', '.strm',
|
".ts",
|
||||||
'.tp', '.f4v']
|
".iso",
|
||||||
|
".rmvb",
|
||||||
|
".avi",
|
||||||
|
".mov",
|
||||||
|
".mpeg",
|
||||||
|
".mpg",
|
||||||
|
".wmv",
|
||||||
|
".3gp",
|
||||||
|
".asf",
|
||||||
|
".m4v",
|
||||||
|
".flv",
|
||||||
|
".m2ts",
|
||||||
|
".strm",
|
||||||
|
".tp",
|
||||||
|
".f4v",
|
||||||
|
]
|
||||||
)
|
)
|
||||||
# 支持的字幕文件后缀格式
|
# 支持的字幕文件后缀格式
|
||||||
RMT_SUBEXT: list = Field(default_factory=lambda: ['.srt', '.ass', '.ssa', '.sup'])
|
RMT_SUBEXT: list = Field(default_factory=lambda: [".srt", ".ass", ".ssa", ".sup"])
|
||||||
# 支持的音轨文件后缀格式
|
# 支持的音轨文件后缀格式
|
||||||
RMT_AUDIOEXT: list = Field(
|
RMT_AUDIOEXT: list = Field(
|
||||||
default_factory=lambda: ['.aac', '.ac3', '.amr', '.caf', '.cda', '.dsf',
|
default_factory=lambda: [
|
||||||
'.dff', '.kar', '.m4a', '.mp1', '.mp2', '.mp3',
|
".aac",
|
||||||
'.mid', '.mod', '.mka', '.mpc', '.nsf', '.ogg',
|
".ac3",
|
||||||
'.pcm', '.rmi', '.s3m', '.snd', '.spx', '.tak',
|
".amr",
|
||||||
'.tta', '.vqf', '.wav', '.wma',
|
".caf",
|
||||||
'.aifc', '.aiff', '.alac', '.adif', '.adts',
|
".cda",
|
||||||
'.flac', '.midi', '.opus', '.sfalc']
|
".dsf",
|
||||||
|
".dff",
|
||||||
|
".kar",
|
||||||
|
".m4a",
|
||||||
|
".mp1",
|
||||||
|
".mp2",
|
||||||
|
".mp3",
|
||||||
|
".mid",
|
||||||
|
".mod",
|
||||||
|
".mka",
|
||||||
|
".mpc",
|
||||||
|
".nsf",
|
||||||
|
".ogg",
|
||||||
|
".pcm",
|
||||||
|
".rmi",
|
||||||
|
".s3m",
|
||||||
|
".snd",
|
||||||
|
".spx",
|
||||||
|
".tak",
|
||||||
|
".tta",
|
||||||
|
".vqf",
|
||||||
|
".wav",
|
||||||
|
".wma",
|
||||||
|
".aifc",
|
||||||
|
".aiff",
|
||||||
|
".alac",
|
||||||
|
".adif",
|
||||||
|
".adts",
|
||||||
|
".flac",
|
||||||
|
".midi",
|
||||||
|
".opus",
|
||||||
|
".sfalc",
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# ==================== 媒体服务器配置 ====================
|
# ==================== 媒体服务器配置 ====================
|
||||||
@@ -288,7 +338,7 @@ class ConfigModel(BaseModel):
|
|||||||
# 交互搜索自动下载用户ID,使用,分割
|
# 交互搜索自动下载用户ID,使用,分割
|
||||||
AUTO_DOWNLOAD_USER: Optional[str] = None
|
AUTO_DOWNLOAD_USER: Optional[str] = None
|
||||||
# 下载器临时文件后缀
|
# 下载器临时文件后缀
|
||||||
DOWNLOAD_TMPEXT: list = Field(default_factory=lambda: ['.!qb', '.part'])
|
DOWNLOAD_TMPEXT: list = Field(default_factory=lambda: [".!qb", ".part"])
|
||||||
|
|
||||||
# ==================== CookieCloud配置 ====================
|
# ==================== CookieCloud配置 ====================
|
||||||
# CookieCloud是否启动本地服务
|
# CookieCloud是否启动本地服务
|
||||||
@@ -308,14 +358,18 @@ class ConfigModel(BaseModel):
|
|||||||
# 文件整理线程数
|
# 文件整理线程数
|
||||||
TRANSFER_THREADS: int = 1
|
TRANSFER_THREADS: int = 1
|
||||||
# 电影重命名格式
|
# 电影重命名格式
|
||||||
MOVIE_RENAME_FORMAT: str = "{{title}}{% if year %} ({{year}}){% endif %}" \
|
MOVIE_RENAME_FORMAT: str = (
|
||||||
"/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}" \
|
"{{title}}{% if year %} ({{year}}){% endif %}"
|
||||||
"{{fileExt}}"
|
"/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}"
|
||||||
|
"{{fileExt}}"
|
||||||
|
)
|
||||||
# 电视剧重命名格式
|
# 电视剧重命名格式
|
||||||
TV_RENAME_FORMAT: str = "{{title}}{% if year %} ({{year}}){% endif %}" \
|
TV_RENAME_FORMAT: str = (
|
||||||
"/Season {{season}}" \
|
"{{title}}{% if year %} ({{year}}){% endif %}"
|
||||||
"/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}} 集{% endif %}" \
|
"/Season {{season}}"
|
||||||
"{{fileExt}}"
|
"/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}} 集{% endif %}"
|
||||||
|
"{{fileExt}}"
|
||||||
|
)
|
||||||
# 重命名时支持的S0别名
|
# 重命名时支持的S0别名
|
||||||
RENAME_FORMAT_S0_NAMES: list = Field(default=["Specials", "SPs"])
|
RENAME_FORMAT_S0_NAMES: list = Field(default=["Specials", "SPs"])
|
||||||
# 为指定默认字幕添加.default后缀
|
# 为指定默认字幕添加.default后缀
|
||||||
@@ -337,26 +391,28 @@ class ConfigModel(BaseModel):
|
|||||||
|
|
||||||
# ==================== 插件配置 ====================
|
# ==================== 插件配置 ====================
|
||||||
# 插件市场仓库地址,多个地址使用,分隔,地址以/结尾
|
# 插件市场仓库地址,多个地址使用,分隔,地址以/结尾
|
||||||
PLUGIN_MARKET: str = ("https://github.com/jxxghp/MoviePilot-Plugins,"
|
PLUGIN_MARKET: str = (
|
||||||
"https://github.com/thsrite/MoviePilot-Plugins,"
|
"https://github.com/jxxghp/MoviePilot-Plugins,"
|
||||||
"https://github.com/honue/MoviePilot-Plugins,"
|
"https://github.com/thsrite/MoviePilot-Plugins,"
|
||||||
"https://github.com/InfinityPacer/MoviePilot-Plugins,"
|
"https://github.com/honue/MoviePilot-Plugins,"
|
||||||
"https://github.com/DDSRem-Dev/MoviePilot-Plugins,"
|
"https://github.com/InfinityPacer/MoviePilot-Plugins,"
|
||||||
"https://github.com/madrays/MoviePilot-Plugins,"
|
"https://github.com/DDSRem-Dev/MoviePilot-Plugins,"
|
||||||
"https://github.com/justzerock/MoviePilot-Plugins,"
|
"https://github.com/madrays/MoviePilot-Plugins,"
|
||||||
"https://github.com/KoWming/MoviePilot-Plugins,"
|
"https://github.com/justzerock/MoviePilot-Plugins,"
|
||||||
"https://github.com/wikrin/MoviePilot-Plugins,"
|
"https://github.com/KoWming/MoviePilot-Plugins,"
|
||||||
"https://github.com/HankunYu/MoviePilot-Plugins,"
|
"https://github.com/wikrin/MoviePilot-Plugins,"
|
||||||
"https://github.com/baozaodetudou/MoviePilot-Plugins,"
|
"https://github.com/HankunYu/MoviePilot-Plugins,"
|
||||||
"https://github.com/Aqr-K/MoviePilot-Plugins,"
|
"https://github.com/baozaodetudou/MoviePilot-Plugins,"
|
||||||
"https://github.com/hotlcc/MoviePilot-Plugins-Third,"
|
"https://github.com/Aqr-K/MoviePilot-Plugins,"
|
||||||
"https://github.com/gxterry/MoviePilot-Plugins,"
|
"https://github.com/hotlcc/MoviePilot-Plugins-Third,"
|
||||||
"https://github.com/DzAvril/MoviePilot-Plugins,"
|
"https://github.com/gxterry/MoviePilot-Plugins,"
|
||||||
"https://github.com/mrtian2016/MoviePilot-Plugins,"
|
"https://github.com/DzAvril/MoviePilot-Plugins,"
|
||||||
"https://github.com/Hqyel/MoviePilot-Plugins-Third,"
|
"https://github.com/mrtian2016/MoviePilot-Plugins,"
|
||||||
"https://github.com/xijin285/MoviePilot-Plugins,"
|
"https://github.com/Hqyel/MoviePilot-Plugins-Third,"
|
||||||
"https://github.com/Seed680/MoviePilot-Plugins,"
|
"https://github.com/xijin285/MoviePilot-Plugins,"
|
||||||
"https://github.com/imaliang/MoviePilot-Plugins")
|
"https://github.com/Seed680/MoviePilot-Plugins,"
|
||||||
|
"https://github.com/imaliang/MoviePilot-Plugins"
|
||||||
|
)
|
||||||
# 插件安装数据共享
|
# 插件安装数据共享
|
||||||
PLUGIN_STATISTIC_SHARE: bool = True
|
PLUGIN_STATISTIC_SHARE: bool = True
|
||||||
# 是否开启插件热加载
|
# 是否开启插件热加载
|
||||||
@@ -366,9 +422,9 @@ class ConfigModel(BaseModel):
|
|||||||
# Github token,提高请求api限流阈值 ghp_****
|
# Github token,提高请求api限流阈值 ghp_****
|
||||||
GITHUB_TOKEN: Optional[str] = None
|
GITHUB_TOKEN: Optional[str] = None
|
||||||
# Github代理服务器,格式:https://mirror.ghproxy.com/
|
# Github代理服务器,格式:https://mirror.ghproxy.com/
|
||||||
GITHUB_PROXY: Optional[str] = ''
|
GITHUB_PROXY: Optional[str] = ""
|
||||||
# pip镜像站点,格式:https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
|
# pip镜像站点,格式:https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
|
||||||
PIP_PROXY: Optional[str] = ''
|
PIP_PROXY: Optional[str] = ""
|
||||||
# 指定的仓库Github token,多个仓库使用,分隔,格式:{user1}/{repo1}:ghp_****,{user2}/{repo2}:github_pat_****
|
# 指定的仓库Github token,多个仓库使用,分隔,格式:{user1}/{repo1}:ghp_****,{user2}/{repo2}:github_pat_****
|
||||||
REPO_GITHUB_TOKEN: Optional[str] = None
|
REPO_GITHUB_TOKEN: Optional[str] = None
|
||||||
|
|
||||||
@@ -384,24 +440,28 @@ class ConfigModel(BaseModel):
|
|||||||
|
|
||||||
# ==================== 安全配置 ====================
|
# ==================== 安全配置 ====================
|
||||||
# 允许的图片缓存域名
|
# 允许的图片缓存域名
|
||||||
SECURITY_IMAGE_DOMAINS: list = Field(default=[
|
SECURITY_IMAGE_DOMAINS: list = Field(
|
||||||
"image.tmdb.org",
|
default=[
|
||||||
"static-mdb.v.geilijiasu.com",
|
"image.tmdb.org",
|
||||||
"bing.com",
|
"static-mdb.v.geilijiasu.com",
|
||||||
"doubanio.com",
|
"bing.com",
|
||||||
"lain.bgm.tv",
|
"doubanio.com",
|
||||||
"raw.githubusercontent.com",
|
"lain.bgm.tv",
|
||||||
"github.com",
|
"raw.githubusercontent.com",
|
||||||
"thetvdb.com",
|
"github.com",
|
||||||
"cctvpic.com",
|
"thetvdb.com",
|
||||||
"iqiyipic.com",
|
"cctvpic.com",
|
||||||
"hdslb.com",
|
"iqiyipic.com",
|
||||||
"cmvideo.cn",
|
"hdslb.com",
|
||||||
"ykimg.com",
|
"cmvideo.cn",
|
||||||
"qpic.cn"
|
"ykimg.com",
|
||||||
])
|
"qpic.cn",
|
||||||
|
]
|
||||||
|
)
|
||||||
# 允许的图片文件后缀格式
|
# 允许的图片文件后缀格式
|
||||||
SECURITY_IMAGE_SUFFIXES: list = Field(default=[".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".avif"])
|
SECURITY_IMAGE_SUFFIXES: list = Field(
|
||||||
|
default=[".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".avif"]
|
||||||
|
)
|
||||||
# PassKey 是否强制用户验证(生物识别等)
|
# PassKey 是否强制用户验证(生物识别等)
|
||||||
PASSKEY_REQUIRE_UV: bool = True
|
PASSKEY_REQUIRE_UV: bool = True
|
||||||
# 允许在未启用 OTP 时直接注册 PassKey
|
# 允许在未启用 OTP 时直接注册 PassKey
|
||||||
@@ -459,12 +519,16 @@ class ConfigModel(BaseModel):
|
|||||||
# AI推荐用户偏好
|
# AI推荐用户偏好
|
||||||
AI_RECOMMEND_USER_PREFERENCE: str = ""
|
AI_RECOMMEND_USER_PREFERENCE: str = ""
|
||||||
# Tavily API密钥(用于网络搜索)
|
# Tavily API密钥(用于网络搜索)
|
||||||
TAVILY_API_KEY: List[str] = ["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",
|
TAVILY_API_KEY: List[str] = [
|
||||||
"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",
|
"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",
|
||||||
"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"]
|
"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",
|
||||||
|
"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",
|
||||||
|
]
|
||||||
|
|
||||||
# AI推荐条目数量限制
|
# AI推荐条目数量限制
|
||||||
AI_RECOMMEND_MAX_ITEMS: int = 50
|
AI_RECOMMEND_MAX_ITEMS: int = 50
|
||||||
|
# LLM工具选择中间件最大工具数量,0为不启用工具选择中间件
|
||||||
|
LLM_MAX_TOOLS: int = 0
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
||||||
@@ -501,15 +565,25 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
if not value or len(value) < 16:
|
if not value or len(value) < 16:
|
||||||
new_token = secrets.token_urlsafe(16)
|
new_token = secrets.token_urlsafe(16)
|
||||||
if not value:
|
if not value:
|
||||||
logger.info(f"'API_TOKEN' 未设置,已随机生成新的【API_TOKEN】{new_token}")
|
logger.info(
|
||||||
|
f"'API_TOKEN' 未设置,已随机生成新的【API_TOKEN】{new_token}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"'API_TOKEN' 长度不足 16 个字符,存在安全隐患,已随机生成新的【API_TOKEN】{new_token}")
|
logger.warning(
|
||||||
|
f"'API_TOKEN' 长度不足 16 个字符,存在安全隐患,已随机生成新的【API_TOKEN】{new_token}"
|
||||||
|
)
|
||||||
return new_token, True
|
return new_token, True
|
||||||
return value, str(value) != str(original_value)
|
return value, str(value) != str(original_value)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generic_type_converter(value: Any, original_value: Any, expected_type: Type, default: Any, field_name: str,
|
def generic_type_converter(
|
||||||
raise_exception: bool = False) -> Tuple[Any, bool]:
|
value: Any,
|
||||||
|
original_value: Any,
|
||||||
|
expected_type: Type,
|
||||||
|
default: Any,
|
||||||
|
field_name: str,
|
||||||
|
raise_exception: bool = False,
|
||||||
|
) -> Tuple[Any, bool]:
|
||||||
"""
|
"""
|
||||||
通用类型转换函数,根据预期类型转换值。如果转换失败,返回默认值
|
通用类型转换函数,根据预期类型转换值。如果转换失败,返回默认值
|
||||||
:return: 元组 (转换后的值, 是否需要更新)
|
:return: 元组 (转换后的值, 是否需要更新)
|
||||||
@@ -530,15 +604,25 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value_clean = value.lower()
|
value_clean = value.lower()
|
||||||
bool_map = {
|
bool_map = {
|
||||||
"false": False, "no": False, "0": False, "off": False,
|
"false": False,
|
||||||
"true": True, "yes": True, "1": True, "on": True
|
"no": False,
|
||||||
|
"0": False,
|
||||||
|
"off": False,
|
||||||
|
"true": True,
|
||||||
|
"yes": True,
|
||||||
|
"1": True,
|
||||||
|
"on": True,
|
||||||
}
|
}
|
||||||
if value_clean in bool_map:
|
if value_clean in bool_map:
|
||||||
converted = bool_map[value_clean]
|
converted = bool_map[value_clean]
|
||||||
return converted, str(converted).lower() != str(original_value).lower()
|
return converted, str(converted).lower() != str(
|
||||||
|
original_value
|
||||||
|
).lower()
|
||||||
elif isinstance(value, (int, float)):
|
elif isinstance(value, (int, float)):
|
||||||
converted = bool(value)
|
converted = bool(value)
|
||||||
return converted, str(converted).lower() != str(original_value).lower()
|
return converted, str(converted).lower() != str(
|
||||||
|
original_value
|
||||||
|
).lower()
|
||||||
return default, True
|
return default, True
|
||||||
elif expected_type is int:
|
elif expected_type is int:
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
@@ -568,12 +652,15 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
return value, str(value) != str(original_value)
|
return value, str(value) != str(original_value)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
if raise_exception:
|
if raise_exception:
|
||||||
raise ValueError(f"配置项 '{field_name}' 的值 '{value}' 无法转换成正确的类型") from e
|
raise ValueError(
|
||||||
|
f"配置项 '{field_name}' 的值 '{value}' 无法转换成正确的类型"
|
||||||
|
) from e
|
||||||
logger.error(
|
logger.error(
|
||||||
f"配置项 '{field_name}' 的值 '{value}' 无法转换成正确的类型,使用默认值 '{default}',错误信息: {e}")
|
f"配置项 '{field_name}' 的值 '{value}' 无法转换成正确的类型,使用默认值 '{default}',错误信息: {e}"
|
||||||
|
)
|
||||||
return default, True
|
return default, True
|
||||||
|
|
||||||
@model_validator(mode='before')
|
@model_validator(mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def generic_type_validator(cls, data: Any): # noqa
|
def generic_type_validator(cls, data: Any): # noqa
|
||||||
"""
|
"""
|
||||||
@@ -583,11 +670,13 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
# 处理 API_TOKEN 特殊验证
|
# 处理 API_TOKEN 特殊验证
|
||||||
if 'API_TOKEN' in data:
|
if "API_TOKEN" in data:
|
||||||
converted_value, needs_update = cls.validate_api_token(data['API_TOKEN'], data['API_TOKEN'])
|
converted_value, needs_update = cls.validate_api_token(
|
||||||
|
data["API_TOKEN"], data["API_TOKEN"]
|
||||||
|
)
|
||||||
if needs_update:
|
if needs_update:
|
||||||
cls.update_env_config("API_TOKEN", data["API_TOKEN"], converted_value)
|
cls.update_env_config("API_TOKEN", data["API_TOKEN"], converted_value)
|
||||||
data['API_TOKEN'] = converted_value
|
data["API_TOKEN"] = converted_value
|
||||||
|
|
||||||
# 对其他字段进行类型转换
|
# 对其他字段进行类型转换
|
||||||
for field_name, field_info in cls.model_fields.items():
|
for field_name, field_info in cls.model_fields.items():
|
||||||
@@ -609,18 +698,24 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_env_config(field_name: str, original_value: Any, converted_value: Any) -> Tuple[bool, str]:
|
def update_env_config(
|
||||||
|
field_name: str, original_value: Any, converted_value: Any
|
||||||
|
) -> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
更新 env 配置
|
更新 env 配置
|
||||||
"""
|
"""
|
||||||
message = None
|
message = None
|
||||||
is_converted = original_value is not None and str(original_value) != str(converted_value)
|
is_converted = original_value is not None and str(original_value) != str(
|
||||||
|
converted_value
|
||||||
|
)
|
||||||
if is_converted:
|
if is_converted:
|
||||||
message = f"配置项 '{field_name}' 的值 '{original_value}' 无效,已替换为 '{converted_value}'"
|
message = f"配置项 '{field_name}' 的值 '{original_value}' 无效,已替换为 '{converted_value}'"
|
||||||
logger.warning(message)
|
logger.warning(message)
|
||||||
|
|
||||||
if field_name in os.environ:
|
if field_name in os.environ:
|
||||||
message = f"配置项 '{field_name}' 已在环境变量中设置,请手动更新以保持一致性"
|
message = (
|
||||||
|
f"配置项 '{field_name}' 已在环境变量中设置,请手动更新以保持一致性"
|
||||||
|
)
|
||||||
logger.warning(message)
|
logger.warning(message)
|
||||||
return False, message
|
return False, message
|
||||||
else:
|
else:
|
||||||
@@ -628,10 +723,16 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
if isinstance(converted_value, (list, dict, set)):
|
if isinstance(converted_value, (list, dict, set)):
|
||||||
value_to_write = json.dumps(converted_value)
|
value_to_write = json.dumps(converted_value)
|
||||||
else:
|
else:
|
||||||
value_to_write = str(converted_value) if converted_value is not None else ""
|
value_to_write = (
|
||||||
|
str(converted_value) if converted_value is not None else ""
|
||||||
|
)
|
||||||
|
|
||||||
set_key(dotenv_path=SystemUtils.get_env_path(), key_to_set=field_name, value_to_set=value_to_write,
|
set_key(
|
||||||
quote_mode="always")
|
dotenv_path=SystemUtils.get_env_path(),
|
||||||
|
key_to_set=field_name,
|
||||||
|
value_to_set=value_to_write,
|
||||||
|
quote_mode="always",
|
||||||
|
)
|
||||||
if is_converted:
|
if is_converted:
|
||||||
logger.info(f"配置项 '{field_name}' 已自动修正并写入到 'app.env' 文件")
|
logger.info(f"配置项 '{field_name}' 已自动修正并写入到 'app.env' 文件")
|
||||||
return True, message
|
return True, message
|
||||||
@@ -650,7 +751,9 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
field = Settings.model_fields[key]
|
field = Settings.model_fields[key]
|
||||||
original_value = getattr(self, key)
|
original_value = getattr(self, key)
|
||||||
if key == "API_TOKEN":
|
if key == "API_TOKEN":
|
||||||
converted_value, needs_update = self.validate_api_token(value, original_value)
|
converted_value, needs_update = self.validate_api_token(
|
||||||
|
value, original_value
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
converted_value, needs_update = self.generic_type_converter(
|
converted_value, needs_update = self.generic_type_converter(
|
||||||
value, original_value, field.annotation, field.default, key
|
value, original_value, field.annotation, field.default, key
|
||||||
@@ -668,7 +771,9 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
|
|
||||||
def update_settings(self, env: Dict[str, Any]) -> Dict[str, Tuple[Optional[bool], str]]:
|
def update_settings(
|
||||||
|
self, env: Dict[str, Any]
|
||||||
|
) -> Dict[str, Tuple[Optional[bool], str]]:
|
||||||
"""
|
"""
|
||||||
更新多个配置项
|
更新多个配置项
|
||||||
"""
|
"""
|
||||||
@@ -751,7 +856,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
fanart=512,
|
fanart=512,
|
||||||
meta=(self.META_CACHE_EXPIRE or 72) * 3600,
|
meta=(self.META_CACHE_EXPIRE or 72) * 3600,
|
||||||
scheduler=100,
|
scheduler=100,
|
||||||
threadpool=100
|
threadpool=100,
|
||||||
)
|
)
|
||||||
return SystemConfModel(
|
return SystemConfModel(
|
||||||
torrents=100,
|
torrents=100,
|
||||||
@@ -762,7 +867,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
fanart=128,
|
fanart=128,
|
||||||
meta=(self.META_CACHE_EXPIRE or 24) * 3600,
|
meta=(self.META_CACHE_EXPIRE or 24) * 3600,
|
||||||
scheduler=50,
|
scheduler=50,
|
||||||
threadpool=50
|
threadpool=50,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -844,7 +949,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
return {
|
return {
|
||||||
"subject": f"mailto:{self.SUPERUSER}@movie-pilot.org",
|
"subject": f"mailto:{self.SUPERUSER}@movie-pilot.org",
|
||||||
"publicKey": "BH3w49sZA6jXUnE-yt4jO6VKh73lsdsvwoJ6Hx7fmPIDKoqGiUl2GEoZzy-iJfn4SfQQcx7yQdHf9RknwrL_lSM",
|
"publicKey": "BH3w49sZA6jXUnE-yt4jO6VKh73lsdsvwoJ6Hx7fmPIDKoqGiUl2GEoZzy-iJfn4SfQQcx7yQdHf9RknwrL_lSM",
|
||||||
"privateKey": "JTixnYY0vEw97t9uukfO3UWKfHKJdT5kCQDiv3gu894"
|
"privateKey": "JTixnYY0vEw97t9uukfO3UWKfHKJdT5kCQDiv3gu894",
|
||||||
}
|
}
|
||||||
|
|
||||||
def MP_DOMAIN(self, url: str = None):
|
def MP_DOMAIN(self, url: str = None):
|
||||||
@@ -866,11 +971,11 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
)
|
)
|
||||||
# 规范重命名格式
|
# 规范重命名格式
|
||||||
rename_format = rename_format.replace("\\", "/")
|
rename_format = rename_format.replace("\\", "/")
|
||||||
rename_format = re.sub(r'/+', '/', rename_format)
|
rename_format = re.sub(r"/+", "/", rename_format)
|
||||||
return rename_format.strip("/")
|
return rename_format.strip("/")
|
||||||
|
|
||||||
def TMDB_IMAGE_URL(
|
def TMDB_IMAGE_URL(
|
||||||
self, file_path: Optional[str], file_size: str = "original"
|
self, file_path: Optional[str], file_size: str = "original"
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
获取TMDB图片网址
|
获取TMDB图片网址
|
||||||
@@ -881,9 +986,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
"""
|
"""
|
||||||
if not file_path:
|
if not file_path:
|
||||||
return None
|
return None
|
||||||
return (
|
return f"https://{self.TMDB_IMAGE_DOMAIN}/t/p/{file_size}/{file_path.removeprefix('/')}"
|
||||||
f"https://{self.TMDB_IMAGE_DOMAIN}/t/p/{file_size}/{file_path.removeprefix('/')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# 实例化配置
|
# 实例化配置
|
||||||
@@ -894,6 +997,7 @@ class GlobalVar(object):
|
|||||||
"""
|
"""
|
||||||
全局标识
|
全局标识
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 系统停止事件
|
# 系统停止事件
|
||||||
STOP_EVENT: threading.Event = threading.Event()
|
STOP_EVENT: threading.Event = threading.Event()
|
||||||
# webpush订阅
|
# webpush订阅
|
||||||
|
|||||||
Reference in New Issue
Block a user