mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
refactor: complete runtime configuration migration
This commit is contained in:
@@ -9,6 +9,10 @@ from typing import Optional, Any, Tuple, List, Set, Union, Dict
|
||||
|
||||
from app.application.chain.context import ChainRuntimeContext, get_chain_runtime_context
|
||||
from app.application.chain.data import get_chain_data_ports
|
||||
from app.application.configuration import (
|
||||
ChainRuntimeConfig,
|
||||
get_chain_runtime_config_snapshot,
|
||||
)
|
||||
from app.chain._messaging import MessageProcessingMixin, NotificationMixin
|
||||
from app.chain._recognition import RecognitionMixin
|
||||
from app.domain.context import Context, MediaInfo, SubtitleInfo, TorrentInfo
|
||||
@@ -64,6 +68,19 @@ class ChainBase(RecognitionMixin, MessageProcessingMixin, NotificationMixin,
|
||||
)
|
||||
self.messagequeue = context.message_queue_factory(self.run_module)
|
||||
|
||||
@property
|
||||
def runtime_config(self) -> ChainRuntimeConfig:
|
||||
"""返回实例快照;兼容绕过构造器的旧调用并按需取得当前快照。"""
|
||||
configuration = getattr(self, "_runtime_config", None)
|
||||
if configuration is None:
|
||||
return get_chain_runtime_config_snapshot()
|
||||
return configuration
|
||||
|
||||
@runtime_config.setter
|
||||
def runtime_config(self, configuration: ChainRuntimeConfig) -> None:
|
||||
"""保存显式注入的 Chain 配置快照。"""
|
||||
self._runtime_config = configuration
|
||||
|
||||
def load_cache(self, filename: str) -> Any:
|
||||
"""
|
||||
加载缓存
|
||||
|
||||
+13
-10
@@ -26,13 +26,16 @@ from app.application.chain.data import (
|
||||
DownloadHistoryPortProxy as DownloadHistoryOper,
|
||||
TransferHistoryPortProxy as TransferHistoryOper,
|
||||
)
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.configuration import (
|
||||
get_chain_runtime_config_snapshot,
|
||||
get_configured_system_config,
|
||||
)
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.domain.media import normalize_music_type
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.foundation import text as text_tools
|
||||
from app.runtime.config import global_vars, settings
|
||||
from app.runtime.config import global_vars
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.workflow import FileItem
|
||||
from app.schemas.message import Message
|
||||
@@ -320,7 +323,7 @@ class FileFilterMixin:
|
||||
"""
|
||||
if history.type == MediaType.MUSIC.value:
|
||||
return True
|
||||
return src_path.suffix.lower() in settings.RMT_AUDIOEXT
|
||||
return src_path.suffix.lower() in get_chain_runtime_config_snapshot().audio_extensions
|
||||
|
||||
def _recognize_music_retry_media(
|
||||
self,
|
||||
@@ -1338,7 +1341,7 @@ class FailedRetryMixin:
|
||||
userid=userid,
|
||||
username=username,
|
||||
title=f"整理记录 #{history_id} 已重新整理",
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
save_history=False,
|
||||
)
|
||||
)
|
||||
@@ -1352,7 +1355,7 @@ class FailedRetryMixin:
|
||||
username=username,
|
||||
title="重新整理失败",
|
||||
text=errmsg,
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
save_history=False,
|
||||
)
|
||||
)
|
||||
@@ -1369,7 +1372,7 @@ class FailedRetryMixin:
|
||||
由智能助手接管一条失败的整理记录。
|
||||
"""
|
||||
|
||||
if not settings.AI_AGENT_ENABLE:
|
||||
if not self.runtime_config.ai_agent_enable:
|
||||
self.post_message(
|
||||
Message(
|
||||
channel=channel,
|
||||
@@ -1392,7 +1395,7 @@ class FailedRetryMixin:
|
||||
username=username,
|
||||
title="重新整理失败",
|
||||
text=f"整理记录 #{history_id} 不存在",
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
save_history=False,
|
||||
)
|
||||
)
|
||||
@@ -1408,7 +1411,7 @@ class FailedRetryMixin:
|
||||
username=username,
|
||||
title=f"已将整理记录 #{history_id} 交给智能助手处理",
|
||||
text="处理完成后会在这里回复结果。",
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
save_history=False,
|
||||
)
|
||||
)
|
||||
@@ -1440,7 +1443,7 @@ class FailedRetryMixin:
|
||||
title="智能助手整理完成",
|
||||
text=final_output.strip()
|
||||
or f"整理记录 #{history_id} 已由智能助手处理完成。",
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
save_history=False,
|
||||
)
|
||||
)
|
||||
@@ -1453,7 +1456,7 @@ class FailedRetryMixin:
|
||||
username=username,
|
||||
title="智能助手整理失败",
|
||||
text=str(e),
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
save_history=False,
|
||||
)
|
||||
)
|
||||
|
||||
+36
-22
@@ -16,7 +16,8 @@ from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.runtime.cache import FileCache
|
||||
from app.runtime.config import settings, global_vars
|
||||
from app.runtime.config import global_vars
|
||||
from app.application.configuration import get_chain_runtime_config_snapshot
|
||||
from app.domain.context import (
|
||||
Context,
|
||||
MediaInfo,
|
||||
@@ -128,7 +129,7 @@ class DownloadChain(ChainBase):
|
||||
mtype=MessageType.Download,
|
||||
ctype=ContentType.DownloadAdded,
|
||||
image=media.get_message_image(),
|
||||
link=settings.MP_DOMAIN('/#/downloading'),
|
||||
link=self.runtime_config.downloading_url,
|
||||
userid=userid,
|
||||
username=username,
|
||||
),
|
||||
@@ -171,7 +172,8 @@ class DownloadChain(ChainBase):
|
||||
track_identities = {
|
||||
identity
|
||||
for file in file_list
|
||||
if Path(str(file)).suffix.lower() in settings.RMT_AUDIOEXT
|
||||
if Path(str(file)).suffix.lower()
|
||||
in get_chain_runtime_config_snapshot().audio_extensions
|
||||
and (identity := DownloadChain._music_resource_track_identity(file))
|
||||
}
|
||||
actual_tracks = len(track_identities)
|
||||
@@ -264,7 +266,10 @@ class DownloadChain(ChainBase):
|
||||
"""
|
||||
判断是否为支持的字幕文件。
|
||||
"""
|
||||
return Path(file_name).suffix.lower() in settings.RMT_SUBEXT
|
||||
return (
|
||||
Path(file_name).suffix.lower()
|
||||
in get_chain_runtime_config_snapshot().subtitle_extensions
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_subtitle_working_dir(
|
||||
@@ -441,10 +446,10 @@ class DownloadChain(ChainBase):
|
||||
return False, message, []
|
||||
|
||||
saved_files = []
|
||||
temp_file = settings.TEMP_PATH / file_name
|
||||
temp_file = self.runtime_config.temporary_path / file_name
|
||||
temp_extract_dir = temp_file.with_name(temp_file.stem)
|
||||
try:
|
||||
settings.TEMP_PATH.mkdir(parents=True, exist_ok=True)
|
||||
self.runtime_config.temporary_path.mkdir(parents=True, exist_ok=True)
|
||||
temp_file.write_bytes(response.content)
|
||||
if self._is_subtitle_archive(file_name):
|
||||
try:
|
||||
@@ -457,7 +462,10 @@ class DownloadChain(ChainBase):
|
||||
message = f"字幕压缩包解压失败:{str(err)}"
|
||||
logger.error(f"{message},文件:{temp_file}")
|
||||
return False, message, []
|
||||
for sub_file in SystemUtils.list_files(temp_extract_dir, settings.RMT_SUBEXT):
|
||||
for sub_file in SystemUtils.list_files(
|
||||
temp_extract_dir,
|
||||
self.runtime_config.subtitle_extensions,
|
||||
):
|
||||
uploaded_path, message = self._upload_subtitle_file(
|
||||
storage_chain=storage_chain,
|
||||
storage=storage,
|
||||
@@ -537,8 +545,8 @@ class DownloadChain(ChainBase):
|
||||
|
||||
request = RequestUtils(
|
||||
cookies=subtitle.site_cookie,
|
||||
ua=subtitle.site_ua or settings.USER_AGENT,
|
||||
proxies=settings.PROXY if subtitle.site_proxy else None,
|
||||
ua=subtitle.site_ua or self.runtime_config.user_agent,
|
||||
proxies=self.runtime_config.proxy if subtitle.site_proxy else None,
|
||||
)
|
||||
try:
|
||||
response = request.get_res(subtitle.enclosure, raise_exception=True)
|
||||
@@ -621,7 +629,7 @@ class DownloadChain(ChainBase):
|
||||
:param download_dir: 下载目录
|
||||
:param torrent_content: 种子内容,如果是种子文件,则为文件内容,否则为种子字符串
|
||||
"""
|
||||
if not settings.DOWNLOAD_SUBTITLE:
|
||||
if not self.runtime_config.download_subtitle:
|
||||
return
|
||||
|
||||
# 没有种子文件不处理
|
||||
@@ -673,9 +681,9 @@ class DownloadChain(ChainBase):
|
||||
request = RequestUtils(
|
||||
cookies=torrent.site_cookie,
|
||||
ua=torrent.site_ua,
|
||||
proxies=settings.PROXY if torrent.site_proxy else None,
|
||||
proxies=self.runtime_config.proxy if torrent.site_proxy else None,
|
||||
)
|
||||
settings.TEMP_PATH.mkdir(parents=True, exist_ok=True)
|
||||
self.runtime_config.temporary_path.mkdir(parents=True, exist_ok=True)
|
||||
for sublink in sublink_list:
|
||||
logger.info(f"找到字幕下载链接:{sublink},开始下载...")
|
||||
# 下载
|
||||
@@ -687,7 +695,7 @@ class DownloadChain(ChainBase):
|
||||
continue
|
||||
archive_format = self._SUBTITLE_ARCHIVE_FORMATS.get(Path(file_name).suffix.lower())
|
||||
if archive_format:
|
||||
archive_file = settings.TEMP_PATH / file_name
|
||||
archive_file = self.runtime_config.temporary_path / file_name
|
||||
# 保存
|
||||
archive_file.write_bytes(ret.content)
|
||||
# 解压路径
|
||||
@@ -700,7 +708,10 @@ class DownloadChain(ChainBase):
|
||||
archive_format=archive_format,
|
||||
)
|
||||
# 遍历转移文件
|
||||
for sub_file in SystemUtils.list_files(archive_path, settings.RMT_SUBEXT):
|
||||
for sub_file in SystemUtils.list_files(
|
||||
archive_path,
|
||||
self.runtime_config.subtitle_extensions,
|
||||
):
|
||||
target_sub_file = Path(working_dir_item.path) / Path(sub_file.name)
|
||||
if storage_chain.get_file_item(storage, target_sub_file):
|
||||
logger.info(f"字幕文件已存在:{target_sub_file}")
|
||||
@@ -718,10 +729,13 @@ class DownloadChain(ChainBase):
|
||||
except Exception as err:
|
||||
logger.error(f"删除临时文件失败:{str(err)}")
|
||||
else:
|
||||
if Path(file_name).suffix.lower() not in settings.RMT_SUBEXT:
|
||||
if (
|
||||
Path(file_name).suffix.lower()
|
||||
not in self.runtime_config.subtitle_extensions
|
||||
):
|
||||
logger.warn(f"链接不是支持的字幕文件:{sublink} - {file_name}")
|
||||
continue
|
||||
sub_file = settings.TEMP_PATH / file_name
|
||||
sub_file = self.runtime_config.temporary_path / file_name
|
||||
# 保存
|
||||
sub_file.write_bytes(ret.content)
|
||||
target_sub_file = Path(working_dir_item.path) / Path(sub_file.name)
|
||||
@@ -953,7 +967,7 @@ class DownloadChain(ChainBase):
|
||||
ua=ua,
|
||||
cookies=cookie,
|
||||
headers=headers,
|
||||
proxies=settings.PROXY if proxy else None
|
||||
proxies=get_chain_runtime_config_snapshot().proxy if proxy else None
|
||||
).get_res(url, params=req_params.get('params'))
|
||||
else:
|
||||
# POST请求
|
||||
@@ -961,7 +975,7 @@ class DownloadChain(ChainBase):
|
||||
ua=ua,
|
||||
cookies=cookie,
|
||||
headers=headers,
|
||||
proxies=settings.PROXY if proxy else None
|
||||
proxies=get_chain_runtime_config_snapshot().proxy if proxy else None
|
||||
).post_res(url, params=req_params.get('params'))
|
||||
if not res:
|
||||
return None
|
||||
@@ -1016,7 +1030,7 @@ class DownloadChain(ChainBase):
|
||||
_, content, download_folder, files, error_msg = TorrentHelper().download_torrent(
|
||||
url=torrent_url,
|
||||
cookie=site_cookie,
|
||||
ua=torrent.site_ua or settings.USER_AGENT,
|
||||
ua=torrent.site_ua or self.runtime_config.user_agent,
|
||||
proxy=torrent.site_proxy,
|
||||
cache_invalid=not indirect_download)
|
||||
|
||||
@@ -1255,7 +1269,7 @@ class DownloadChain(ChainBase):
|
||||
or file_meta.begin_episode not in episodes:
|
||||
continue
|
||||
# 只处理音视频、字幕格式
|
||||
media_exts = settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIOEXT
|
||||
media_exts = self.runtime_config.media_extensions
|
||||
if not Path(file).suffix \
|
||||
or Path(file).suffix.lower() not in media_exts:
|
||||
continue
|
||||
@@ -2074,7 +2088,7 @@ class DownloadChain(ChainBase):
|
||||
mtype=MessageType.Download,
|
||||
title="没有正在下载的任务!",
|
||||
userid=userid,
|
||||
link=settings.MP_DOMAIN('#/downloading'),
|
||||
link=self.runtime_config.downloading_url,
|
||||
save_history=False,
|
||||
))
|
||||
return
|
||||
@@ -2094,7 +2108,7 @@ class DownloadChain(ChainBase):
|
||||
title=title,
|
||||
text="\n".join(messages),
|
||||
userid=userid,
|
||||
link=settings.MP_DOMAIN('#/downloading'),
|
||||
link=self.runtime_config.downloading_url,
|
||||
save_history=False,
|
||||
))
|
||||
|
||||
|
||||
+6
-6
@@ -12,7 +12,7 @@ from app.chain.douban import DoubanChain
|
||||
from app.chain.musicbrainz import MusicBrainzChain, _MusicMetadataSourceChain
|
||||
from app.chain.theaudiodb import TheAudioDbChain
|
||||
from app.runtime.cache import async_fresh, fresh
|
||||
from app.runtime.config import settings
|
||||
from app.application.configuration import get_chain_runtime_config_snapshot
|
||||
from app.domain.context import (
|
||||
Context,
|
||||
MediaInfo,
|
||||
@@ -172,7 +172,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
@classmethod
|
||||
def _simplify_recognized_music_info(cls, info: MusicInfo) -> MusicInfo:
|
||||
"""按开关转换标准音乐文本字段,并避免修改来源模块的缓存对象。"""
|
||||
if not settings.MUSIC_METADATA_TO_SIMPLIFIED:
|
||||
if not get_chain_runtime_config_snapshot().music_metadata_to_simplified:
|
||||
return info
|
||||
updates: dict[str, Any] = {}
|
||||
for field_name in cls._music_simplified_text_fields:
|
||||
@@ -462,7 +462,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
is_recognized = lambda result: bool(result)
|
||||
mediainfo = None
|
||||
plugin_available = eventmanager.check(plugin_event)
|
||||
if settings.RECOGNIZE_PLUGIN_FIRST and plugin_available:
|
||||
if get_chain_runtime_config_snapshot().recognize_plugin_first and plugin_available:
|
||||
# 插件优先
|
||||
logger.info(f"插件识别优先模式已开启。请求辅助识别,标题:{log_name} ...")
|
||||
helped = plugin_fn()
|
||||
@@ -912,7 +912,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
@classmethod
|
||||
def is_audio_path(cls, path: Union[str, Path]) -> bool:
|
||||
"""判断路径是否指向系统支持的音频文件。"""
|
||||
return Path(path).suffix.lower() in settings.RMT_AUDIOEXT
|
||||
return Path(path).suffix.lower() in get_chain_runtime_config_snapshot().audio_extensions
|
||||
|
||||
@classmethod
|
||||
def read_path_meta(cls, path: Union[str, Path]) -> MetaMusic:
|
||||
@@ -1094,7 +1094,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
item for item in entries
|
||||
if not item.name.startswith(".")
|
||||
and item.is_file()
|
||||
and item.suffix.lower() in settings.RMT_AUDIOEXT
|
||||
and item.suffix.lower() in get_chain_runtime_config_snapshot().audio_extensions
|
||||
)
|
||||
|
||||
collect(directory)
|
||||
@@ -1525,7 +1525,7 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
is_recognized = lambda result: bool(result)
|
||||
mediainfo = None
|
||||
plugin_available = eventmanager.check(plugin_event)
|
||||
if settings.RECOGNIZE_PLUGIN_FIRST and plugin_available:
|
||||
if get_chain_runtime_config_snapshot().recognize_plugin_first and plugin_available:
|
||||
# 插件优先
|
||||
logger.info(f"插件优先模式已开启。请求辅助识别,标题:{log_name} ...")
|
||||
helped = await plugin_fn()
|
||||
|
||||
+19
-9
@@ -21,7 +21,7 @@ from app.chain.site import SiteChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.chain.interaction import MediaInteractionChain as _MediaInteractionChain
|
||||
from app.runtime.config import settings, global_vars
|
||||
from app.runtime.config import global_vars
|
||||
from app.application.messaging.agent import agent_interaction_manager, parse_agent_choice_callback
|
||||
from app.application.messaging.interaction import InteractionContext, InteractionDispatch
|
||||
from app.application.messaging.media import media_interaction_manager
|
||||
@@ -477,8 +477,13 @@ class MessageChain(ChainBase):
|
||||
if (
|
||||
not no_ai_requested
|
||||
and
|
||||
settings.AI_AGENT_ENABLE
|
||||
and (settings.AI_AGENT_GLOBAL or images or files or has_audio_input)
|
||||
self.runtime_config.ai_agent_enable
|
||||
and (
|
||||
self.runtime_config.ai_agent_global
|
||||
or images
|
||||
or files
|
||||
or has_audio_input
|
||||
)
|
||||
):
|
||||
return self._handle_ai_message(
|
||||
text=text,
|
||||
@@ -554,8 +559,13 @@ class MessageChain(ChainBase):
|
||||
if text.startswith("/"):
|
||||
return False
|
||||
if not (
|
||||
settings.AI_AGENT_ENABLE
|
||||
and (settings.AI_AGENT_GLOBAL or images or files or has_audio_input)
|
||||
self.runtime_config.ai_agent_enable
|
||||
and (
|
||||
self.runtime_config.ai_agent_global
|
||||
or images
|
||||
or files
|
||||
or has_audio_input
|
||||
)
|
||||
):
|
||||
return False
|
||||
if self._interaction_router().has_pending(userid):
|
||||
@@ -1223,7 +1233,7 @@ class MessageChain(ChainBase):
|
||||
"""
|
||||
try:
|
||||
# 检查AI智能体是否启用
|
||||
if not settings.AI_AGENT_ENABLE:
|
||||
if not self.runtime_config.ai_agent_enable:
|
||||
self.post_message(
|
||||
Message(
|
||||
channel=channel,
|
||||
@@ -1280,8 +1290,8 @@ class MessageChain(ChainBase):
|
||||
original_images = images
|
||||
all_files = list(files or [])
|
||||
if images and supports_image_input(
|
||||
provider=settings.LLM_PROVIDER,
|
||||
model=settings.LLM_MODEL,
|
||||
provider=self.runtime_config.llm_provider,
|
||||
model=self.runtime_config.llm_model,
|
||||
):
|
||||
images = self._download_attachments_to_data_urls(
|
||||
images, channel, source
|
||||
@@ -1829,7 +1839,7 @@ class MessageChain(ChainBase):
|
||||
将用户上传文件写入临时目录,并返回本地路径。
|
||||
"""
|
||||
safe_name = self._sanitize_attachment_name(filename, mime_type)
|
||||
base_dir = settings.TEMP_PATH / "agent_uploads" / session_id
|
||||
base_dir = self.runtime_config.temporary_path / "agent_uploads" / session_id
|
||||
base_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
file_id = uuid.uuid4().hex[:8]
|
||||
|
||||
+19
-11
@@ -12,7 +12,6 @@ from app.chain import ChainBase
|
||||
from app.chain.lrclib import LrclibChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.runtime.cache import cached
|
||||
from app.runtime.config import settings
|
||||
from app.domain.context import (
|
||||
MediaInfo,
|
||||
MusicAlbumInfo,
|
||||
@@ -23,7 +22,10 @@ from app.runtime.events import eventmanager, Event
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo, MetaInfoPath
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.configuration import (
|
||||
get_chain_runtime_config_snapshot,
|
||||
get_configured_system_config,
|
||||
)
|
||||
from app.application.audio import AudioMetadataHelper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.workflow import FileItem
|
||||
@@ -314,7 +316,8 @@ class ScrapingChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
try:
|
||||
logger.info(f"正在下载图片:{url} ...")
|
||||
request_utils = RequestUtils(
|
||||
proxies=settings.PROXY, ua=settings.NORMAL_USER_AGENT
|
||||
proxies=self.runtime_config.proxy,
|
||||
ua=self.runtime_config.normal_user_agent,
|
||||
)
|
||||
with request_utils.get_stream(url=url) as r:
|
||||
if r and r.status_code == 200:
|
||||
@@ -932,7 +935,7 @@ class ScrapingChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
or isinstance(meta, MetaMusic)
|
||||
or (
|
||||
fileitem.type == "file"
|
||||
and filepath.suffix.lower() in settings.RMT_AUDIOEXT
|
||||
and filepath.suffix.lower() in self.runtime_config.audio_extensions
|
||||
)
|
||||
)
|
||||
if is_music:
|
||||
@@ -953,7 +956,8 @@ class ScrapingChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
**music_kwargs,
|
||||
)
|
||||
if fileitem.type == "file" and (
|
||||
not filepath.suffix or filepath.suffix.lower() not in settings.RMT_MEDIAEXT
|
||||
not filepath.suffix
|
||||
or filepath.suffix.lower() not in self.runtime_config.video_extensions
|
||||
):
|
||||
return False, "刮削路径不是支持的媒体文件"
|
||||
|
||||
@@ -1130,12 +1134,16 @@ class ScrapingChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@cached(maxsize=64, ttl=settings.CONF.meta, skip_none=True)
|
||||
@cached(
|
||||
maxsize=64,
|
||||
ttl_provider=lambda: get_chain_runtime_config_snapshot().metadata_cache_ttl,
|
||||
skip_none=True,
|
||||
)
|
||||
def _request_music_cover(url: str) -> Optional[tuple[Optional[bytes], str]]:
|
||||
"""下载并缓存音乐封面;仅稳定 404 与成功响应进入有界缓存。"""
|
||||
response = RequestUtils(
|
||||
proxies=settings.PROXY,
|
||||
ua=settings.NORMAL_USER_AGENT,
|
||||
proxies=get_chain_runtime_config_snapshot().proxy,
|
||||
ua=get_chain_runtime_config_snapshot().normal_user_agent,
|
||||
timeout=20,
|
||||
).get_res(url)
|
||||
if response is None:
|
||||
@@ -1161,7 +1169,7 @@ class ScrapingChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
@staticmethod
|
||||
def _is_music_audio_file(path: str) -> bool:
|
||||
"""判断路径是否指向系统支持的音频文件。"""
|
||||
return Path(path).suffix.lower() in settings.RMT_AUDIOEXT
|
||||
return Path(path).suffix.lower() in get_chain_runtime_config_snapshot().audio_extensions
|
||||
|
||||
def _music_audio_fileitems(self, fileitem: _SchemaFileItem) -> list[_SchemaFileItem]:
|
||||
"""展开待刮削目录并过滤系统支持的音频文件。"""
|
||||
@@ -1803,7 +1811,7 @@ class ScrapingChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
for file in files:
|
||||
if (
|
||||
file.type == "dir"
|
||||
and file.name not in settings.RENAME_FORMAT_S0_NAMES
|
||||
and file.name not in self.runtime_config.season_zero_names
|
||||
and MetaInfo(file.name).begin_season is None
|
||||
):
|
||||
# 电视剧不处理非季子目录
|
||||
@@ -1843,7 +1851,7 @@ class ScrapingChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
season_meta = MetaInfo(filepath.name)
|
||||
|
||||
# 特殊季目录处理(Specials/SPs)
|
||||
if filepath.name in settings.RENAME_FORMAT_S0_NAMES:
|
||||
if filepath.name in self.runtime_config.season_zero_names:
|
||||
season_meta.begin_season = 0
|
||||
elif season_meta.name and season_meta.begin_season is not None:
|
||||
# 排除辅助词重新识别,避免误判根目录 (issue https://github.com/jxxghp/MoviePilot/issues/5501)
|
||||
|
||||
+43
-22
@@ -14,14 +14,17 @@ from fastapi.concurrency import run_in_threadpool
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.runtime.config import global_vars, settings
|
||||
from app.runtime.config import global_vars
|
||||
from app.domain.context import Context
|
||||
from app.domain.context import MediaInfo, SubtitleInfo, TorrentInfo
|
||||
from app.runtime.events import eventmanager, Event
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.domain.context import MusicInfo
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.configuration import (
|
||||
get_chain_runtime_config_snapshot,
|
||||
get_configured_system_config,
|
||||
)
|
||||
from app.runtime.progress import AsyncProgressHelper, ProgressHelper
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
|
||||
from app.application.search.state import (
|
||||
@@ -155,7 +158,7 @@ class SearchChain(ChainBase):
|
||||
|
||||
settings 可能被环境变量写成字符串,这里统一兜底为 1,避免异常配置导致搜索中断。
|
||||
"""
|
||||
pages = settings.SEARCH_RESOURCE_PAGES
|
||||
pages = get_chain_runtime_config_snapshot().search_resource_pages
|
||||
try:
|
||||
pages = int(pages)
|
||||
except (TypeError, ValueError):
|
||||
@@ -199,7 +202,10 @@ class SearchChain(ChainBase):
|
||||
"""
|
||||
检查AI推荐功能是否已启用。
|
||||
"""
|
||||
return settings.AI_AGENT_ENABLE and settings.AI_RECOMMEND_ENABLED
|
||||
return (
|
||||
self.runtime_config.ai_agent_enable
|
||||
and self.runtime_config.ai_recommend_enabled
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _calculate_recommend_request_hash(
|
||||
@@ -425,7 +431,7 @@ class SearchChain(ChainBase):
|
||||
"""
|
||||
items: List[str] = []
|
||||
valid_indices: List[int] = []
|
||||
max_items = settings.AI_RECOMMEND_MAX_ITEMS or 50
|
||||
max_items = get_chain_runtime_config_snapshot().ai_recommend_max_items or 50
|
||||
|
||||
if filtered_indices:
|
||||
results_to_process = [
|
||||
@@ -555,7 +561,7 @@ class SearchChain(ChainBase):
|
||||
return
|
||||
|
||||
user_preference = (
|
||||
settings.AI_RECOMMEND_USER_PREFERENCE
|
||||
self.runtime_config.ai_recommend_user_preference
|
||||
or "Prefer high-quality resources with more seeders"
|
||||
)
|
||||
search_results_text = (
|
||||
@@ -1211,8 +1217,9 @@ class SearchChain(ChainBase):
|
||||
mediainfo.tw_title,
|
||||
mediainfo.sg_title] if k]))
|
||||
# 限制搜索关键词数量
|
||||
if settings.MAX_SEARCH_NAME_LIMIT:
|
||||
keywords = keywords[:settings.MAX_SEARCH_NAME_LIMIT]
|
||||
max_names = get_chain_runtime_config_snapshot().max_search_name_limit
|
||||
if max_names:
|
||||
keywords = keywords[:max_names]
|
||||
|
||||
return season_episodes, keywords
|
||||
|
||||
@@ -1274,7 +1281,10 @@ class SearchChain(ChainBase):
|
||||
|
||||
finished_count = 0
|
||||
filtered_by_site: Dict[Tuple[Optional[int], Optional[str]], List[TorrentInfo]] = {}
|
||||
max_workers = min(len(site_torrents), settings.CONF.threadpool or len(site_torrents))
|
||||
max_workers = min(
|
||||
len(site_torrents),
|
||||
self.runtime_config.search_threadpool_size or len(site_torrents),
|
||||
)
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
all_tasks = {
|
||||
executor.submit(__do_site_filter, site_torrent_list): site_key
|
||||
@@ -1539,7 +1549,7 @@ class SearchChain(ChainBase):
|
||||
mediainfo,
|
||||
)
|
||||
torrents.extend(matched_torrents)
|
||||
if matched_torrents and not settings.SEARCH_MULTIPLE_NAME:
|
||||
if matched_torrents and not self.runtime_config.search_multiple_name:
|
||||
break
|
||||
return self._build_music_contexts(
|
||||
torrents=torrents,
|
||||
@@ -1572,7 +1582,7 @@ class SearchChain(ChainBase):
|
||||
mediainfo,
|
||||
)
|
||||
torrents.extend(matched_torrents)
|
||||
if matched_torrents and not settings.SEARCH_MULTIPLE_NAME:
|
||||
if matched_torrents and not self.runtime_config.search_multiple_name:
|
||||
break
|
||||
return await run_in_threadpool(
|
||||
self._build_music_contexts,
|
||||
@@ -1618,7 +1628,7 @@ class SearchChain(ChainBase):
|
||||
"items": [],
|
||||
"total_items": len(torrents),
|
||||
}
|
||||
if keyword_matched and not settings.SEARCH_MULTIPLE_NAME:
|
||||
if keyword_matched and not self.runtime_config.search_multiple_name:
|
||||
break
|
||||
|
||||
contexts = await run_in_threadpool(
|
||||
@@ -1726,7 +1736,7 @@ class SearchChain(ChainBase):
|
||||
torrents.extend(results)
|
||||
|
||||
# 有结果则停止
|
||||
if not settings.SEARCH_MULTIPLE_NAME and torrents:
|
||||
if not self.runtime_config.search_multiple_name and torrents:
|
||||
logger.info(f"共搜索到 {len(torrents)} 个资源,停止搜索")
|
||||
break
|
||||
|
||||
@@ -1816,7 +1826,7 @@ class SearchChain(ChainBase):
|
||||
)
|
||||
search_count += 1
|
||||
# 未开启多名称搜索时,有结果则停止
|
||||
if not settings.SEARCH_MULTIPLE_NAME and torrents:
|
||||
if not self.runtime_config.search_multiple_name and torrents:
|
||||
logger.info(f"共搜索到 {len(torrents)} 个资源,停止搜索")
|
||||
break
|
||||
|
||||
@@ -1918,7 +1928,7 @@ class SearchChain(ChainBase):
|
||||
}
|
||||
|
||||
search_count += 1
|
||||
if not settings.SEARCH_MULTIPLE_NAME and torrents:
|
||||
if not self.runtime_config.search_multiple_name and torrents:
|
||||
logger.info(f"共搜索到 {len(torrents)} 个资源,停止搜索")
|
||||
break
|
||||
|
||||
@@ -2156,7 +2166,7 @@ class SearchChain(ChainBase):
|
||||
) or []
|
||||
)
|
||||
search_count += 1
|
||||
if not settings.SEARCH_MULTIPLE_NAME and subtitles:
|
||||
if not self.runtime_config.search_multiple_name and subtitles:
|
||||
logger.info(f"共搜索到 {len(subtitles)} 个字幕,停止搜索")
|
||||
break
|
||||
|
||||
@@ -2246,7 +2256,7 @@ class SearchChain(ChainBase):
|
||||
}
|
||||
|
||||
search_count += 1
|
||||
if not settings.SEARCH_MULTIPLE_NAME and subtitles:
|
||||
if not self.runtime_config.search_multiple_name and subtitles:
|
||||
logger.info(f"共搜索到 {len(subtitles)} 个字幕,停止搜索")
|
||||
break
|
||||
|
||||
@@ -2331,7 +2341,10 @@ class SearchChain(ChainBase):
|
||||
# 结果集
|
||||
results = []
|
||||
# 同一站点按页顺序抓取,避免空页后仍继续请求该站点的后续页。
|
||||
max_workers = min(len(indexer_sites), settings.CONF.threadpool or len(indexer_sites))
|
||||
max_workers = min(
|
||||
len(indexer_sites),
|
||||
self.runtime_config.search_threadpool_size or len(indexer_sites),
|
||||
)
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
pending_tasks = {}
|
||||
|
||||
@@ -2444,7 +2457,9 @@ class SearchChain(ChainBase):
|
||||
text=f"开始搜索,共 {len(indexer_sites)} 个站点,{len(search_pages)} 页 ...")
|
||||
# 结果集
|
||||
results = []
|
||||
semaphore = asyncio.Semaphore(settings.CONF.threadpool or total_num)
|
||||
semaphore = asyncio.Semaphore(
|
||||
self.runtime_config.search_threadpool_size or total_num
|
||||
)
|
||||
|
||||
async def search_site_page(site: dict, search_page: int) -> List[TorrentInfo]:
|
||||
"""
|
||||
@@ -2579,7 +2594,9 @@ class SearchChain(ChainBase):
|
||||
"total": total_num
|
||||
}
|
||||
|
||||
semaphore = asyncio.Semaphore(settings.CONF.threadpool or total_num)
|
||||
semaphore = asyncio.Semaphore(
|
||||
self.runtime_config.search_threadpool_size or total_num
|
||||
)
|
||||
|
||||
async def search_site(site: dict, search_page: int) -> List[TorrentInfo]:
|
||||
"""
|
||||
@@ -2701,7 +2718,9 @@ class SearchChain(ChainBase):
|
||||
await progress.update(value=0,
|
||||
text=f"开始搜索字幕,共 {len(indexer_sites)} 个站点,{len(search_pages)} 页 ...")
|
||||
results = []
|
||||
semaphore = asyncio.Semaphore(settings.CONF.threadpool or total_num)
|
||||
semaphore = asyncio.Semaphore(
|
||||
self.runtime_config.search_threadpool_size or total_num
|
||||
)
|
||||
|
||||
async def search_site_page(site: dict, search_page: int) -> List[SubtitleInfo]:
|
||||
"""
|
||||
@@ -2816,7 +2835,9 @@ class SearchChain(ChainBase):
|
||||
"total": total_num
|
||||
}
|
||||
|
||||
semaphore = asyncio.Semaphore(settings.CONF.threadpool or total_num)
|
||||
semaphore = asyncio.Semaphore(
|
||||
self.runtime_config.search_threadpool_size or total_num
|
||||
)
|
||||
|
||||
async def search_site(site: dict, search_page: int) -> List[SubtitleInfo]:
|
||||
"""
|
||||
|
||||
+17
-14
@@ -23,7 +23,7 @@ from app.chain.mediaserver import MediaServerChain
|
||||
from app.chain.search import SearchChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.chain.torrents import TorrentsChain
|
||||
from app.runtime.config import settings, global_vars
|
||||
from app.runtime.config import global_vars
|
||||
from app.domain.context import (
|
||||
Context,
|
||||
MediaInfo,
|
||||
@@ -39,7 +39,10 @@ from app.application.chain.data import (
|
||||
SitePortProxy as SiteOper,
|
||||
SubscribePortProxy as SubscribeOper,
|
||||
)
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.configuration import (
|
||||
get_chain_runtime_config_snapshot,
|
||||
get_configured_system_config,
|
||||
)
|
||||
from app.application.messaging.subscribe import SubscribeInteractionHandler
|
||||
from app.application.mediaserver import MediaServerHelper
|
||||
from app.application.subscription.write import add_subscribe, async_add_subscribe
|
||||
@@ -874,10 +877,10 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
def __subscribe_added_link(mtype: MediaType) -> str:
|
||||
"""返回订阅类型对应的前端详情入口。"""
|
||||
if mtype == MediaType.TV:
|
||||
return settings.MP_DOMAIN('#/subscribe/tv?tab=mysub')
|
||||
return get_chain_runtime_config_snapshot().television_subscribe_url
|
||||
if mtype == MediaType.MUSIC:
|
||||
return settings.MP_DOMAIN('#/subscribe/music?tab=mysub')
|
||||
return settings.MP_DOMAIN('#/subscribe/movie?tab=mysub')
|
||||
return get_chain_runtime_config_snapshot().music_subscribe_url
|
||||
return get_chain_runtime_config_snapshot().movie_subscribe_url
|
||||
|
||||
@staticmethod
|
||||
def __subscribe_report_payload(context: _SubscribePostCommitContext) -> dict:
|
||||
@@ -2938,11 +2941,11 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
subscribeoper.delete(subscribe.id)
|
||||
# 发送通知
|
||||
if mediainfo.type == MediaType.TV:
|
||||
link = settings.MP_DOMAIN('#/subscribe/tv?tab=mysub')
|
||||
link = self.runtime_config.television_subscribe_url
|
||||
elif mediainfo.type == MediaType.MUSIC:
|
||||
link = settings.MP_DOMAIN('#/subscribe/music?tab=mysub')
|
||||
link = self.runtime_config.music_subscribe_url
|
||||
else:
|
||||
link = settings.MP_DOMAIN('#/subscribe/movie?tab=mysub')
|
||||
link = self.runtime_config.movie_subscribe_url
|
||||
# 完成订阅按规则发送消息
|
||||
self.post_message(
|
||||
_SchemaMessage(
|
||||
@@ -3195,11 +3198,8 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
if not default_subscribe_key:
|
||||
return None
|
||||
|
||||
# 默认订阅规则
|
||||
if hasattr(settings, default_subscribe_key):
|
||||
value = getattr(settings, default_subscribe_key)
|
||||
else:
|
||||
value = _system_config().get(default_subscribe_key)
|
||||
# 默认订阅规则属于持久化用户配置,不再从部署 Settings 猜测同名属性。
|
||||
value = _system_config().get(default_subscribe_key)
|
||||
|
||||
if not value:
|
||||
return None
|
||||
@@ -3259,7 +3259,10 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
info = _SchemaSubscribeEpisodeInfo()
|
||||
info.title = episode.name
|
||||
info.description = episode.overview
|
||||
info.backdrop = settings.TMDB_IMAGE_URL(episode.still_path, "w500")
|
||||
info.backdrop = self.runtime_config.tmdb_image_url(
|
||||
episode.still_path,
|
||||
"w500",
|
||||
)
|
||||
episodes[episode.episode_number] = info
|
||||
elif subscribe.type == MediaType.TV.value:
|
||||
# 根据开始结束集计算集信息
|
||||
|
||||
+14
-11
@@ -7,7 +7,7 @@ from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.runtime.config import settings
|
||||
from app.application.configuration import get_chain_runtime_config_snapshot
|
||||
from app.runtime.state import SystemHelper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.message import Message
|
||||
@@ -70,8 +70,9 @@ class SystemChain(ChainBase):
|
||||
|
||||
try:
|
||||
# 使用绝对路径确保准确性
|
||||
plugins_dir = settings.ROOT_PATH / "app" / "plugins"
|
||||
backup_dir = settings.CONFIG_PATH / "plugins_backup"
|
||||
config = get_chain_runtime_config_snapshot()
|
||||
plugins_dir = config.root_path / "app" / "plugins"
|
||||
backup_dir = config.config_path / "plugins_backup"
|
||||
|
||||
if not plugins_dir.exists():
|
||||
logger.info("插件目录不存在,跳过备份")
|
||||
@@ -134,8 +135,9 @@ class SystemChain(ChainBase):
|
||||
return
|
||||
|
||||
# 使用绝对路径确保准确性
|
||||
plugins_dir = settings.ROOT_PATH / "app" / "plugins"
|
||||
backup_dir = settings.CONFIG_PATH / "plugins_backup"
|
||||
config = get_chain_runtime_config_snapshot()
|
||||
plugins_dir = config.root_path / "app" / "plugins"
|
||||
backup_dir = config.config_path / "plugins_backup"
|
||||
|
||||
if not backup_dir.exists():
|
||||
logger.info("插件备份目录不存在,跳过恢复")
|
||||
@@ -367,8 +369,8 @@ class SystemChain(ChainBase):
|
||||
try:
|
||||
# 获取所有发布的版本列表
|
||||
response = RequestUtils(
|
||||
proxies=settings.PROXY,
|
||||
headers=settings.GITHUB_HEADERS
|
||||
proxies=get_chain_runtime_config_snapshot().proxy,
|
||||
headers=get_chain_runtime_config_snapshot().github_headers,
|
||||
).get_res("https://api.github.com/repos/jxxghp/MoviePilot/releases")
|
||||
if response:
|
||||
releases = [release['tag_name'] for release in response.json()]
|
||||
@@ -394,8 +396,8 @@ class SystemChain(ChainBase):
|
||||
try:
|
||||
# 获取所有发布的版本列表
|
||||
response = RequestUtils(
|
||||
proxies=settings.PROXY,
|
||||
headers=settings.GITHUB_HEADERS
|
||||
proxies=get_chain_runtime_config_snapshot().proxy,
|
||||
headers=get_chain_runtime_config_snapshot().github_headers,
|
||||
).get_res("https://api.github.com/repos/jxxghp/MoviePilot-Frontend/releases")
|
||||
if response:
|
||||
releases = [release['tag_name'] for release in response.json()]
|
||||
@@ -426,9 +428,10 @@ class SystemChain(ChainBase):
|
||||
获取前端版本
|
||||
"""
|
||||
if SystemUtils.is_frozen() and SystemUtils.is_windows():
|
||||
version_file = settings.CONFIG_PATH.parent / "nginx" / "html" / "version.txt"
|
||||
config = get_chain_runtime_config_snapshot()
|
||||
version_file = config.config_path.parent / "nginx" / "html" / "version.txt"
|
||||
else:
|
||||
version_file = Path(settings.FRONTEND_PATH) / "version.txt"
|
||||
version_file = get_chain_runtime_config_snapshot().frontend_path / "version.txt"
|
||||
if version_file.exists():
|
||||
try:
|
||||
with open(version_file, 'r', encoding='utf-8', errors='replace') as f:
|
||||
|
||||
+17
-17
@@ -13,7 +13,7 @@ from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.runtime.config import settings, global_vars
|
||||
from app.runtime.config import global_vars
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.runtime.events import eventmanager
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
@@ -112,11 +112,11 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
"""初始化文件整理处理链。"""
|
||||
super().__init__()
|
||||
# 主要媒体文件后缀
|
||||
self._media_exts = settings.RMT_MEDIAEXT
|
||||
self._media_exts = self.runtime_config.video_extensions
|
||||
# 字幕文件后缀
|
||||
self._subtitle_exts = settings.RMT_SUBEXT
|
||||
self._subtitle_exts = self.runtime_config.subtitle_extensions
|
||||
# 音频文件后缀
|
||||
self._audio_exts = settings.RMT_AUDIOEXT
|
||||
self._audio_exts = self.runtime_config.audio_extensions
|
||||
# 可处理的文件后缀(视频文件、字幕、音频文件)
|
||||
self._allowed_exts = self._media_exts + self._audio_exts + self._subtitle_exts
|
||||
# 待整理任务队列
|
||||
@@ -154,7 +154,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
启动文件整理线程
|
||||
"""
|
||||
self._queue_active = True
|
||||
for i in range(settings.TRANSFER_THREADS):
|
||||
for i in range(self.runtime_config.transfer_threads):
|
||||
logger.info(f"启动文件整理线程 {i + 1} ...")
|
||||
thread = threading.Thread(
|
||||
target=self.__start_transfer, name=f"transfer-{i}", daemon=True
|
||||
@@ -341,8 +341,8 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
# AI智能体自动重试整理
|
||||
if (
|
||||
history
|
||||
and settings.AI_AGENT_ENABLE
|
||||
and settings.AI_AGENT_RETRY_TRANSFER
|
||||
and self.runtime_config.ai_agent_enable
|
||||
and self.runtime_config.ai_agent_retry_transfer
|
||||
):
|
||||
try:
|
||||
# 使用 download_hash 或源文件父目录作为分组键,
|
||||
@@ -554,7 +554,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
username=task.username,
|
||||
manual_identity=manual_identity,
|
||||
)
|
||||
if not settings.TRANSFER_FAILURE_NOTIFICATION_AGGREGATION:
|
||||
if not self.runtime_config.transfer_failure_notification_aggregation:
|
||||
self._send_transfer_failure_notifications([notification])
|
||||
return
|
||||
try:
|
||||
@@ -616,7 +616,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
text = "\n".join(text_parts)
|
||||
buttons = [[{
|
||||
"text": "批量处理",
|
||||
"url": settings.MP_DOMAIN("#/history"),
|
||||
"url": self.runtime_config.history_url,
|
||||
}]]
|
||||
title = f"{first.media_title} 入库失败({len(notifications)} 个文件)"
|
||||
self.post_message(
|
||||
@@ -626,7 +626,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
text=text,
|
||||
image=first.image,
|
||||
username=first.username,
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
buttons=buttons,
|
||||
)
|
||||
)
|
||||
@@ -855,7 +855,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
|
||||
def __expire_stale_transfer_tasks(self):
|
||||
"""清理外部接管后失去状态心跳的运行中整理任务。"""
|
||||
timeout_minutes = max(int(settings.TRANSFER_TASK_TIMEOUT), 0)
|
||||
timeout_minutes = max(int(self.runtime_config.transfer_task_timeout), 0)
|
||||
expire_tasks = getattr(self.jobview, "expire_stale_running_tasks", None)
|
||||
expired_tasks = (
|
||||
expire_tasks(timeout_seconds=timeout_minutes * 60)
|
||||
@@ -1110,8 +1110,8 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
# AI智能体自动重试整理
|
||||
if (
|
||||
his
|
||||
and settings.AI_AGENT_ENABLE
|
||||
and settings.AI_AGENT_RETRY_TRANSFER
|
||||
and self.runtime_config.ai_agent_enable
|
||||
and self.runtime_config.ai_agent_retry_transfer
|
||||
):
|
||||
try:
|
||||
# 使用 download_hash 或源文件父目录作为分组键
|
||||
@@ -1136,7 +1136,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
|
||||
# 只有 TMDB 主源沿用历史 TMDB 标题,避免辅助 ID 改写其它识别源标题。
|
||||
if (
|
||||
not settings.SCRAP_FOLLOW_TMDB
|
||||
not self.runtime_config.scrape_follow_tmdb
|
||||
and mediainfo.media_source == MediaSource.TMDB
|
||||
):
|
||||
transfer_history = transferhis.get_by_media_identity(
|
||||
@@ -2533,7 +2533,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
source=source,
|
||||
text=errmsg,
|
||||
userid=userid,
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
save_history=False,
|
||||
)
|
||||
)
|
||||
@@ -2570,7 +2570,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
source=source,
|
||||
text=errmsg,
|
||||
userid=userid,
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
save_history=False,
|
||||
)
|
||||
)
|
||||
@@ -2734,7 +2734,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
ctype=ContentType.OrganizeSuccess,
|
||||
image=mediainfo.get_message_image(),
|
||||
username=username,
|
||||
link=settings.MP_DOMAIN("#/history"),
|
||||
link=self.runtime_config.history_url,
|
||||
),
|
||||
meta=meta,
|
||||
mediainfo=mediainfo,
|
||||
|
||||
Reference in New Issue
Block a user