refactor: lazy load credentials for testing

This commit is contained in:
2026-04-06 07:20:23 +02:00
parent a8335d9920
commit 9b04160783
8 changed files with 110 additions and 19 deletions
+3 -3
View File
@@ -15,8 +15,8 @@ from ..cache import CacheEngine
from ..config import (
HTTP_TIMEOUT,
MUSIXMATCH_TOKEN_URL,
MUSIXMATCH_USERTOKEN,
MUSIXMATCH_COOLDOWN_MS,
credentials,
)
_MXM_HEADERS = {"Cookie": "x-mxm-token-guid="}
@@ -105,8 +105,8 @@ class MusixmatchAuthenticator(BaseAuthenticator):
async def _get_token(self) -> Optional[str]:
"""Return a valid token: env var > memory > DB > fresh fetch."""
if MUSIXMATCH_USERTOKEN:
return MUSIXMATCH_USERTOKEN
if credentials.MUSIXMATCH_USERTOKEN:
return credentials.MUSIXMATCH_USERTOKEN
if self._cached_token:
return self._cached_token
+3 -3
View File
@@ -7,7 +7,7 @@ Description: QQ Music API authenticator - currently only a proxy
from typing import Optional
from .base import BaseAuthenticator
from ..config import QQ_MUSIC_API_URL
from ..config import credentials
class QQMusicAuthenticator(BaseAuthenticator):
@@ -19,7 +19,7 @@ class QQMusicAuthenticator(BaseAuthenticator):
return "qqmusic"
def is_configured(self) -> bool:
return bool(QQ_MUSIC_API_URL)
return bool(credentials.QQ_MUSIC_API_URL)
async def authenticate(self) -> Optional[str]:
return QQ_MUSIC_API_URL
return credentials.QQ_MUSIC_API_URL
+7 -5
View File
@@ -18,9 +18,9 @@ from ..config import (
HTTP_TIMEOUT,
SPOTIFY_SERVER_TIME_URL,
SPOTIFY_SECRET_URL,
SPOTIFY_SP_DC,
SPOTIFY_TOKEN_URL,
UA_BROWSER,
credentials,
)
_SPOTIFY_BASE_HEADERS = {
@@ -43,7 +43,7 @@ class SpotifyAuthenticator(BaseAuthenticator):
return "spotify"
def is_configured(self) -> bool:
return bool(SPOTIFY_SP_DC)
return bool(credentials.SPOTIFY_SP_DC)
@staticmethod
def _generate_totp(server_time_s: int, secret: str) -> str:
@@ -133,14 +133,16 @@ class SpotifyAuthenticator(BaseAuthenticator):
if db_token and time.time() < self._token_expires_at - 30:
return db_token
if not SPOTIFY_SP_DC:
logger.error("Spotify: SPOTIFY_SP_DC env var not set — cannot authenticate")
if not credentials.SPOTIFY_SP_DC:
logger.error(
"Spotify: settings.SPOTIFY_SP_DC env var not set — cannot authenticate"
)
return None
headers = {
"User-Agent": UA_BROWSER,
"Accept": "*/*",
"Cookie": f"sp_dc={SPOTIFY_SP_DC}",
"Cookie": f"sp_dc={credentials.SPOTIFY_SP_DC}",
**_SPOTIFY_BASE_HEADERS,
}
+28 -5
View File
@@ -65,21 +65,20 @@ SPOTIFY_SECRET_URL = (
"https://raw.githubusercontent.com/xyloflake/spot-secrets-go"
"/refs/heads/main/secrets/secrets.json"
)
SPOTIFY_SP_DC = os.environ.get("SPOTIFY_SP_DC", "")
# Netease api
NETEASE_SEARCH_URL = "https://music.163.com/api/cloudsearch/pc"
NETEASE_LYRIC_URL = "https://interface3.music.163.com/api/song/lyric"
# QQ api endpoints
QQ_MUSIC_API_SEARCH_ENDPOINT = "/api/search"
QQ_MUSIC_API_LYRIC_ENDPOINT = "/api/lyric"
# LRCLIB api
LRCLIB_API_URL = "https://lrclib.net/api/get"
LRCLIB_SEARCH_URL = "https://lrclib.net/api/search"
# QQ Music API (self-hosted proxy)
QQ_MUSIC_API_URL = os.environ.get("QQ_MUSIC_API_URL", "").rstrip("/")
# Musixmatch desktop API
MUSIXMATCH_USERTOKEN = os.environ.get("MUSIXMATCH_USERTOKEN", "")
MUSIXMATCH_TOKEN_URL = "https://apic-desktop.musixmatch.com/ws/1.1/token.get"
MUSIXMATCH_SEARCH_URL = "https://apic-desktop.musixmatch.com/ws/1.1/track.search"
MUSIXMATCH_MACRO_URL = "https://apic-desktop.musixmatch.com/ws/1.1/macro.subtitles.get"
@@ -91,6 +90,30 @@ MUSIXMATCH_COOLDOWN_MS = 600_000 # 10 minutes
# Player preference (used when multiple MPRIS players are active)
PREFERRED_PLAYER = os.environ.get("PREFERRED_PLAYER", "spotify")
class _Credentials:
"""Credential config with lazy os.environ reads.
Stable constants live as module-level names above.
Credentials are @property so monkeypatch.setenv / monkeypatch.delenv
affect them without needing to patch each consumer separately.
"""
@property
def SPOTIFY_SP_DC(self) -> str:
return os.environ.get("SPOTIFY_SP_DC", "")
@property
def QQ_MUSIC_API_URL(self) -> str:
return os.environ.get("QQ_MUSIC_API_URL", "").rstrip("/")
@property
def MUSIXMATCH_USERTOKEN(self) -> str:
return os.environ.get("MUSIXMATCH_USERTOKEN", "")
credentials = _Credentials()
# User-Agents
UA_BROWSER = "Mozilla/5.0 (X11; Linux x86_64; rv:149.0) Gecko/20100101 Firefox/149.0"
UA_LRX = f"LRX-CLI {APP_VERSION} (https://github.com/Uyanide/lrx-cli)"
+4 -2
View File
@@ -25,6 +25,8 @@ from ..config import (
TTL_NOT_FOUND,
TTL_NETWORK_ERROR,
MULTI_CANDIDATE_DELAY_S,
QQ_MUSIC_API_LYRIC_ENDPOINT,
QQ_MUSIC_API_SEARCH_ENDPOINT,
)
from ..authenticators import QQMusicAuthenticator
@@ -52,7 +54,7 @@ class QQMusicFetcher(BaseFetcher):
try:
async with httpx.AsyncClient(timeout=HTTP_TIMEOUT) as client:
resp = await client.get(
f"{await self.auth.authenticate()}/api/search",
f"{await self.auth.authenticate()}{QQ_MUSIC_API_SEARCH_ENDPOINT}",
params={"keyword": query, "type": "song", "num": limit},
)
resp.raise_for_status()
@@ -111,7 +113,7 @@ class QQMusicFetcher(BaseFetcher):
try:
async with httpx.AsyncClient(timeout=HTTP_TIMEOUT) as client:
resp = await client.get(
f"{await self.auth.authenticate()}/api/lyric",
f"{await self.auth.authenticate()}{QQ_MUSIC_API_LYRIC_ENDPOINT}",
params={"mid": mid},
)
resp.raise_for_status()