refactor(config): retire RuntimeSettingsCompat host usage

This commit is contained in:
jxxghp
2026-08-26 15:55:21 +08:00
parent cdab54254d
commit 9dbe424c3d
162 changed files with 1966 additions and 1745 deletions
+11 -12
View File
@@ -10,9 +10,8 @@ from typing import Any, Optional, Tuple, Union
from uuid import UUID
from app.runtime.execution import run_in_threadpool
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.modules import _ModuleBase
from app.schemas.types import ModuleType, OtherModulesType
@@ -71,19 +70,19 @@ class AcoustIdModule(_ModuleBase):
模块初始化早于 fpcalc 安装,或运行期依赖被移除,测试也能如实反映本地
依赖状态,而不是只校验网络连通性。
"""
if not str(settings.ACOUSTID_API_KEY or "").strip():
if not str(get_runtime_setting('ACOUSTID_API_KEY') or "").strip():
return False, "AcoustID API Key 未配置"
fpcalc_path = self._resolve_fpcalc()
if not fpcalc_path:
return False, "未找到 fpcalc,请先安装 Chromaprint"
self._fpcalc_path = fpcalc_path
response = RequestUtils(
ua=settings.USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
timeout=15,
).get_res(
url=self._base_url,
params={"client": settings.ACOUSTID_API_KEY, "format": "json"},
params={"client": get_runtime_setting('ACOUSTID_API_KEY'), "format": "json"},
)
if response is None:
return False, "AcoustID 网络连接失败"
@@ -298,13 +297,13 @@ class AcoustIdModule(_ModuleBase):
fingerprint: str,
) -> Optional[str]:
"""查询 AcoustID 指纹库并提取 MusicBrainz Recording ID。"""
api_key = str(settings.ACOUSTID_API_KEY or "").strip()
api_key = str(get_runtime_setting('ACOUSTID_API_KEY') or "").strip()
if not api_key:
return None
self._wait_for_rate_limit()
response = RequestUtils(
ua=settings.USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
timeout=30,
).post_res(
url=self._base_url,
@@ -337,13 +336,13 @@ class AcoustIdModule(_ModuleBase):
fingerprint: str,
) -> Optional[str]:
"""异步查询 AcoustID 指纹库并提取 MusicBrainz Recording ID。"""
api_key = str(settings.ACOUSTID_API_KEY or "").strip()
api_key = str(get_runtime_setting('ACOUSTID_API_KEY') or "").strip()
if not api_key:
return None
await self._async_wait_for_rate_limit()
response = await AsyncRequestUtils(
ua=settings.USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
timeout=30,
).post_res(
url=self._base_url,
+3 -3
View File
@@ -77,7 +77,7 @@ class AniListModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param media_source: 请求级识别数据源
:return: 是否启用 AniList 识别
"""
return (media_source or get_runtime_setting("RECOGNIZE_SOURCE")) == MediaSource.AniList
return (media_source or get_runtime_setting('RECOGNIZE_SOURCE')) == MediaSource.AniList
@staticmethod
def _media_type(info: dict) -> MediaType:
@@ -570,7 +570,7 @@ class AniListModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param episode: 集号
:return: NFO XML 文本
"""
scrape_source = mediainfo.scrape_source or get_runtime_setting("SCRAP_SOURCE")
scrape_source = mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')
if scrape_source != "anilist":
return None
return self.scraper.get_metadata_nfo(mediainfo, season=season, episode=episode)
@@ -589,7 +589,7 @@ class AniListModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param episode: 集号
:return: 图片文件名与下载地址映射
"""
scrape_source = mediainfo.scrape_source or get_runtime_setting("SCRAP_SOURCE")
scrape_source = mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')
if scrape_source != "anilist":
return None
return self.scraper.get_metadata_img(mediainfo, season=season, episode=episode)
+32 -33
View File
@@ -2,9 +2,8 @@ from datetime import date
from typing import Optional
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.adapters.network.http import AsyncRequestUtils, RequestUtils
@@ -103,16 +102,16 @@ class AniListApi:
def __init__(self) -> None:
"""初始化同步与异步请求客户端"""
headers = {
"User-Agent": settings.NORMAL_USER_AGENT,
"User-Agent": get_runtime_setting('NORMAL_USER_AGENT'),
"Accept": "application/json",
"Content-Type": "application/json",
}
self._request = RequestUtils(
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
headers=headers,
)
self._async_request = AsyncRequestUtils(
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
headers=headers,
)
self._proxy_available = True
@@ -363,8 +362,8 @@ class AniListApi:
return seasons[(current.month - 1) // 3], current.year
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="detail",
)
@@ -380,8 +379,8 @@ class AniListApi:
return result.get("Media") if result else None
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="detail",
)
@@ -397,8 +396,8 @@ class AniListApi:
return result.get("Media") if result else None
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="search",
)
@@ -421,8 +420,8 @@ class AniListApi:
return self._page_medias(result)
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="search",
)
@@ -445,8 +444,8 @@ class AniListApi:
return self._page_medias(result)
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="discover",
)
@@ -483,8 +482,8 @@ class AniListApi:
return self._page_medias(self._invoke(self._page_query, variables))
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="discover",
)
@@ -576,8 +575,8 @@ class AniListApi:
)
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="credits",
)
@@ -606,8 +605,8 @@ class AniListApi:
return result.get("Media", {}).get("characters", {}).get("edges") or [] if result else []
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="credits",
)
@@ -636,8 +635,8 @@ class AniListApi:
return result.get("Media", {}).get("characters", {}).get("edges") or [] if result else []
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="recommendations",
)
@@ -662,8 +661,8 @@ class AniListApi:
return self._medias_by_ids(media_ids)
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="recommendations",
)
@@ -688,8 +687,8 @@ class AniListApi:
return await self._async_medias_by_ids(media_ids)
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="person_detail",
)
@@ -713,8 +712,8 @@ class AniListApi:
return result.get("Staff") if result else None
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="person_detail",
)
@@ -738,8 +737,8 @@ class AniListApi:
return result.get("Staff") if result else None
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="person_credits",
)
@@ -763,8 +762,8 @@ class AniListApi:
return self._medias_by_ids([node.get("id") for node in nodes])
@cached(
maxsize=settings.CONF.anilist,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').anilist,
ttl=get_runtime_setting('CONF').meta,
skip_empty=True,
shared_key="person_credits",
)
+5 -5
View File
@@ -44,7 +44,7 @@ class BangumiModule(MediaAuxiliaryProviderMixin, _ModuleBase):
"""
初始化Bangumi客户端
"""
self._config = BangumiConfigSnapshot(proxy=get_runtime_setting("PROXY"))
self._config = BangumiConfigSnapshot(proxy=get_runtime_setting('PROXY'))
self.bangumiapi = BangumiApi()
self.scraper = MediaScraperHelper()
@@ -126,7 +126,7 @@ class BangumiModule(MediaAuxiliaryProviderMixin, _ModuleBase):
return None
bangumiid = int(media_id) if media_id is not None else None
if not bangumiid and (
not meta or (media_source or get_runtime_setting("RECOGNIZE_SOURCE")) != MediaSource.Bangumi
not meta or (media_source or get_runtime_setting('RECOGNIZE_SOURCE')) != MediaSource.Bangumi
):
return None
@@ -175,7 +175,7 @@ class BangumiModule(MediaAuxiliaryProviderMixin, _ModuleBase):
return None
bangumiid = int(media_id) if media_id is not None else None
if not bangumiid and (
not meta or (media_source or get_runtime_setting("RECOGNIZE_SOURCE")) != MediaSource.Bangumi
not meta or (media_source or get_runtime_setting('RECOGNIZE_SOURCE')) != MediaSource.Bangumi
):
return None
@@ -316,7 +316,7 @@ class BangumiModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param episode: 集号
:return: NFO XML文本
"""
scrape_source = mediainfo.scrape_source or get_runtime_setting("SCRAP_SOURCE")
scrape_source = mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')
if scrape_source != "bangumi":
return None
return self.scraper.get_metadata_nfo(mediainfo, season=season, episode=episode)
@@ -335,7 +335,7 @@ class BangumiModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param episode: 集号
:return: 图片文件名与下载地址映射
"""
scrape_source = mediainfo.scrape_source or get_runtime_setting("SCRAP_SOURCE")
scrape_source = mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')
if scrape_source != "bangumi":
return None
return self.scraper.get_metadata_img(mediainfo, season=season, episode=episode)
+7 -8
View File
@@ -4,9 +4,8 @@ from typing import Optional
import requests
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.adapters.network.http import RequestUtils, AsyncRequestUtils
@@ -33,16 +32,16 @@ class BangumiApi(object):
def __init__(self):
self._session = requests.Session()
self._req = RequestUtils(
ua=settings.NORMAL_USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('NORMAL_USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
session=self._session,
)
self._async_req = AsyncRequestUtils(
ua=settings.NORMAL_USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('NORMAL_USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
)
@cached(maxsize=settings.CONF.bangumi, ttl=settings.CONF.meta, shared_key="get")
@cached(maxsize=get_runtime_setting('CONF').bangumi, ttl=get_runtime_setting('CONF').meta, shared_key="get")
def __invoke(self, url, key: Optional[str] = None, **kwargs):
req_url = self._base_url + url
params = {}
@@ -58,7 +57,7 @@ class BangumiApi(object):
print(e)
return None
@cached(maxsize=settings.CONF.bangumi, ttl=settings.CONF.meta, shared_key="get")
@cached(maxsize=get_runtime_setting('CONF').bangumi, ttl=get_runtime_setting('CONF').meta, shared_key="get")
async def __async_invoke(self, url, key: Optional[str] = None, **kwargs):
req_url = self._base_url + url
params = {}
+2 -3
View File
@@ -8,9 +8,8 @@ import discord
from discord import app_commands
from app.runtime.execution import run_in_threadpool
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.messaging.ingress import async_forward_message_to_host
from app.domain.context import MediaInfo, Context
from app.domain.metainfo import MetaInfo
@@ -72,7 +71,7 @@ class Discord:
intents.guilds = True
self._client: Optional[discord.Client] = discord.Client(
intents=intents, proxy=settings.PROXY_HOST
intents=intents, proxy=get_runtime_setting('PROXY_HOST')
)
self._tree: Optional[app_commands.CommandTree] = app_commands.CommandTree(self._client)
self._loop: asyncio.AbstractEventLoop = asyncio.new_event_loop()
+5 -5
View File
@@ -706,7 +706,7 @@ class DoubanModule(MediaAuxiliaryProviderMixin, _ModuleBase):
if (
meta
and not doubanid
and (kwargs.get("media_source") or get_runtime_setting("RECOGNIZE_SOURCE")) != "douban"
and (kwargs.get("media_source") or get_runtime_setting('RECOGNIZE_SOURCE')) != "douban"
):
return None
@@ -777,7 +777,7 @@ class DoubanModule(MediaAuxiliaryProviderMixin, _ModuleBase):
if (
meta
and not doubanid
and (kwargs.get("media_source") or get_runtime_setting("RECOGNIZE_SOURCE")) != "douban"
and (kwargs.get("media_source") or get_runtime_setting('RECOGNIZE_SOURCE')) != "douban"
):
return None
@@ -1684,7 +1684,7 @@ class DoubanModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param mediainfo: 媒体信息
:param season: 季号
"""
if (mediainfo.scrape_source or get_runtime_setting("SCRAP_SOURCE")) != "douban":
if (mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')) != "douban":
return None
return self.scraper.get_metadata_nfo(mediainfo=mediainfo, season=season)
@@ -1695,7 +1695,7 @@ class DoubanModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param season: 季号
:param episode: 集号
"""
if (mediainfo.scrape_source or get_runtime_setting("SCRAP_SOURCE")) != "douban":
if (mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')) != "douban":
return None
return self.scraper.get_metadata_img(mediainfo=mediainfo, season=season, episode=episode)
@@ -1706,7 +1706,7 @@ class DoubanModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param mediainfo: 媒体信息
:return: None 表示不处理,MediaInfo 表示继续处理
"""
if mediainfo.media_source != MediaSource.Douban and get_runtime_setting("RECOGNIZE_SOURCE") != "douban":
if mediainfo.media_source != MediaSource.Douban and get_runtime_setting('RECOGNIZE_SOURCE') != "douban":
return None
if not mediainfo.douban_id:
return None
+13 -14
View File
@@ -13,9 +13,8 @@ import requests
from bs4 import BeautifulSoup
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.adapters.network.http import RequestUtils, AsyncRequestUtils
from app.foundation.singleton import WeakSingleton
@@ -233,7 +232,7 @@ class DoubanApi(metaclass=WeakSingleton):
"""
return resp.json() if resp is not None else None
@cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta, skip_none=True, shared_key="get")
@cached(maxsize=get_runtime_setting('CONF').douban, ttl=get_runtime_setting('CONF').meta, skip_none=True, shared_key="get")
def __invoke(self, url: str, **kwargs) -> dict:
"""
GET请求
@@ -245,7 +244,7 @@ class DoubanApi(metaclass=WeakSingleton):
).get_res(url=req_url, params=params)
return self._handle_response(resp)
@cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta, skip_none=True, shared_key="get")
@cached(maxsize=get_runtime_setting('CONF').douban, ttl=get_runtime_setting('CONF').meta, skip_none=True, shared_key="get")
async def __async_invoke(self, url: str, **kwargs) -> dict:
"""
GET请求(异步版本)
@@ -268,7 +267,7 @@ class DoubanApi(metaclass=WeakSingleton):
params.pop('_ts')
return req_url, params
@cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta, skip_none=True, shared_key="post")
@cached(maxsize=get_runtime_setting('CONF').douban, ttl=get_runtime_setting('CONF').meta, skip_none=True, shared_key="post")
def __post(self, url: str, **kwargs) -> dict:
"""
POST请求
@@ -285,19 +284,19 @@ class DoubanApi(metaclass=WeakSingleton):
"""
req_url, params = self._prepare_post_request(url, **kwargs)
resp = RequestUtils(
ua=settings.NORMAL_USER_AGENT,
ua=get_runtime_setting('NORMAL_USER_AGENT'),
session=self._session,
).post_res(url=req_url, data=params)
return self._handle_response(resp)
@cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta, skip_none=True, shared_key="post")
@cached(maxsize=get_runtime_setting('CONF').douban, ttl=get_runtime_setting('CONF').meta, skip_none=True, shared_key="post")
async def __async_post(self, url: str, **kwargs) -> dict:
"""
POST请求(异步版本)
"""
req_url, params = self._prepare_post_request(url, **kwargs)
resp = await AsyncRequestUtils(
ua=settings.NORMAL_USER_AGENT
ua=get_runtime_setting('NORMAL_USER_AGENT')
).post_res(url=req_url, data=params)
return self._handle_response(resp)
@@ -644,7 +643,7 @@ class DoubanApi(metaclass=WeakSingleton):
self._urls["music_single"], start=start, count=count
)
@cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta, skip_none=True)
@cached(maxsize=get_runtime_setting('CONF').douban, ttl=get_runtime_setting('CONF').meta, skip_none=True)
def music_tag(
self,
tag: str,
@@ -665,8 +664,8 @@ class DoubanApi(metaclass=WeakSingleton):
while len(items) < required:
url = f"{self._music_web_url}/tag/{parse.quote(normalized_tag, safe='')}"
response = RequestUtils(
ua=settings.NORMAL_USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('NORMAL_USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
timeout=20,
accept_type="text/html,application/xhtml+xml",
).get_res(url=url, params={"start": page * page_size, "type": sort})
@@ -679,12 +678,12 @@ class DoubanApi(metaclass=WeakSingleton):
page += 1
return {"items": items[first_offset:first_offset + max(count, 1)]}
@cached(maxsize=settings.CONF.douban, ttl=settings.CONF.meta, skip_none=True)
@cached(maxsize=get_runtime_setting('CONF').douban, ttl=get_runtime_setting('CONF').meta, skip_none=True)
def music_chart(self) -> dict:
"""从豆瓣音乐官方榜单页读取新碟榜,并补充专辑详情供卡片展示。"""
response = RequestUtils(
ua=settings.NORMAL_USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('NORMAL_USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
timeout=20,
accept_type="text/html,application/xhtml+xml",
).get_res(url=f"{self._music_web_url}/chart")
+2 -3
View File
@@ -12,9 +12,8 @@ from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibr
from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem
from app.schemas.mediaserver import RefreshMediaItem as _SchemaRefreshMediaItem
from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.mediaserver import MediaServerIdentityHelper, format_emby_family_item
from app.runtime.log import logger
from app.schemas.mediaserver import MediaServerItem
@@ -44,7 +43,7 @@ class Emby:
self._playhost = UrlUtils.standardize_base_url(self._playhost)
self._apikey = apikey
self._username = username
self.user = self.get_user(username or settings.SUPERUSER)
self.user = self.get_user(username or get_runtime_setting('SUPERUSER'))
self.folders = self.get_emby_folders()
self.serverid = self.get_server_id()
self._sync_libraries = sync_libraries or []
+8 -9
View File
@@ -4,9 +4,8 @@ from typing import Optional, Tuple, Union
from app.runtime.cache import cached
from app.domain.context import MediaInfo
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.runtime.tasks import get_task_registry
from app.modules import _ModuleBase
@@ -309,14 +308,14 @@ class FanartModule(_ModuleBase):
"""
# 代理
_proxies: dict = settings.PROXY
_proxies: dict = get_runtime_setting('PROXY')
# Fanart Api
_movie_url: str = (
f"https://webservice.fanart.tv/v3/movies/%s?api_key={settings.FANART_API_KEY}"
f"https://webservice.fanart.tv/v3/movies/%s?api_key={get_runtime_setting('FANART_API_KEY')}"
)
_tv_url: str = (
f"https://webservice.fanart.tv/v3/tv/%s?api_key={settings.FANART_API_KEY}"
f"https://webservice.fanart.tv/v3/tv/%s?api_key={get_runtime_setting('FANART_API_KEY')}"
)
def init_module(self) -> None:
@@ -451,7 +450,7 @@ class FanartModule(_ModuleBase):
"""
获取 Fanart 查询参数
"""
if not settings.FANART_ENABLE:
if not get_runtime_setting('FANART_ENABLE'):
return None
if not mediainfo.tmdb_id and not mediainfo.tvdb_id:
return None
@@ -532,7 +531,7 @@ class FanartModule(_ModuleBase):
"""
其他图片优先环境变量指定语言再like最多
"""
lang_env = settings.FANART_LANG
lang_env = get_runtime_setting('FANART_LANG')
if lang_env:
langs = [lang.strip() for lang in lang_env.split(",") if lang.strip()]
for lang in langs:
@@ -582,7 +581,7 @@ class FanartModule(_ModuleBase):
return cls._FANART_NAME_MAP.get(fanart_name.lower(), fanart_name)
@classmethod
@cached(maxsize=settings.CONF.fanart, ttl=settings.CONF.meta, shared_key="get")
@cached(maxsize=get_runtime_setting('CONF').fanart, ttl=get_runtime_setting('CONF').meta, shared_key="get")
def __request_fanart(
cls, media_type: MediaType, queryid: Union[str, int]
) -> Optional[dict]:
@@ -601,7 +600,7 @@ class FanartModule(_ModuleBase):
return None
@classmethod
@cached(maxsize=settings.CONF.fanart, ttl=settings.CONF.meta, shared_key="get")
@cached(maxsize=get_runtime_setting('CONF').fanart, ttl=get_runtime_setting('CONF').meta, shared_key="get")
async def __async_request_fanart(
cls, media_type: MediaType, queryid: Union[str, int]
) -> Optional[dict]:
+2 -3
View File
@@ -50,9 +50,8 @@ from lark_oapi.event.callback.model.p2_card_action_trigger import (
P2CardActionTriggerResponse,
)
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.messaging.ingress import submit_message_to_host
from app.domain.context import Context, MediaInfo
from app.application.security.user import get_configured_user_channel_lookup
@@ -1065,7 +1064,7 @@ class Feishu:
response = None
temp_path = None
try:
response = RequestUtils(timeout=30, ua=settings.USER_AGENT).get_res(image_url)
response = RequestUtils(timeout=30, ua=get_runtime_setting('USER_AGENT')).get_res(image_url)
if not response or not getattr(response, "content", None):
logger.warning(f"飞书图片下载失败:{image_url}")
return None
+6 -7
View File
@@ -1,9 +1,8 @@
from pathlib import Path
from typing import Any, Optional, List, Tuple, Union, Dict, Callable
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import MediaInfo, MusicInfo
from app.domain.meta.metabase import MetaBase
from app.domain.meta.metamusic import MetaMusic
@@ -191,7 +190,7 @@ class FileManagerModule(_ModuleBase):
"""
handler = TransHandler()
# 重命名格式
rename_format = settings.RENAME_FORMAT(mediainfo.type)
rename_format = get_runtime_setting('RENAME_FORMAT')(mediainfo.type)
# 获取重命名后的名称
path = handler.get_rename_path(
template_string=rename_format,
@@ -631,7 +630,7 @@ class FileManagerModule(_ModuleBase):
# 媒体分类路径
dir_path = handler.get_dest_dir(mediainfo=mediainfo, target_dir=dest_dir)
# 重命名格式
rename_format = settings.RENAME_FORMAT(mediainfo.type)
rename_format = get_runtime_setting('RENAME_FORMAT')(mediainfo.type)
# 元数据补上常用属性,尽可能确保重命名后的路径不出现空白
meta = self._build_library_lookup_meta(mediainfo)
# 获取路径(重命名路径)
@@ -665,9 +664,9 @@ class FileManagerModule(_ModuleBase):
continue
if media_files:
media_extensions = (
settings.RMT_AUDIOEXT
get_runtime_setting('RMT_AUDIOEXT')
if mediainfo.type == MediaType.MUSIC
else settings.RMT_MEDIAEXT
else get_runtime_setting('RMT_MEDIAEXT')
)
for media_file in media_files:
if (
@@ -692,7 +691,7 @@ class FileManagerModule(_ModuleBase):
if kwargs.get("server"):
return None
if not settings.LOCAL_EXISTS_SEARCH:
if not get_runtime_setting('LOCAL_EXISTS_SEARCH'):
return None
logger.debug(f"正在本地媒体库中查找 {mediainfo.title_year}...")
+10 -12
View File
@@ -8,19 +8,17 @@ from typing import List, Optional, Tuple, Union
import requests
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
settings = RuntimeSettingsCompat()
from app.adapters.network.http import RequestUtils
from app.foundation import temporal as time_tools
from app.foundation.singleton import WeakSingleton
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
from app.runtime.stop import runtime_stop_state
from app.schemas.exception import StorageQueryError
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.types import StorageSchema
from app.schemas.workflow import FileItem as _SchemaFileItem
lock = threading.Lock()
@@ -48,7 +46,7 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
base_url = "https://openapi.alipan.com"
# 阿里云盘目录时间不随子文件变更而更新,默认关闭目录修改时间检查
snapshot_check_folder_modtime = settings.ALIPAN_SNAPSHOT_CHECK_FOLDER_MODTIME
snapshot_check_folder_modtime = get_runtime_setting('ALIPAN_SNAPSHOT_CHECK_FOLDER_MODTIME')
# 文件块大小,默认10MB
chunk_size = 10 * 1024 * 1024
@@ -117,7 +115,7 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
resp = self.session.post(
f"{self.base_url}/oauth/authorize/qrcode",
json={
"client_id": settings.ALIPAN_APP_ID,
"client_id": get_runtime_setting('ALIPAN_APP_ID'),
"scopes": [
"user:base",
"file:all:read",
@@ -181,7 +179,7 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
resp = self.session.post(
f"{self.base_url}/oauth/access_token",
json={
"client_id": settings.ALIPAN_APP_ID,
"client_id": get_runtime_setting('ALIPAN_APP_ID'),
"grant_type": "authorization_code",
"code": self._auth_state["authCode"],
"code_verifier": self._auth_state["code_verifier"],
@@ -205,7 +203,7 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
resp = self.session.post(
f"{self.base_url}/oauth/access_token",
json={
"client_id": settings.ALIPAN_APP_ID,
"client_id": get_runtime_setting('ALIPAN_APP_ID'),
"grant_type": "refresh_token",
"refresh_token": refresh_token,
},
@@ -745,7 +743,7 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
logger.error(f"【阿里云盘】下载链接为空: {fileitem.name}")
return None
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
local_path = self._build_download_path(fileitem, path or get_runtime_setting('TEMP_PATH'))
if not local_path:
return None
@@ -759,7 +757,7 @@ class AliPan(StorageBase, metaclass=WeakSingleton):
try:
# 构建请求头,包含必要的认证信息
headers = {
"User-Agent": settings.NORMAL_USER_AGENT,
"User-Agent": get_runtime_setting('NORMAL_USER_AGENT'),
"Referer": "https://www.aliyundrive.com/",
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
+7 -9
View File
@@ -5,20 +5,18 @@ from datetime import datetime
from pathlib import Path
from typing import List, Optional
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
settings = RuntimeSettingsCompat()
from app.adapters.network.http import RequestUtils
from app.foundation.singleton import WeakSingleton
from app.foundation.url import UrlUtils
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.cache import cached
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
from app.runtime.stop import runtime_stop_state
from app.schemas.exception import OperationInterrupted, StorageQueryError
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.types import StorageSchema
from app.schemas.workflow import FileItem as _SchemaFileItem
# OpenList/AList 在 per_page<=0 时会退回后端默认 200,显式指定最大页大小避免大目录被截断。
OPENLIST_MAX_LIST_PAGE_SIZE = 500
@@ -41,7 +39,7 @@ class Alist(StorageBase, metaclass=WeakSingleton):
}
# 快照检查目录修改时间
snapshot_check_folder_modtime = settings.OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME
snapshot_check_folder_modtime = get_runtime_setting('OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME')
def __init__(self):
super().__init__()
@@ -692,7 +690,7 @@ class Alist(StorageBase, metaclass=WeakSingleton):
download_url = download_url + "?sign=" + result["data"]["sign"]
if not path:
local_path = settings.TEMP_PATH / fileitem.name
local_path = get_runtime_setting('TEMP_PATH') / fileitem.name
else:
local_path = path / fileitem.name
+5 -7
View File
@@ -4,19 +4,17 @@ import time
from pathlib import Path
from typing import List, Optional
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
settings = RuntimeSettingsCompat()
from app.adapters.system.fsproxy import fsproxy
from app.adapters.system.host import SystemUtils
from app.application.directory import DirectoryHelper
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
from app.runtime.stop import runtime_stop_state
from app.schemas.exception import StorageQueryError
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.types import StorageSchema
from app.schemas.workflow import FileItem as _SchemaFileItem
class LocalStorage(StorageBase):
@@ -477,7 +475,7 @@ class LocalStorage(StorageBase):
total_storage, free_storage = SystemUtils.space_usage(
[Path(d.download_path) for d in directory_helper.get_local_download_dirs() if d.download_path] +
[Path(d.library_path) for d in directory_helper.get_local_library_dirs() if d.library_path],
btrfs_fsid_dedup=settings.BTRFS_FSID_DEDUP,
btrfs_fsid_dedup=get_runtime_setting('BTRFS_FSID_DEDUP'),
)
return _SchemaStorageUsage(
total=total_storage,
+5 -7
View File
@@ -6,17 +6,15 @@ from collections import OrderedDict
from pathlib import Path
from typing import List, Optional, Union
from app.runtime.settings import RuntimeSettingsCompat
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
settings = RuntimeSettingsCompat()
from app.adapters.system.host import SystemUtils
from app.foundation import temporal as time_tools
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
from app.schemas.exception import StorageQueryError
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.types import StorageSchema
from app.schemas.workflow import FileItem as _SchemaFileItem
_MAX_FOLDER_LOCKS = 4096
_folder_locks: OrderedDict[str, threading.Lock] = OrderedDict()
@@ -50,7 +48,7 @@ class Rclone(StorageBase):
"copy": "复制"
}
snapshot_check_folder_modtime = settings.RCLONE_SNAPSHOT_CHECK_FOLDER_MODTIME
snapshot_check_folder_modtime = get_runtime_setting('RCLONE_SNAPSHOT_CHECK_FOLDER_MODTIME')
def init_storage(self):
"""
@@ -376,7 +374,7 @@ class Rclone(StorageBase):
"""
带实时进度显示的下载
"""
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
local_path = self._build_download_path(fileitem, path or get_runtime_setting('TEMP_PATH'))
if not local_path:
return None
+5 -7
View File
@@ -12,17 +12,15 @@ from smbprotocol.exceptions import (
SMBResponseException,
)
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
settings = RuntimeSettingsCompat()
from app.foundation.singleton import WeakSingleton
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
from app.runtime.stop import runtime_stop_state
from app.schemas.exception import StorageQueryError
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.types import StorageSchema
from app.schemas.workflow import FileItem as _SchemaFileItem
lock = threading.Lock()
@@ -550,7 +548,7 @@ class SMB(StorageBase, metaclass=WeakSingleton):
"""
带实时进度显示的下载
"""
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
local_path = self._build_download_path(fileitem, path or get_runtime_setting('TEMP_PATH'))
if not local_path:
return None
smb_path = self._normalize_path(fileitem.path)
+8 -10
View File
@@ -12,19 +12,17 @@ from cryptography.hazmat.primitives import hashes
from oss2 import SizedFileAdapter, determine_part_size
from oss2.models import PartInfo
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.stop import runtime_stop_state
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.workflow import FileItem as _SchemaFileItem
settings = RuntimeSettingsCompat()
from app.foundation import size as size_tools
from app.foundation.singleton import WeakSingleton
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.runtime.log import logger
from app.runtime.rate import QpsRateLimiter, RateStats
from app.runtime.settings import get_runtime_setting
from app.runtime.stop import runtime_stop_state
from app.schemas.exception import StorageQueryError
from app.schemas.file import StorageUsage as _SchemaStorageUsage
from app.schemas.types import StorageSchema
from app.schemas.workflow import FileItem as _SchemaFileItem
lock = Lock()
@@ -130,7 +128,7 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
生成 OAuth2 授权 URL
"""
try:
resp = self.session.get(f"{settings.U115_AUTH_SERVER}/u115/auth_url")
resp = self.session.get(f"{get_runtime_setting('U115_AUTH_SERVER')}/u115/auth_url")
if resp is None:
return {}, "无法连接到授权服务器"
@@ -165,7 +163,7 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
resp = self.session.post(
"https://passportapi.115.com/open/authDeviceCode",
data={
"client_id": settings.U115_APP_ID,
"client_id": get_runtime_setting('U115_APP_ID'),
"code_challenge": code_challenge,
"code_challenge_method": "sha256",
},
@@ -229,7 +227,7 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
try:
resp = self.session.get(
f"{settings.U115_AUTH_SERVER}/u115/token", params={"state": state}
f"{get_runtime_setting('U115_AUTH_SERVER')}/u115/token", params={"state": state}
)
if resp is None:
return {}, "无法连接到授权服务器"
@@ -910,7 +908,7 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
logger.error(f"【115】下载链接为空: {fileitem.name}")
return None
local_path = self._build_download_path(fileitem, path or settings.TEMP_PATH)
local_path = self._build_download_path(fileitem, path or get_runtime_setting('TEMP_PATH'))
if not local_path:
return None
+10 -11
View File
@@ -4,9 +4,8 @@ from typing import Optional, List, Tuple
from jinja2 import Template
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import MediaInfo, MusicInfo
from app.runtime.events import eventmanager
from app.domain.meta.metabase import MetaBase
@@ -204,7 +203,7 @@ class TransHandler:
"""
if not _fileitem.extension:
return False
if f".{_fileitem.extension.lower()}" in settings.RMT_SUBEXT:
if f".{_fileitem.extension.lower()}" in get_runtime_setting('RMT_SUBEXT'):
return True
return False
@@ -224,11 +223,11 @@ class TransHandler:
if not _fileitem.extension:
return False
extension = f".{_fileitem.extension.lower()}"
if extension in settings.RMT_SUBEXT:
if extension in get_runtime_setting('RMT_SUBEXT'):
return True
if __is_music_lyrics_file(_fileitem):
return True
if mediainfo.type != MediaType.MUSIC and extension in settings.RMT_AUDIOEXT:
if mediainfo.type != MediaType.MUSIC and extension in get_runtime_setting('RMT_AUDIOEXT'):
return True
return False
@@ -252,7 +251,7 @@ class TransHandler:
try:
# 重命名格式
rename_format = settings.RENAME_FORMAT(mediainfo.type)
rename_format = get_runtime_setting('RENAME_FORMAT')(mediainfo.type)
# 判断是否为文件夹
if fileitem.type == "dir":
@@ -953,10 +952,10 @@ class TransHandler:
# 添加默认字幕标识
if (
(settings.DEFAULT_SUB == "zh-cn" and new_file_type == ".chi.zh-cn")
or (settings.DEFAULT_SUB == "zh-tw" and new_file_type == ".zh-tw")
or (settings.DEFAULT_SUB == "ja" and new_file_type == ".ja")
or (settings.DEFAULT_SUB == "eng" and new_file_type == ".eng")
(get_runtime_setting('DEFAULT_SUB') == "zh-cn" and new_file_type == ".chi.zh-cn")
or (get_runtime_setting('DEFAULT_SUB') == "zh-tw" and new_file_type == ".zh-tw")
or (get_runtime_setting('DEFAULT_SUB') == "ja" and new_file_type == ".ja")
or (get_runtime_setting('DEFAULT_SUB') == "eng" and new_file_type == ".eng")
):
new_sub_tag = ".default" + new_file_type
else:
@@ -1283,7 +1282,7 @@ class TransHandler:
if media_file.type != "file":
continue
# 当前只有视频文件需要保留最新版本,其余格式无需处理,以避免误删 (issue 5449)
if f".{media_file.extension.lower()}" not in settings.RMT_MEDIAEXT:
if f".{media_file.extension.lower()}" not in get_runtime_setting('RMT_MEDIAEXT'):
continue
# 识别文件中的季集信息
filemeta = MetaInfoPath(media_path)
+6 -8
View File
@@ -12,10 +12,8 @@ from app.domain.scraper import MediaScraperHelper
from app.foundation.text import convert as zhconv_convert
from app.modules import _ModuleBase
from app.modules._base.media_auxiliary import MediaAuxiliaryProviderMixin
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
from app.schemas.context import MediaCredit, MediaImageSet
from app.schemas.media import normalize_media_source
from app.schemas.types import (
@@ -59,7 +57,7 @@ class ImdbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
def init_module(self) -> None:
"""按当前代理配置初始化 IMDb 客户端和通用刮削器。"""
self._config = ImdbConfigSnapshot(proxy=settings.PROXY)
self._config = ImdbConfigSnapshot(proxy=get_runtime_setting('PROXY'))
self.imdb_api = ImdbApi(proxies=self._config.proxy)
self.scraper = MediaScraperHelper()
@@ -517,7 +515,7 @@ class ImdbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
if requested_source not in {None, MediaSource.IMDb}:
return None
selected_source = requested_source or normalize_media_source(
settings.RECOGNIZE_SOURCE
get_runtime_setting('RECOGNIZE_SOURCE')
)
if selected_source != MediaSource.IMDb or not meta or not meta.name:
return None
@@ -560,7 +558,7 @@ class ImdbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
if requested_source not in {None, MediaSource.IMDb}:
return None
selected_source = requested_source or normalize_media_source(
settings.RECOGNIZE_SOURCE
get_runtime_setting('RECOGNIZE_SOURCE')
)
if selected_source != MediaSource.IMDb or not meta or not meta.name:
return None
@@ -657,7 +655,7 @@ class ImdbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
) -> Optional[str]:
"""生成 IMDb 来源的 NFO 元数据文本。"""
del kwargs
if (mediainfo.scrape_source or settings.SCRAP_SOURCE) != MediaSource.IMDb.value:
if (mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')) != MediaSource.IMDb.value:
return None
if not self.scraper:
return None
@@ -672,7 +670,7 @@ class ImdbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
episode: Optional[int] = None,
) -> Optional[dict]:
"""生成 IMDb 来源的图片文件名与下载地址映射。"""
if (mediainfo.scrape_source or settings.SCRAP_SOURCE) != MediaSource.IMDb.value:
if (mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')) != MediaSource.IMDb.value:
return None
if not self.scraper:
return None
+11 -13
View File
@@ -9,11 +9,9 @@ from pydantic import BaseModel, ConfigDict, Field, ValidationError
from app.adapters.network.http import AsyncRequestUtils, RequestUtils
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.tasks import get_task_registry
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
from app.runtime.tasks import get_task_registry
TModel = TypeVar("TModel", bound=BaseModel)
@@ -227,7 +225,7 @@ class ImdbApi:
def __init__(self, proxies: Optional[dict] = None) -> None:
"""按一次模块配置快照创建网络请求适配器。"""
headers = {
"User-Agent": settings.NORMAL_USER_AGENT,
"User-Agent": get_runtime_setting('NORMAL_USER_AGENT'),
"Accept": "application/graphql+json, application/json",
"Content-Type": "application/json",
"x-imdb-client-name": "imdb-web-next-localized",
@@ -262,8 +260,8 @@ class ImdbApi:
return cls._freeze_value(params or {})
@cached(
maxsize=settings.CONF.imdb,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').imdb,
ttl=get_runtime_setting('CONF').meta,
skip_none=True,
shared_key="imdb_get",
)
@@ -274,8 +272,8 @@ class ImdbApi:
return self._request.get_json(url, params=dict(params_key))
@cached(
maxsize=settings.CONF.imdb,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').imdb,
ttl=get_runtime_setting('CONF').meta,
skip_none=True,
shared_key="imdb_get",
)
@@ -286,8 +284,8 @@ class ImdbApi:
return await self._async_request.get_json(url, params=dict(params_key))
@cached(
maxsize=settings.CONF.imdb,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').imdb,
ttl=get_runtime_setting('CONF').meta,
skip_none=True,
shared_key="imdb_graphql",
skip_if=_is_graphql_error,
@@ -302,8 +300,8 @@ class ImdbApi:
)
@cached(
maxsize=settings.CONF.imdb,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').imdb,
ttl=get_runtime_setting('CONF').meta,
skip_none=True,
shared_key="imdb_graphql",
skip_if=_is_graphql_error,
+5 -6
View File
@@ -8,9 +8,8 @@ from urllib.parse import urljoin, urlsplit
from requests import Session
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.adapters.network.cloudflare import under_challenge
from app.runtime.log import logger
from app.adapters.network.http import RequestUtils
@@ -228,7 +227,7 @@ class SiteParserBase(metaclass=ABCMeta):
)
)
# 解析用户未读消息
if settings.SITE_MESSAGE:
if get_runtime_setting('SITE_MESSAGE'):
self._pase_unread_msgs()
# 解析用户上传、下载、分享率等信息
if self._user_traffic_page:
@@ -346,7 +345,7 @@ class SiteParserBase(metaclass=ABCMeta):
:return:
"""
req_headers = None
proxies = settings.PROXY if self._proxy else None
proxies = get_runtime_setting('PROXY') if self._proxy else None
if self._ua or headers or self._addition_headers:
if self.request_mode == "apikey":
@@ -408,8 +407,8 @@ class SiteParserBase(metaclass=ABCMeta):
f"{self._site_name} 检测到Cloudflare,请更新Cookie和UA")
return ""
return RequestUtils.get_decoded_html_content(res,
settings.ENCODING_DETECTION_PERFORMANCE_MODE,
settings.ENCODING_DETECTION_MIN_CONFIDENCE)
get_runtime_setting('ENCODING_DETECTION_PERFORMANCE_MODE'),
get_runtime_setting('ENCODING_DETECTION_MIN_CONFIDENCE'))
return ""
+3 -4
View File
@@ -4,9 +4,8 @@ from urllib.parse import urljoin
from typing import Optional, Tuple
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.adapters.network.http import RequestUtils
from app.domain import site as site_rules
from app.foundation import temporal as time_tools
@@ -195,7 +194,7 @@ class RousiSiteUserInfo(SiteParserBase):
res = RequestUtils(
headers=headers,
timeout=60,
proxies=settings.PROXY if self._proxy else None
proxies=get_runtime_setting('PROXY') if self._proxy else None
).get_res(
url=urljoin(self._base_url, "api/messages"),
params=params
@@ -231,7 +230,7 @@ class RousiSiteUserInfo(SiteParserBase):
RequestUtils(
headers=headers,
timeout=60,
proxies=settings.PROXY if self._proxy else None
proxies=get_runtime_setting('PROXY') if self._proxy else None
).post_res(
url=urljoin(self._base_url, "api/messages/read-all")
)
+8 -9
View File
@@ -9,9 +9,8 @@ from jinja2 import Template
from pyquery import PyQuery
from app.runtime.execution import run_in_threadpool
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.types import MediaType
from app.adapters.system import rust as rust_accel
@@ -136,9 +135,9 @@ class SiteSpider:
self.page = page
if self.domain and not str(self.domain).endswith("/"):
self.domain = self.domain + "/"
self.ua = indexer.get('ua') or settings.USER_AGENT
self.proxies = settings.PROXY if indexer.get('proxy') else None
self.proxy_server = settings.PROXY_SERVER if indexer.get('proxy') else None
self.ua = indexer.get('ua') or get_runtime_setting('USER_AGENT')
self.proxies = get_runtime_setting('PROXY') if indexer.get('proxy') else None
self.proxy_server = get_runtime_setting('PROXY_SERVER') if indexer.get('proxy') else None
self.cookie = indexer.get('cookie')
self.referer = referer
# 初始化属性
@@ -362,8 +361,8 @@ class SiteSpider:
return self.parse(
RequestUtils.get_decoded_html_content(
ret,
performance_mode=settings.ENCODING_DETECTION_PERFORMANCE_MODE,
confidence_threshold=settings.ENCODING_DETECTION_MIN_CONFIDENCE
performance_mode=get_runtime_setting('ENCODING_DETECTION_PERFORMANCE_MODE'),
confidence_threshold=get_runtime_setting('ENCODING_DETECTION_MIN_CONFIDENCE')
)
)
@@ -394,8 +393,8 @@ class SiteSpider:
self.parse,
RequestUtils.get_decoded_html_content(
ret,
performance_mode=settings.ENCODING_DETECTION_PERFORMANCE_MODE,
confidence_threshold=settings.ENCODING_DETECTION_MIN_CONFIDENCE
performance_mode=get_runtime_setting('ENCODING_DETECTION_PERFORMANCE_MODE'),
confidence_threshold=get_runtime_setting('ENCODING_DETECTION_MIN_CONFIDENCE')
)
)
+2 -3
View File
@@ -1,9 +1,8 @@
import urllib.parse
from typing import Tuple, List
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config
from app.runtime.log import logger
from app.schemas.types import MediaType
@@ -70,7 +69,7 @@ class HaiDanSpider:
self._searchurl = self._searchurl % self._url
self._name = indexer.get('name')
if indexer.get('proxy'):
self._proxy = settings.PROXY
self._proxy = get_runtime_setting('PROXY')
self._cookie = indexer.get('cookie')
self._ua = indexer.get('ua')
self._timeout = indexer.get('timeout') or 15
+2 -3
View File
@@ -1,8 +1,7 @@
from typing import Tuple, List, Optional
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config
from app.runtime.log import logger
from app.schemas.types import MediaType
@@ -76,7 +75,7 @@ class HddolbySpider:
self._domain_host = site_rules.extract_domain(self._domain)
self._name = indexer.get('name')
if indexer.get('proxy'):
self._proxy = settings.PROXY
self._proxy = get_runtime_setting('PROXY')
self._cookie = indexer.get('cookie')
self._ua = indexer.get('ua')
self._apikey = indexer.get('apikey')
+2 -3
View File
@@ -4,9 +4,8 @@ import re
from typing import Tuple, List, Optional
from urllib.parse import urlparse
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config
from app.runtime.log import logger
from app.schemas.types import MediaType
@@ -75,7 +74,7 @@ class MTorrentSpider:
self._searchurl = self._searchurl % self._domain
self._name = indexer.get('name')
if indexer.get('proxy'):
self._proxy = settings.PROXY
self._proxy = get_runtime_setting('PROXY')
self._cookie = indexer.get('cookie')
self._ua = indexer.get('ua')
self._apikey = indexer.get('apikey')
+2 -3
View File
@@ -2,9 +2,8 @@ import base64
import json
from typing import List, Optional, Tuple
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.configuration import get_configured_system_config
from app.runtime.log import logger
from app.schemas.types import MediaType
@@ -60,7 +59,7 @@ class RousiSpider:
self._downloadurl = self._downloadurl % (self._domain, "%s")
self._name = indexer.get('name')
if indexer.get('proxy'):
self._proxy = settings.PROXY
self._proxy = get_runtime_setting('PROXY')
self._cookie = indexer.get('cookie')
self._ua = indexer.get('ua')
self._apikey = indexer.get('apikey')
+3 -4
View File
@@ -3,9 +3,8 @@ import json
import time
from typing import List, Optional, Tuple
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.types import MediaType
from app.adapters.network.http import AsyncRequestUtils, RequestUtils
@@ -34,9 +33,9 @@ class SunnyPTSpider:
self._api_url = str(
indexer.get("api_url") or "https://api.sunnypt.top/api/v1/mp"
).rstrip("/")
self._proxy = settings.PROXY if indexer.get("proxy") else None
self._proxy = get_runtime_setting('PROXY') if indexer.get("proxy") else None
self._use_proxy = bool(indexer.get("proxy"))
self._user_agent = indexer.get("ua") or settings.USER_AGENT
self._user_agent = indexer.get("ua") or get_runtime_setting('USER_AGENT')
self._api_key = indexer.get("apikey")
self._timeout = indexer.get("timeout") or 15
self._configured_categories = self._parse_configured_categories(
+2 -3
View File
@@ -2,9 +2,8 @@ import re
from typing import Tuple, List, Optional
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.adapters.network.http import RequestUtils, AsyncRequestUtils
from app.foundation.singleton import SingletonClass
@@ -33,7 +32,7 @@ class TNodeSpider(metaclass=SingletonClass):
self._searchurl = self._baseurl % self._domain
self._name = indexer.get('name')
if indexer.get('proxy'):
self._proxy = settings.PROXY
self._proxy = get_runtime_setting('PROXY')
self._cookie = indexer.get('cookie')
self._ua = indexer.get('ua')
self._timeout = indexer.get('timeout') or 15
+2 -3
View File
@@ -1,9 +1,8 @@
from typing import List, Tuple, Optional
from urllib.parse import quote
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.types import MediaType
from app.adapters.network.http import RequestUtils, AsyncRequestUtils
@@ -34,7 +33,7 @@ class TorrentLeech:
"""初始化站点认证信息和媒体分类配置。"""
self._indexer = indexer
if indexer.get('proxy'):
self._proxy = settings.PROXY
self._proxy = get_runtime_setting('PROXY')
self._timeout = indexer.get('timeout') or 15
def __category_ids(self, mtype: MediaType = None) -> List[str]:
+3 -4
View File
@@ -2,9 +2,8 @@ import base64
import json
from typing import List, Optional, Tuple
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.types import MediaType
from app.adapters.network.http import AsyncRequestUtils, RequestUtils
@@ -46,9 +45,9 @@ class YemaSpider:
indexer = indexer or {}
self._name = indexer.get("name") or "YemaPT"
self._site_url = str(indexer.get("domain") or "https://www.yemapt.org/").rstrip("/")
self._proxy = settings.PROXY if indexer.get("proxy") else None
self._proxy = get_runtime_setting('PROXY') if indexer.get("proxy") else None
self._use_proxy = bool(indexer.get("proxy"))
self._user_agent = indexer.get("ua") or settings.USER_AGENT
self._user_agent = indexer.get("ua") or get_runtime_setting('USER_AGENT')
self._api_key = indexer.get("apikey")
self._timeout = indexer.get("timeout") or 15
self._search_url = f"{self._site_url}/openApi/torrent/fetchOpenTorrentList.json"
+4 -5
View File
@@ -9,9 +9,8 @@ from app.schemas.mediaserver import MediaServerItem as _SchemaMediaServerItem
from app.schemas.mediaserver import MediaServerLibrary as _SchemaMediaServerLibrary
from app.schemas.mediaserver import MediaServerPlayItem as _SchemaMediaServerPlayItem
from app.schemas.mediaserver import WebhookEventInfo as _SchemaWebhookEventInfo
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.mediaserver import MediaServerIdentityHelper, format_emby_family_item
from app.runtime.log import logger
from app.schemas.types import MediaType
@@ -40,7 +39,7 @@ class Jellyfin:
if self._playhost:
self._playhost = UrlUtils.standardize_base_url(self._playhost)
self._apikey = apikey
self.user = self.get_user(settings.SUPERUSER)
self.user = self.get_user(get_runtime_setting('SUPERUSER'))
self.serverid = self.get_server_id()
self._sync_libraries = sync_libraries or []
@@ -253,9 +252,9 @@ class Jellyfin:
for user in users:
if user.get("Name") == user_name:
return user.get("Id")
if user_name == settings.SUPERUSER:
if user_name == get_runtime_setting('SUPERUSER'):
logger.warning(
"MoviePilot 当前配置的超级管理员用户名为 {},请确保Jellyfin中存在同名管理员账号,否则可能无法正常使用部分功能!".format(settings.SUPERUSER)
"MoviePilot 当前配置的超级管理员用户名为 {},请确保Jellyfin中存在同名管理员账号,否则可能无法正常使用部分功能!".format(get_runtime_setting('SUPERUSER'))
)
# 查询管理员,优先选择同时具备全库访问能力的账号,再回退到普通管理员。
# 获取总媒体库数量
+6 -7
View File
@@ -1,9 +1,8 @@
from typing import Any, Optional, Tuple, Union
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import MusicInfo
from app.runtime.log import logger
from app.modules import _ModuleBase
@@ -155,7 +154,7 @@ class ListenBrainzModule(_ModuleBase):
)
@classmethod
@cached(maxsize=settings.CONF.listenbrainz, ttl=settings.CONF.meta, skip_none=True)
@cached(maxsize=get_runtime_setting('CONF').listenbrainz, ttl=get_runtime_setting('CONF').meta, skip_none=True)
def _request_json(
cls,
path: str,
@@ -164,10 +163,10 @@ class ListenBrainzModule(_ModuleBase):
"""请求 ListenBrainz JSON 接口并统一处理网络和响应错误。"""
response = RequestUtils(
headers={
"User-Agent": f"{settings.USER_AGENT} (https://github.com/jxxghp/MoviePilot)",
"User-Agent": f"{get_runtime_setting('USER_AGENT')} (https://github.com/jxxghp/MoviePilot)",
"Accept": "application/json",
},
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
timeout=20,
).get_res(f"{cls._base_url}{path}", params=params)
if response is None:
@@ -281,7 +280,7 @@ class ListenBrainzModule(_ModuleBase):
if not release_mbid:
return None
# 支持配置音乐封面代理地址,解决 coverartarchive.org 无法访问的问题
base = (settings.MUSIC_COVER_PROXY or "https://coverartarchive.org").rstrip("/")
base = (get_runtime_setting('MUSIC_COVER_PROXY') or "https://coverartarchive.org").rstrip("/")
return f"{base}/release/{release_mbid}/front-500"
@classmethod
@@ -290,7 +289,7 @@ class ListenBrainzModule(_ModuleBase):
if not release_group_id:
return None
# 支持配置音乐封面代理地址,解决 coverartarchive.org 无法访问的问题
base = (settings.MUSIC_COVER_PROXY or "https://coverartarchive.org").rstrip("/")
base = (get_runtime_setting('MUSIC_COVER_PROXY') or "https://coverartarchive.org").rstrip("/")
return f"{base}/release-group/{release_group_id}/front-500"
@staticmethod
+5 -6
View File
@@ -9,10 +9,9 @@ from app.domain.meta.metamusic import MetaMusic
from app.modules import _ModuleBase
from app.runtime.cache import cached
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.schemas.types import ModuleType, OtherModulesType
settings = RuntimeSettingsCompat()
class LrclibModule(_ModuleBase):
@@ -201,13 +200,13 @@ class LrclibModule(_ModuleBase):
time.sleep(delay)
response = RequestUtils(
headers={
"User-Agent": f"{settings.USER_AGENT} (https://github.com/jxxghp/MoviePilot)",
"User-Agent": f"{get_runtime_setting('USER_AGENT')} (https://github.com/jxxghp/MoviePilot)",
"Accept": "application/json",
},
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
timeout=20,
).get_res(
f"{(base_url or str(settings.LRCLIB_BASE_URL)).rstrip('/')}{path}",
f"{(base_url or str(get_runtime_setting('LRCLIB_BASE_URL'))).rstrip('/')}{path}",
params=params,
)
cls._last_request_at = time.monotonic()
@@ -231,7 +230,7 @@ class LrclibModule(_ModuleBase):
if response.status_code in (429, 503):
retry_after = cls._retry_after_seconds(response.headers.get("Retry-After"))
response.close()
max_wait = max(int(settings.LYRICS_PROVIDER_RETRY_MAX_WAIT), 0)
max_wait = max(int(get_runtime_setting('LYRICS_PROVIDER_RETRY_MAX_WAIT')), 0)
if retry_after > max_wait:
cls._cooldown_until = time.monotonic() + retry_after
logger.warning(f"LRCLIB 进入冷却 {retry_after:g} 秒,跳过当前批次后续请求")
+9 -10
View File
@@ -8,9 +8,8 @@ from typing import Any, Iterable, Optional, Tuple, Union
from requests import Session
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import (
MusicAlbumInfo,
MusicArtistInfo,
@@ -1761,7 +1760,7 @@ class MusicBrainzModule(_ModuleBase):
if not release_group_id:
return None
# 支持配置音乐封面代理地址,解决 coverartarchive.org 无法访问的问题
base = (settings.MUSIC_COVER_PROXY or "https://coverartarchive.org").rstrip("/")
base = (get_runtime_setting('MUSIC_COVER_PROXY') or "https://coverartarchive.org").rstrip("/")
return f"{base}/release-group/{release_group_id}/front-500"
@classmethod
@@ -1795,7 +1794,7 @@ class MusicBrainzModule(_ModuleBase):
await asyncio.sleep(delay)
@classmethod
@cached(maxsize=settings.CONF.musicbrainz, ttl=settings.CONF.meta, skip_none=True)
@cached(maxsize=get_runtime_setting('CONF').musicbrainz, ttl=get_runtime_setting('CONF').meta, skip_none=True)
def _request_json(
cls,
path: str,
@@ -1811,10 +1810,10 @@ class MusicBrainzModule(_ModuleBase):
cls._wait_for_rate_limit()
response = RequestUtils(
headers={
"User-Agent": f"{settings.USER_AGENT} (https://github.com/jxxghp/MoviePilot)",
"User-Agent": f"{get_runtime_setting('USER_AGENT')} (https://github.com/jxxghp/MoviePilot)",
"Accept": "application/json",
},
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
session=cls._get_session(),
timeout=20,
).get_res(f"{cls._base_url}{path}", params=params)
@@ -1850,8 +1849,8 @@ class MusicBrainzModule(_ModuleBase):
@classmethod
@cached(
maxsize=settings.CONF.musicbrainz,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').musicbrainz,
ttl=get_runtime_setting('CONF').meta,
skip_none=True,
shared_key="_request_json",
)
@@ -1866,10 +1865,10 @@ class MusicBrainzModule(_ModuleBase):
await cls._async_wait_for_rate_limit()
response = await AsyncRequestUtils(
headers={
"User-Agent": f"{settings.USER_AGENT} (https://github.com/jxxghp/MoviePilot)",
"User-Agent": f"{get_runtime_setting('USER_AGENT')} (https://github.com/jxxghp/MoviePilot)",
"Accept": "application/json",
},
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
timeout=20,
).get_res(f"{cls._base_url}{path}", params=params)
if response is None:
+4 -5
View File
@@ -6,9 +6,8 @@ from time import time
from typing import Optional
from app.runtime.cache import FileCache, TTLCache
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import MusicInfo
from app.domain.meta.metamusic import MetaMusic
from app.runtime.log import logger
@@ -37,15 +36,15 @@ class MusicBrainzCache(metaclass=WeakSingleton):
def __init__(self):
"""初始化音乐识别缓存并恢复未过期的持久化数据。"""
self.maxsize = settings.CONF.musicbrainz
self.ttl = settings.CONF.meta
self.maxsize = get_runtime_setting('CONF').musicbrainz
self.ttl = get_runtime_setting('CONF').meta
self.region = "__musicbrainz_cache__"
self._cache = TTLCache(region=self.region, maxsize=self.maxsize, ttl=self.ttl)
self._expires_at: dict[str, float] = {}
self._dirty = False
self._file_cache = None
if not self._cache.is_redis():
self._file_cache = FileCache(base=settings.CACHE_PATH, ttl=self.ttl)
self._file_cache = FileCache(base=get_runtime_setting('CACHE_PATH'), ttl=self.ttl)
self._restore()
def _restore(self) -> None:
+6 -8
View File
@@ -6,11 +6,9 @@ from app.domain.context import MusicInfo, MusicLyrics
from app.domain.meta.metamusic import MetaMusic
from app.modules import _ModuleBase
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.schemas.types import ModuleType, OtherModulesType
settings = RuntimeSettingsCompat()
class MusixmatchModule(_ModuleBase):
"""使用用户授权的 Musixmatch 官方 API 获取同步或纯文本歌词。"""
@@ -30,7 +28,7 @@ class MusixmatchModule(_ModuleBase):
def test(self) -> Tuple[bool, str]:
"""验证 API Key 和官方接口连通性。"""
if not str(settings.MUSIXMATCH_API_KEY or "").strip():
if not str(get_runtime_setting('MUSIXMATCH_API_KEY') or "").strip():
return False, "Musixmatch API Key 未配置"
payload = self._request("matcher.lyrics.get", {"q_track": "test", "q_artist": "test"})
return (True, "") if payload is not None else (False, "Musixmatch API 连接或授权失败")
@@ -106,15 +104,15 @@ class MusixmatchModule(_ModuleBase):
def _request(self, method: str, params: dict[str, Any]) -> Optional[dict[str, Any]]:
"""请求官方 API,并对限流或服务过载设置进程内冷却。"""
api_key = str(settings.MUSIXMATCH_API_KEY or "").strip()
api_key = str(get_runtime_setting('MUSIXMATCH_API_KEY') or "").strip()
if not api_key or time.monotonic() < self._cooldown_until:
return None
response = RequestUtils(
ua=settings.USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
timeout=20,
).get_res(
f"{str(settings.MUSIXMATCH_BASE_URL).rstrip('/')}/{method}",
f"{str(get_runtime_setting('MUSIXMATCH_BASE_URL')).rstrip('/')}/{method}",
params={**params, "apikey": api_key},
)
if response is None:
+2 -4
View File
@@ -2,11 +2,9 @@ from typing import Tuple, Union
from app.application.database import get_database_governance
from app.modules import _ModuleBase
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.schemas.types import ModuleType, OtherModulesType
settings = RuntimeSettingsCompat()
class PostgreSQLModule(_ModuleBase):
"""
@@ -55,7 +53,7 @@ class PostgreSQLModule(_ModuleBase):
"""
测试模块连接性
"""
if settings.DB_TYPE != "postgresql":
if get_runtime_setting('DB_TYPE') != "postgresql":
return None
error = get_database_governance().test()
if error:
+7 -8
View File
@@ -4,7 +4,7 @@ from typing import Set, Tuple, Optional, Union, List, Dict
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.modules._base import _DownloaderModuleBase
from app.modules.qbittorrent.qbittorrent import Qbittorrent
from app.schemas.transfer import DownloaderFile, DownloaderTorrent
@@ -19,7 +19,6 @@ from app.foundation import size as size_tools
from app.foundation import temporal as time_tools
from app.foundation import text as text_tools
settings = RuntimeSettingsCompat()
_QBITTORRENT_DOWNLOADING_STATES = {
"allocating",
@@ -128,8 +127,8 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]):
tag = text_tools.random_string(10)
if label:
tags = label.split(',') + [tag]
elif settings.TORRENT_TAG:
tags = [tag, settings.TORRENT_TAG]
elif get_runtime_setting('TORRENT_TAG'):
tags = [tag, get_runtime_setting('TORRENT_TAG')]
else:
tags = [tag]
# 如果要选择文件则先暂停
@@ -163,9 +162,9 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]):
# 给种子打上标签
if "已整理" in torrent_tags:
server.remove_torrents_tag(ids=torrent_hash, tag=['已整理'])
if settings.TORRENT_TAG and settings.TORRENT_TAG not in torrent_tags:
logger.info(f"给种子 {torrent_hash} 打上标签:{settings.TORRENT_TAG}")
server.set_torrents_tag(ids=torrent_hash, tags=[settings.TORRENT_TAG])
if get_runtime_setting('TORRENT_TAG') and get_runtime_setting('TORRENT_TAG') not in torrent_tags:
logger.info(f"给种子 {torrent_hash} 打上标签:{get_runtime_setting('TORRENT_TAG')}")
server.set_torrents_tag(ids=torrent_hash, tags=[get_runtime_setting('TORRENT_TAG')])
# 获取种子内容布局: `Original: 原始, Subfolder: 创建子文件夹, NoSubfolder: 不创建子文件夹`
torrent_layout = server.get_content_layout()
return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, f"下载任务已存在"
@@ -250,7 +249,7 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]):
servers: Dict[str, Qbittorrent] = self.get_instances()
ret_torrents = []
query_status = self._normalize_query_status(status)
query_tags = None if include_all_tags else settings.TORRENT_TAG
query_tags = None if include_all_tags else get_runtime_setting('TORRENT_TAG')
def __get_torrent_path(torrent_data: dict) -> Path:
"""
+7 -10
View File
@@ -7,19 +7,15 @@ import hashlib
import io
import pickle
import threading
from typing import Optional, List, Tuple
from typing import List, Optional, Tuple
from PIL import Image
from app.runtime.cache import FileCache
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.adapters.network.http import RequestUtils
from app.application.messaging.ingress import submit_message_to_host
from app.domain.context import MediaInfo, Context
from app.domain.context import Context, MediaInfo
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.runtime.thread import ThreadHelper
from app.foundation import size as size_tools
from app.modules.qqbot.api import (
get_access_token,
get_gateway_url,
@@ -27,8 +23,9 @@ from app.modules.qqbot.api import (
send_proactive_group_message,
)
from app.modules.qqbot.gateway import run_gateway
from app.adapters.network.http import RequestUtils
from app.foundation import size as size_tools
from app.runtime.cache import FileCache
from app.runtime.log import logger
from app.runtime.thread import ThreadHelper
# QQ Markdown 图片展示尺寸限制,避免竖版海报被客户端拉伸变形
_DEFAULT_IMAGE_SIZE: Tuple[int, int] = (208, 320)
+2 -4
View File
@@ -2,11 +2,9 @@ from typing import Tuple, Union
from app.adapters.cache.redis import RedisHelper
from app.modules import _ModuleBase
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.schemas.types import ModuleType, OtherModulesType
settings = RuntimeSettingsCompat()
class RedisModule(_ModuleBase):
"""
@@ -55,7 +53,7 @@ class RedisModule(_ModuleBase):
"""
测试模块连接性
"""
if settings.CACHE_BACKEND_TYPE != "redis":
if get_runtime_setting('CACHE_BACKEND_TYPE') != "redis":
return None
if RedisHelper().test():
return True, ""
+8 -9
View File
@@ -4,7 +4,7 @@ from typing import Set, Tuple, Optional, Union, List, Dict
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.modules._base import _DownloaderModuleBase
from app.modules.rtorrent.rtorrent import Rtorrent
from app.schemas.transfer import DownloaderFile, DownloaderTorrent
@@ -19,7 +19,6 @@ from app.foundation import size as size_tools
from app.foundation import temporal as time_tools
from app.foundation import text as text_tools
settings = RuntimeSettingsCompat()
class RtorrentModule(_DownloaderModuleBase[Rtorrent]):
@@ -113,8 +112,8 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]):
tag = text_tools.random_string(10)
if label:
tags = label.split(",") + [tag]
elif settings.TORRENT_TAG:
tags = [tag, settings.TORRENT_TAG]
elif get_runtime_setting('TORRENT_TAG'):
tags = [tag, get_runtime_setting('TORRENT_TAG')]
else:
tags = [tag]
# 如果要选择文件则先暂停
@@ -160,14 +159,14 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]):
ids=torrent_hash, tag=["已整理"]
)
if (
settings.TORRENT_TAG
and settings.TORRENT_TAG not in torrent_tags
get_runtime_setting('TORRENT_TAG')
and get_runtime_setting('TORRENT_TAG') not in torrent_tags
):
logger.info(
f"给种子 {torrent_hash} 打上标签:{settings.TORRENT_TAG}"
f"给种子 {torrent_hash} 打上标签:{get_runtime_setting('TORRENT_TAG')}"
)
server.set_torrents_tag(
ids=torrent_hash, tags=[settings.TORRENT_TAG]
ids=torrent_hash, tags=[get_runtime_setting('TORRENT_TAG')]
)
return (
downloader or self.get_default_config_name(),
@@ -266,7 +265,7 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]):
servers: Dict[str, Rtorrent] = self.get_instances()
ret_torrents = []
query_status = self._normalize_query_status(status)
query_tags = None if include_all_tags else settings.TORRENT_TAG
query_tags = None if include_all_tags else get_runtime_setting('TORRENT_TAG')
def __get_torrent_path(torrent_data: dict) -> Path:
"""
+2 -3
View File
@@ -8,9 +8,8 @@ from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from slack_sdk import WebClient
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.messaging.ingress import forward_message_to_host
from app.domain.context import MediaInfo, Context
from app.domain.metainfo import MetaInfo
@@ -266,7 +265,7 @@ class Slack:
try:
headers = {
"Authorization": f"Bearer {self._oauth_token}",
"User-Agent": settings.USER_AGENT,
"User-Agent": get_runtime_setting('USER_AGENT'),
"Accept": "*/*",
}
resp = RequestUtils(headers=headers, timeout=30).get_res(file_url)
+2 -3
View File
@@ -4,9 +4,8 @@ from urllib.parse import urljoin, urlparse
from lxml import etree
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import Context
from app.application.site.query import get_configured_site_query_service
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
@@ -146,7 +145,7 @@ class SubtitleModule(_ModuleBase):
request = RequestUtils(
cookies=torrent.site_cookie,
ua=torrent.site_ua,
proxies=settings.PROXY if torrent.site_proxy else None,
proxies=get_runtime_setting('PROXY') if torrent.site_proxy else None,
)
res = request.get_res(torrent.page_url)
if res and res.status_code == 200:
+2 -3
View File
@@ -36,9 +36,8 @@ try:
except ImportError:
from telegramify_markdown.type import ContentTypes, File, Photo, Text # noqa: E402
from app.runtime.settings import RuntimeSettingsCompat # noqa: E402
from app.runtime.settings import get_runtime_setting # noqa: E402
settings = RuntimeSettingsCompat()
from app.domain.context import MediaInfo, Context # noqa: E402
from app.domain.metainfo import MetaInfo # noqa: E402
from app.application.image import ImageHelper # noqa: E402
@@ -124,7 +123,7 @@ class Telegram:
apihelper.API_URL = "https://api.telegram.org/bot{0}/{1}"
apihelper.FILE_URL = "https://api.telegram.org/file/bot{0}/{1}"
# 设置代理
apihelper.proxy = settings.PROXY
apihelper.proxy = get_runtime_setting('PROXY')
# bot
_bot = TeleBot(self._telegram_token, parse_mode=TELEGRAM_PARSE_MODE_MARKDOWN)
# 记录句柄
+10 -11
View File
@@ -1,9 +1,8 @@
from typing import Any, Optional, Tuple, Union
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import (
MusicAlbumInfo,
MusicArtistInfo,
@@ -553,20 +552,20 @@ class TheAudioDbModule(_ModuleBase):
return results
@classmethod
@cached(maxsize=settings.CONF.theaudiodb, ttl=settings.CONF.meta, skip_none=True)
@cached(maxsize=get_runtime_setting('CONF').theaudiodb, ttl=get_runtime_setting('CONF').meta, skip_none=True)
def _request_json(
cls,
endpoint: str,
params: Optional[dict[str, Any]] = None,
) -> Optional[dict[str, Any]]:
"""请求 TheAudioDB V1 JSON 接口并统一处理错误响应。"""
api_key = str(settings.THEAUDIODB_API_KEY or "").strip()
api_key = str(get_runtime_setting('THEAUDIODB_API_KEY') or "").strip()
if not api_key:
logger.warning("TheAudioDB API Key 未配置,跳过请求")
return None
response = RequestUtils(
ua=settings.USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
timeout=30,
).get_res(
url=f"{cls._base_url}/{api_key}/{endpoint}",
@@ -594,8 +593,8 @@ class TheAudioDbModule(_ModuleBase):
@classmethod
@cached(
maxsize=settings.CONF.theaudiodb,
ttl=settings.CONF.meta,
maxsize=get_runtime_setting('CONF').theaudiodb,
ttl=get_runtime_setting('CONF').meta,
skip_none=True,
shared_key="_request_json",
)
@@ -605,13 +604,13 @@ class TheAudioDbModule(_ModuleBase):
params: Optional[dict[str, Any]] = None,
) -> Optional[dict[str, Any]]:
"""异步请求 TheAudioDB V1 JSON 接口并统一处理错误响应。"""
api_key = str(settings.THEAUDIODB_API_KEY or "").strip()
api_key = str(get_runtime_setting('THEAUDIODB_API_KEY') or "").strip()
if not api_key:
logger.warning("TheAudioDB API Key 未配置,跳过请求")
return None
response = await AsyncRequestUtils(
ua=settings.USER_AGENT,
proxies=settings.PROXY,
ua=get_runtime_setting('USER_AGENT'),
proxies=get_runtime_setting('PROXY'),
timeout=30,
).get_res(
url=f"{cls._base_url}/{api_key}/{endpoint}",
+12 -12
View File
@@ -94,13 +94,13 @@ class TheMovieDbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
"""
测试模块连接性
"""
ret = RequestUtils(ua=get_runtime_setting("NORMAL_USER_AGENT"), proxies=get_runtime_setting("PROXY")).get_res(
f"https://{get_runtime_setting("TMDB_API_DOMAIN")}/3/movie/550?api_key={get_runtime_setting("TMDB_API_KEY")}")
ret = RequestUtils(ua=get_runtime_setting('NORMAL_USER_AGENT'), proxies=get_runtime_setting('PROXY')).get_res(
f"https://{get_runtime_setting('TMDB_API_DOMAIN')}/3/movie/550?api_key={get_runtime_setting('TMDB_API_KEY')}")
if ret and ret.status_code == 200:
return True, ""
elif ret:
return False, f"无法连接 {get_runtime_setting("TMDB_API_DOMAIN")},错误码:{ret.status_code}"
return False, f"{get_runtime_setting("TMDB_API_DOMAIN")} 网络连接失败"
return False, f"无法连接 {get_runtime_setting('TMDB_API_DOMAIN')},错误码:{ret.status_code}"
return False, f"{get_runtime_setting('TMDB_API_DOMAIN')} 网络连接失败"
def init_setting(self) -> Tuple[str, Union[str, bool]]:
pass
@@ -122,7 +122,7 @@ class TheMovieDbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
if not tmdbid and not meta:
return False
selected_source = normalize_media_source(media_source or get_runtime_setting("RECOGNIZE_SOURCE"))
selected_source = normalize_media_source(media_source or get_runtime_setting('RECOGNIZE_SOURCE'))
if meta and not tmdbid and selected_source != MediaSource.TMDB:
return False
@@ -973,7 +973,7 @@ class TheMovieDbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param season: 季号
:param episode: 集号
"""
if (mediainfo.scrape_source or get_runtime_setting("SCRAP_SOURCE")) != "themoviedb":
if (mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')) != "themoviedb":
return None
return self.scraper.get_metadata_nfo(meta=meta, mediainfo=mediainfo, season=season, episode=episode)
@@ -985,7 +985,7 @@ class TheMovieDbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param season: 季号
:param episode: 集号
"""
if (mediainfo.scrape_source or get_runtime_setting("SCRAP_SOURCE")) != "themoviedb":
if (mediainfo.scrape_source or get_runtime_setting('SCRAP_SOURCE')) != "themoviedb":
return None
return self.scraper.get_metadata_img(mediainfo=mediainfo, season=season, episode=episode)
@@ -1106,7 +1106,7 @@ class TheMovieDbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
:param mediainfo: 媒体信息
:return: None 表示不处理MediaInfo 表示继续处理
"""
if mediainfo.media_source != "themoviedb" and get_runtime_setting("RECOGNIZE_SOURCE") != "themoviedb":
if mediainfo.media_source != "themoviedb" and get_runtime_setting('RECOGNIZE_SOURCE') != "themoviedb":
return None
if not mediainfo.tmdb_id:
return mediainfo
@@ -1147,15 +1147,15 @@ class TheMovieDbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
# 背景图
if not mediainfo.backdrop_path:
if image_path := cls._pick_best_tmdb_image(images.get("backdrops")):
mediainfo.backdrop_path = get_runtime_setting("TMDB_IMAGE_URL")(image_path)
mediainfo.backdrop_path = get_runtime_setting('TMDB_IMAGE_URL')(image_path)
# 标志
if not mediainfo.logo_path:
if image_path := cls._pick_best_tmdb_image(images.get("logos")):
mediainfo.logo_path = get_runtime_setting("TMDB_IMAGE_URL")(image_path)
mediainfo.logo_path = get_runtime_setting('TMDB_IMAGE_URL')(image_path)
# 海报
if not mediainfo.poster_path:
if image_path := cls._pick_best_tmdb_image(images.get("posters")):
mediainfo.poster_path = get_runtime_setting("TMDB_IMAGE_URL")(image_path)
mediainfo.poster_path = get_runtime_setting('TMDB_IMAGE_URL')(image_path)
return mediainfo
def obtain_images(self, mediainfo: MediaInfo) -> Optional[MediaInfo]:
@@ -1245,7 +1245,7 @@ class TheMovieDbModule(MediaAuxiliaryProviderMixin, _ModuleBase):
image_path = seasoninfo.get(image_type.value)
if image_path:
return get_runtime_setting("TMDB_IMAGE_URL")(image_path, image_prefix)
return get_runtime_setting('TMDB_IMAGE_URL')(image_path, image_prefix)
return None
def tmdb_movie_similar(self, tmdbid: int) -> List[MediaInfo]:
+3 -4
View File
@@ -5,9 +5,8 @@ from typing import Union
import ruamel.yaml
from ruamel.yaml import CommentedMap
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.category import CategoryConfig
from app.foundation.singleton import WeakSingleton
@@ -33,7 +32,7 @@ class CategoryHelper(metaclass=WeakSingleton):
"""
def __init__(self):
self._category_path: Path = settings.CONFIG_PATH / "category.yaml"
self._category_path: Path = get_runtime_setting('CONFIG_PATH') / "category.yaml"
self._categorys = {}
self._movie_categorys = {}
self._tv_categorys = {}
@@ -45,7 +44,7 @@ class CategoryHelper(metaclass=WeakSingleton):
"""
try:
if not self._category_path.exists():
shutil.copy(settings.INNER_CONFIG_PATH / "category.yaml", self._category_path)
shutil.copy(get_runtime_setting('INNER_CONFIG_PATH') / "category.yaml", self._category_path)
with open(self._category_path, mode='r', encoding='utf-8', errors='replace') as f:
try:
yaml_loader = ruamel.yaml.YAML()
+11 -12
View File
@@ -2,9 +2,8 @@ from pathlib import Path
from typing import Optional, Tuple
from xml.dom import minidom
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.context import MediaInfo
from app.domain.meta.metabase import MetaBase
from app.schemas.types import MediaType
@@ -22,14 +21,14 @@ class TmdbScraper:
获取元数据TMDB Api
"""
if not self._meta_tmdb:
self._meta_tmdb = TmdbApi(language=settings.TMDB_LOCALE)
self._meta_tmdb = TmdbApi(language=get_runtime_setting('TMDB_LOCALE'))
return self._meta_tmdb
def original_tmdb(self, mediainfo: Optional[MediaInfo] = None):
"""
获取图片TMDB Api
"""
if settings.TMDB_SCRAP_ORIGINAL_IMAGE and mediainfo:
if get_runtime_setting('TMDB_SCRAP_ORIGINAL_IMAGE') and mediainfo:
return TmdbApi(language=mediainfo.original_language)
return self.default_tmdb
@@ -116,7 +115,7 @@ class TmdbScraper:
# TMDB集still图片
ext = Path(still_path).suffix
still_name = f"episode-thumb{ext}"
still_url = settings.TMDB_IMAGE_URL(still_path)
still_url = get_runtime_setting('TMDB_IMAGE_URL')(still_path)
images[still_name] = still_url
else:
# 季的图片
@@ -144,14 +143,14 @@ class TmdbScraper:
images[image_name] = attr_value
# 替换原语言Poster
if settings.TMDB_SCRAP_ORIGINAL_IMAGE:
if get_runtime_setting('TMDB_SCRAP_ORIGINAL_IMAGE'):
_mediainfo = self.original_tmdb(mediainfo).get_info(
mediainfo.type, mediainfo.tmdb_id
)
if _mediainfo:
for attr_name, attr_value in _mediainfo.items():
if attr_name.endswith("_path") and attr_value is not None:
image_url = settings.TMDB_IMAGE_URL(attr_value)
image_url = get_runtime_setting('TMDB_IMAGE_URL')(attr_value)
image_name = (
attr_name.replace("_path", "") + Path(image_url).suffix
)
@@ -181,11 +180,11 @@ class TmdbScraper:
if not mediainfo.poster_path:
poster_path = self.__pick_best_image_path(image_info.get("posters"))
if poster_path:
mediainfo.poster_path = settings.TMDB_IMAGE_URL(poster_path)
mediainfo.poster_path = get_runtime_setting('TMDB_IMAGE_URL')(poster_path)
if not mediainfo.backdrop_path:
backdrop_path = self.__pick_best_image_path(image_info.get("backdrops"))
if backdrop_path:
mediainfo.backdrop_path = settings.TMDB_IMAGE_URL(backdrop_path)
mediainfo.backdrop_path = get_runtime_setting('TMDB_IMAGE_URL')(backdrop_path)
@staticmethod
def __pick_best_image_path(images: list) -> Optional[str]:
@@ -215,7 +214,7 @@ class TmdbScraper:
# 后缀
ext = Path(poster_path).suffix
# URL
url = settings.TMDB_IMAGE_URL(poster_path)
url = get_runtime_setting('TMDB_IMAGE_URL')(poster_path)
# S0海报格式不同
if season == 0:
image_name = f"season-specials-poster{ext}"
@@ -286,7 +285,7 @@ class TmdbScraper:
DomUtils.add_node(doc, xactor, "tmdbid", actor.get("id") or "")
if profile_path := actor.get("profile_path"):
DomUtils.add_node(
doc, xactor, "thumb", settings.TMDB_IMAGE_URL(profile_path)
doc, xactor, "thumb", get_runtime_setting('TMDB_IMAGE_URL')(profile_path)
)
DomUtils.add_node(
doc,
@@ -453,7 +452,7 @@ class TmdbScraper:
DomUtils.add_node(doc, xactor, "tmdbid", actor.get("id") or "")
if profile_path := actor.get("profile_path"):
DomUtils.add_node(
doc, xactor, "thumb", settings.TMDB_IMAGE_URL(profile_path)
doc, xactor, "thumb", get_runtime_setting('TMDB_IMAGE_URL')(profile_path)
)
DomUtils.add_node(
doc,
+9 -10
View File
@@ -6,9 +6,8 @@ from time import time
from typing import Any, Optional
from app.runtime.cache import FileCache, TTLCache
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.domain.meta.metabase import MetaBase
from app.runtime.log import logger
from app.schemas.types import MediaSource, MediaType
@@ -32,8 +31,8 @@ class TmdbCache(metaclass=WeakSingleton):
"""
def __init__(self):
"""初始化 TMDB 识别缓存并恢复未过期的持久化数据。"""
self.maxsize = settings.CONF.tmdb
self.ttl = settings.CONF.meta
self.maxsize = get_runtime_setting('CONF').tmdb
self.ttl = get_runtime_setting('CONF').meta
self.region = "__tmdb_cache__"
self._cache = TTLCache(region=self.region, maxsize=self.maxsize, ttl=self.ttl)
self._expires_at: dict[str, float] = {}
@@ -42,8 +41,8 @@ class TmdbCache(metaclass=WeakSingleton):
self._legacy_file_cache = None
self._legacy_cache_found = False
if not self._cache.is_redis():
self._file_cache = FileCache(base=settings.CACHE_PATH, ttl=self.ttl)
self._legacy_file_cache = FileCache(base=settings.TEMP_PATH.parent, ttl=self.ttl)
self._file_cache = FileCache(base=get_runtime_setting('CACHE_PATH'), ttl=self.ttl)
self._legacy_file_cache = FileCache(base=get_runtime_setting('TEMP_PATH').parent, ttl=self.ttl)
self._restore()
def _restore(self) -> None:
@@ -53,7 +52,7 @@ class TmdbCache(metaclass=WeakSingleton):
if not content:
content = self._legacy_file_cache.get(
self.region,
region=settings.TEMP_PATH.name,
region=get_runtime_setting('TEMP_PATH').name,
)
if content:
self._legacy_cache_found = True
@@ -146,7 +145,7 @@ class TmdbCache(metaclass=WeakSingleton):
获取缓存KEY
"""
media_id = meta.media_id if meta.media_source == MediaSource.TMDB else None
return f"[{meta.type.value if meta.type else '未知'}][{settings.TMDB_LOCALE}]{media_id or meta.name}-{meta.year}-{meta.begin_season}"
return f"[{meta.type.value if meta.type else '未知'}][{get_runtime_setting('TMDB_LOCALE')}]{media_id or meta.name}-{meta.year}-{meta.begin_season}"
@staticmethod
def __is_type_conflicted(meta: MetaBase, media_type: Any, tmdb_id: Any) -> bool:
@@ -263,7 +262,7 @@ class TmdbCache(metaclass=WeakSingleton):
# 负识别缓存使用独立的短 TTL:故障期间「合法 JSON 但结果为空」会被
# 记为未识别,若按完整有效期固化,故障自愈后同名仍会被判无法识别;
# 短过期让恢复后可重新识别,真不存在的条目过期后重新确认一次即可
self._set(key, {"id": 0}, ttl=settings.EMPTY_RESULT_CACHE_TTL)
self._set(key, {"id": 0}, ttl=get_runtime_setting('EMPTY_RESULT_CACHE_TTL'))
def save(self, force: bool = False) -> None:
"""
@@ -314,7 +313,7 @@ class TmdbCache(metaclass=WeakSingleton):
if self._legacy_cache_found:
self._legacy_file_cache.delete(
self.region,
region=settings.TEMP_PATH.name,
region=get_runtime_setting('TEMP_PATH').name,
)
self._legacy_cache_found = False
self._dirty = False
+2 -3
View File
@@ -2,9 +2,8 @@ import re
import traceback
from typing import Optional, List
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.schemas.types import MediaType
from app.foundation import text as text_tools
@@ -1279,7 +1278,7 @@ class TmdbApi:
"""
languages = []
for language in (
settings.TMDB_LOCALE,
get_runtime_setting('TMDB_LOCALE'),
"en",
None,
original_language,
@@ -1,7 +1,6 @@
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from ..tmdb import TMDb
try:
@@ -16,7 +15,7 @@ class Discover(TMDb):
"tv": "/discover/tv"
}
@cached(maxsize=1, ttl=43200, empty_ttl=settings.EMPTY_RESULT_CACHE_TTL)
@cached(maxsize=1, ttl=43200, empty_ttl=get_runtime_setting('EMPTY_RESULT_CACHE_TTL'))
def discover_movies(self, params_tuple):
"""
Discover movies by different types of data like average rating, number of votes, genres and certifications.
@@ -26,7 +25,7 @@ class Discover(TMDb):
params = dict(params_tuple)
return self._request_obj(self._urls["movies"], urlencode(params), key="results", call_cached=False)
@cached(maxsize=1, ttl=43200, empty_ttl=settings.EMPTY_RESULT_CACHE_TTL)
@cached(maxsize=1, ttl=43200, empty_ttl=get_runtime_setting('EMPTY_RESULT_CACHE_TTL'))
def discover_tv_shows(self, params_tuple):
"""
Discover TV shows by different types of data like average rating, number of votes, genres,
@@ -36,7 +35,7 @@ class Discover(TMDb):
"""
return self._request_obj(self._urls["tv"], urlencode(params_tuple), key="results", call_cached=False)
@cached(maxsize=1, ttl=43200, empty_ttl=settings.EMPTY_RESULT_CACHE_TTL)
@cached(maxsize=1, ttl=43200, empty_ttl=get_runtime_setting('EMPTY_RESULT_CACHE_TTL'))
async def async_discover_movies(self, params_tuple):
"""
Discover movies by different types of data like average rating, number of votes, genres and certifications.异步版本
@@ -46,7 +45,7 @@ class Discover(TMDb):
params = dict(params_tuple)
return await self._async_request_obj(self._urls["movies"], urlencode(params), key="results", call_cached=False)
@cached(maxsize=1, ttl=43200, empty_ttl=settings.EMPTY_RESULT_CACHE_TTL)
@cached(maxsize=1, ttl=43200, empty_ttl=get_runtime_setting('EMPTY_RESULT_CACHE_TTL'))
async def async_discover_tv_shows(self, params_tuple):
"""
Discover TV shows by different types of data like average rating, number of votes, genres,
+12 -13
View File
@@ -10,9 +10,8 @@ import requests
import requests.exceptions
from app.runtime.cache import cached, fresh, async_fresh
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.adapters.network.http import RequestUtils, AsyncRequestUtils
from .exceptions import TMDbException, TMDbConnectionError
@@ -44,7 +43,7 @@ def _is_empty_result_snapshot(snapshot) -> bool:
判断响应快照是否为空结果列表/搜索类接口的 results 为空列表
这类快照结构合法但无业务内容常由代理瞬时故障产生不能靠 skip_none/skip_empty
识别快照本身是非空字典需单独谓词判定后按 settings.EMPTY_RESULT_CACHE_TTL
识别快照本身是非空字典需单独谓词判定后按 get_runtime_setting('EMPTY_RESULT_CACHE_TTL')
TTL 缓存详情类接口无 results 字段不属于空结果
"""
if not isinstance(snapshot, dict):
@@ -60,13 +59,13 @@ class TMDb(object):
_RESPONSE_SNAPSHOT_MARKER = "__mp_tmdb_response_snapshot__"
def __init__(self, session=None, language=None):
self._api_key = settings.TMDB_API_KEY
self._language = language or settings.TMDB_LOCALE or "en-US"
self._api_key = get_runtime_setting('TMDB_API_KEY')
self._language = language or get_runtime_setting('TMDB_LOCALE') or "en-US"
self._session_id = None
self._session = session
self._wait_on_rate_limit = True
self._proxies = settings.PROXY
self._domain = settings.TMDB_API_DOMAIN
self._proxies = get_runtime_setting('PROXY')
self._domain = get_runtime_setting('TMDB_API_DOMAIN')
self._page = None
self._total_results = None
self._total_pages = None
@@ -76,7 +75,7 @@ class TMDb(object):
# TMDB 在部分代理和运营商链路下的 HTTP/2 长连接偶发卡死,识别路径优先保证稳定性。
self._async_req = AsyncRequestUtils(
ua=settings.NORMAL_USER_AGENT,
ua=get_runtime_setting('NORMAL_USER_AGENT'),
proxies=self.proxies,
http2=False,
)
@@ -91,7 +90,7 @@ class TMDb(object):
"""
self._session = session or requests.Session()
self._req = RequestUtils(
ua=settings.NORMAL_USER_AGENT,
ua=get_runtime_setting('NORMAL_USER_AGENT'),
session=self._session,
proxies=self.proxies,
)
@@ -175,9 +174,9 @@ class TMDb(object):
def wait_on_rate_limit(self, wait_on_rate_limit):
self._wait_on_rate_limit = bool(wait_on_rate_limit)
@cached(maxsize=settings.CONF.tmdb, ttl=settings.CONF.meta, skip_none=True,
@cached(maxsize=get_runtime_setting('CONF').tmdb, ttl=get_runtime_setting('CONF').meta, skip_none=True,
skip_if=_is_business_failure_snapshot,
empty_ttl=settings.EMPTY_RESULT_CACHE_TTL, empty_if=_is_empty_result_snapshot)
empty_ttl=get_runtime_setting('EMPTY_RESULT_CACHE_TTL'), empty_if=_is_empty_result_snapshot)
def request(self, method, url, data, json, **kwargs):
req = self._request_once(method, url, data, json)
if req is None and method == "GET" and self._owns_session:
@@ -201,9 +200,9 @@ class TMDb(object):
return self._req.get_res(url, params=data, json=json)
return self._req.post_res(url, data=data, json=json)
@cached(maxsize=settings.CONF.tmdb, ttl=settings.CONF.meta, skip_none=True,
@cached(maxsize=get_runtime_setting('CONF').tmdb, ttl=get_runtime_setting('CONF').meta, skip_none=True,
skip_if=_is_business_failure_snapshot,
empty_ttl=settings.EMPTY_RESULT_CACHE_TTL, empty_if=_is_empty_result_snapshot)
empty_ttl=get_runtime_setting('EMPTY_RESULT_CACHE_TTL'), empty_if=_is_empty_result_snapshot)
async def async_request(self, method, url, data, json, **kwargs):
req = await self._async_request_once(method, url, data, json)
if req is None:
+4 -4
View File
@@ -38,11 +38,11 @@ class TheTvDbModule(_ModuleBase):
action = "刷新" if is_retry else "创建"
logger.info(f"开始{action}TVDB登录会话...")
try:
if not get_runtime_setting("TVDB_V4_API_KEY"):
if not get_runtime_setting('TVDB_V4_API_KEY'):
raise ConnectionError("TVDB API Key 未配置,无法初始化会话。")
self.tvdb = tvdb_v4_official.TVDB(apikey=get_runtime_setting("TVDB_V4_API_KEY"),
pin=get_runtime_setting("TVDB_V4_API_PIN"),
proxy=get_runtime_setting("PROXY"),
self.tvdb = tvdb_v4_official.TVDB(apikey=get_runtime_setting('TVDB_V4_API_KEY'),
pin=get_runtime_setting('TVDB_V4_API_PIN'),
proxy=get_runtime_setting('PROXY'),
timeout=self.__timeout)
if self.tvdb:
logger.info(f"TVDB登录会话{action}成功。")
+3 -5
View File
@@ -7,11 +7,9 @@ import json
import urllib.parse
from http import HTTPStatus
from app.runtime.cache import cached
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.adapters.network.http import RequestUtils
from app.runtime.cache import cached
from app.runtime.settings import get_runtime_setting
class Auth:
@@ -69,7 +67,7 @@ class Request:
self.proxy = proxy
self.timeout = timeout
@cached(maxsize=settings.CONF.tmdb, ttl=settings.CONF.meta, skip_none=True)
@cached(maxsize=get_runtime_setting('CONF').tmdb, ttl=get_runtime_setting('CONF').meta, skip_none=True)
def make_request(self, url: str, if_modified_since: bool = None):
"""
向指定的 URL 发起请求并返回数据
+8 -9
View File
@@ -4,7 +4,7 @@ from typing import Set, Tuple, Optional, Union, List, Dict
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.modules._base import _DownloaderModuleBase
from app.modules.transmission.transmission import Transmission
from app.schemas.transfer import DownloaderFile, DownloaderTorrent
@@ -18,7 +18,6 @@ from app.schemas.types import (
from app.foundation import size as size_tools
from app.foundation import temporal as time_tools
settings = RuntimeSettingsCompat()
_TRANSMISSION_DOWNLOADING_STATES = {
"download_pending",
@@ -111,8 +110,8 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]):
# 标签
if label:
labels = label.split(',')
elif settings.TORRENT_TAG:
labels = settings.TORRENT_TAG.split(',')
elif get_runtime_setting('TORRENT_TAG'):
labels = get_runtime_setting('TORRENT_TAG').split(',')
else:
labels = None
# 添加任务
@@ -139,16 +138,16 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]):
torrent_hash = torrent.hashString
logger.warn(f"下载器中已存在该种子任务:{torrent_hash} - {torrent.name}")
# 给种子打上标签
if settings.TORRENT_TAG:
logger.info(f"给种子 {torrent_hash} 打上标签:{settings.TORRENT_TAG}")
if get_runtime_setting('TORRENT_TAG'):
logger.info(f"给种子 {torrent_hash} 打上标签:{get_runtime_setting('TORRENT_TAG')}")
# 种子标签
labels = [str(tag).strip()
for tag in torrent.labels] if hasattr(torrent, "labels") else []
if "已整理" in labels:
labels.remove("已整理")
server.set_torrent_tag(ids=torrent_hash, tags=labels)
if settings.TORRENT_TAG and settings.TORRENT_TAG not in labels:
labels.append(settings.TORRENT_TAG)
if get_runtime_setting('TORRENT_TAG') and get_runtime_setting('TORRENT_TAG') not in labels:
labels.append(get_runtime_setting('TORRENT_TAG'))
server.set_torrent_tag(ids=torrent_hash, tags=labels)
return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, f"下载任务已存在"
finally:
@@ -213,7 +212,7 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]):
servers: Dict[str, Transmission] = self.get_instances()
ret_torrents = []
query_status = self._normalize_query_status(status)
query_tags = None if include_all_tags else settings.TORRENT_TAG
query_tags = None if include_all_tags else get_runtime_setting('TORRENT_TAG')
def __get_torrent_attr(torrent_data, *attr_names):
"""
+2 -3
View File
@@ -7,9 +7,8 @@ from enum import Enum
from typing import List, Optional, Union
from urllib.parse import quote
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.adapters.network.http import RequestUtils, requests
@@ -583,7 +582,7 @@ class Api:
else:
queries_unquoted = None
headers = {
"User-Agent": settings.USER_AGENT,
"User-Agent": get_runtime_setting('USER_AGENT'),
"Accept": "application/json",
"Referer": self._host,
"Authorization": self._token,
+3 -4
View File
@@ -4,9 +4,8 @@ from typing import Union, Tuple
from pywebpush import webpush, WebPushException
from app.runtime.config import global_vars
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.modules import _ModuleBase, _MessageBase
from app.schemas.message import Message
@@ -103,9 +102,9 @@ class WebPushModule(_ModuleBase, _MessageBase):
"body": content,
"url": message.link or "/?shotcut=message"
}),
vapid_private_key=settings.VAPID.get("privateKey"),
vapid_private_key=get_runtime_setting('VAPID').get("privateKey"),
vapid_claims={
"sub": settings.VAPID.get("subject")
"sub": get_runtime_setting('VAPID').get("subject")
},
**webpush_options,
)
+8 -11
View File
@@ -1,3 +1,4 @@
import base64
import hashlib
import json
import pickle
@@ -5,26 +6,22 @@ import re
import threading
import time
import uuid
import base64
from typing import Optional, List, Dict, Tuple, Set
from typing import Dict, List, Optional, Set, Tuple
import websocket
from Crypto.Cipher import AES
from app.runtime.cache import FileCache
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.application.messaging.ingress import submit_message_to_host
from app.domain.context import MediaInfo, Context
from app.domain.metainfo import MetaInfo
from app.adapters.network.http import RequestUtils
from app.application.messaging.agent import matches_channel_admin
from app.application.messaging.ingress import submit_message_to_host
from app.domain.context import Context, MediaInfo
from app.domain.metainfo import MetaInfo
from app.foundation import size as size_tools
from app.runtime.cache import FileCache
from app.runtime.log import logger
from app.runtime.thread import ThreadHelper
from app.schemas.message import IncomingMessage
from app.schemas.types import NotificationChannel
from app.adapters.network.http import RequestUtils
from app.foundation import size as size_tools
class WeChatBot:
+4 -5
View File
@@ -17,9 +17,8 @@ from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from app.runtime.cache import FileCache
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
settings = RuntimeSettingsCompat()
from app.application.messaging.ingress import forward_message_to_host
from app.domain.context import Context, MediaInfo
from app.domain.metainfo import MetaInfo
@@ -1686,14 +1685,14 @@ class WechatClawBot:
except Exception:
return None
if image_url.startswith("/"):
image_url = settings.MP_DOMAIN(image_url)
image_url = get_runtime_setting('MP_DOMAIN')(image_url)
if not image_url.lower().startswith("http"):
return None
try:
resp = RequestUtils(
timeout=20,
proxies=settings.PROXY,
ua=settings.USER_AGENT,
proxies=get_runtime_setting('PROXY'),
ua=get_runtime_setting('USER_AGENT'),
).get_res(image_url)
if resp and resp.status_code == 200 and resp.content:
content_type = (resp.headers.get("Content-Type") or "").lower()