Add configurable folder rename patterns via settings with anime_folder_rename_regex and custom_pattern options. Integrate into SerieScanner and SeriesApp for consistent episode organization.
192 lines
6.7 KiB
Python
192 lines
6.7 KiB
Python
import re
|
|
import secrets
|
|
from typing import Optional
|
|
|
|
from pydantic import Field
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Application settings from environment variables."""
|
|
|
|
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
|
|
|
jwt_secret_key: str = Field(
|
|
default_factory=lambda: secrets.token_urlsafe(32),
|
|
validation_alias="JWT_SECRET_KEY",
|
|
)
|
|
password_salt: str = Field(
|
|
default="default-salt",
|
|
validation_alias="PASSWORD_SALT"
|
|
)
|
|
master_password_hash: Optional[str] = Field(
|
|
default=None,
|
|
validation_alias="MASTER_PASSWORD_HASH"
|
|
)
|
|
# ⚠️ WARNING: DEVELOPMENT ONLY - NEVER USE IN PRODUCTION ⚠️
|
|
# This field allows setting a plaintext master password via environment
|
|
# variable for development/testing purposes only. In production
|
|
# deployments, use MASTER_PASSWORD_HASH instead and NEVER set this field.
|
|
master_password: Optional[str] = Field(
|
|
default=None,
|
|
validation_alias="MASTER_PASSWORD",
|
|
description=(
|
|
"**DEVELOPMENT ONLY** - Plaintext master password. "
|
|
"NEVER enable in production. Use MASTER_PASSWORD_HASH instead."
|
|
),
|
|
)
|
|
token_expiry_hours: int = Field(
|
|
default=24,
|
|
validation_alias="SESSION_TIMEOUT_HOURS"
|
|
)
|
|
anime_directory: str = Field(
|
|
default="",
|
|
validation_alias="ANIME_DIRECTORY"
|
|
)
|
|
log_level: str = Field(
|
|
default="INFO",
|
|
validation_alias="LOG_LEVEL"
|
|
)
|
|
|
|
# Additional settings from .env
|
|
database_url: str = Field(
|
|
default="sqlite:///./data/aniworld.db",
|
|
validation_alias="DATABASE_URL"
|
|
)
|
|
cors_origins: str = Field(
|
|
default="http://localhost:3000",
|
|
validation_alias="CORS_ORIGINS",
|
|
)
|
|
api_rate_limit: int = Field(
|
|
default=100,
|
|
validation_alias="API_RATE_LIMIT"
|
|
)
|
|
default_provider: str = Field(
|
|
default="aniworld.to",
|
|
validation_alias="DEFAULT_PROVIDER"
|
|
)
|
|
provider_timeout: int = Field(
|
|
default=30,
|
|
validation_alias="PROVIDER_TIMEOUT"
|
|
)
|
|
retry_attempts: int = Field(
|
|
default=3,
|
|
validation_alias="RETRY_ATTEMPTS"
|
|
)
|
|
|
|
# NFO / TMDB Settings
|
|
tmdb_api_key: Optional[str] = Field(
|
|
default=None,
|
|
validation_alias="TMDB_API_KEY",
|
|
description="TMDB API key for scraping TV show metadata"
|
|
)
|
|
nfo_auto_create: bool = Field(
|
|
default=False,
|
|
validation_alias="NFO_AUTO_CREATE",
|
|
description="Automatically create NFO files when scanning series"
|
|
)
|
|
nfo_update_on_scan: bool = Field(
|
|
default=False,
|
|
validation_alias="NFO_UPDATE_ON_SCAN",
|
|
description="Update existing NFO files when scanning series"
|
|
)
|
|
nfo_download_poster: bool = Field(
|
|
default=True,
|
|
validation_alias="NFO_DOWNLOAD_POSTER",
|
|
description="Download poster.jpg when creating NFO"
|
|
)
|
|
nfo_download_logo: bool = Field(
|
|
default=True,
|
|
validation_alias="NFO_DOWNLOAD_LOGO",
|
|
description="Download logo.png when creating NFO"
|
|
)
|
|
nfo_download_fanart: bool = Field(
|
|
default=True,
|
|
validation_alias="NFO_DOWNLOAD_FANART",
|
|
description="Download fanart.jpg when creating NFO"
|
|
)
|
|
nfo_image_size: str = Field(
|
|
default="original",
|
|
validation_alias="NFO_IMAGE_SIZE",
|
|
description="Image size to download (original, w500, etc.)"
|
|
)
|
|
nfo_prefer_fsk_rating: bool = Field(
|
|
default=True,
|
|
validation_alias="NFO_PREFER_FSK_RATING",
|
|
description="Prefer German FSK rating over MPAA rating in NFO files"
|
|
)
|
|
nfo_folder_ignore_patterns: str = Field(
|
|
default="The Last of Us|Loki|Chernobyl|Star Trek Discovery|Marvel|Matrix|Fast & Furious|Jurassic|James Bond|Mission: Impossible|Bourne|Hunger Games|Die Hard|John Wick|Pacific Rim|Guardians of the Galaxy|Avengers|Batman|Superman|Wonder Woman|Spider-Man|X-Men|Fantastic Four|Terminator|Predator|Rambo|Rocky|Expendables|Tomb Raider|Jumanji|Jurassic Park|Pirates of the Caribbean|Harry Potter|Lord of the Rings|Hobbit|Game of Thrones|Westworld|Stranger Things|Breaking Bad|Better Call Saul|Sherlock|Downton Abbey|The Crown|Bridgerton|Sex Education|Normal People|Emily in Paris|The Witcher|Servant|Lucifer|Dark|Shadow and Bone|Grimm|Fairytale",
|
|
validation_alias="NFO_FOLDER_IGNORE_PATTERNS",
|
|
description="Regex patterns for folder names to skip during scan (pipe-separated)"
|
|
)
|
|
|
|
@property
|
|
def folder_ignore_patterns(self) -> list[str]:
|
|
"""Parse ignore patterns from comma-separated string into list.
|
|
|
|
Returns:
|
|
List of regex patterns to skip during folder scanning.
|
|
"""
|
|
if not self.nfo_folder_ignore_patterns:
|
|
return []
|
|
return [
|
|
pattern.strip()
|
|
for pattern in self.nfo_folder_ignore_patterns.split("|")
|
|
if pattern.strip()
|
|
]
|
|
|
|
def should_ignore_folder(self, folder_name: str) -> bool:
|
|
"""Check if folder should be ignored based on ignore patterns.
|
|
|
|
Args:
|
|
folder_name: Name of folder to check.
|
|
|
|
Returns:
|
|
True if folder matches any ignore pattern, False otherwise.
|
|
"""
|
|
for pattern in self.folder_ignore_patterns:
|
|
if re.search(pattern, folder_name, re.IGNORECASE):
|
|
return True
|
|
return False
|
|
|
|
@property
|
|
def allowed_origins(self) -> list[str]:
|
|
"""Return the list of allowed CORS origins.
|
|
|
|
The environment variable should contain a comma-separated list.
|
|
When ``*`` is provided we fall back to a safe local development
|
|
default instead of allowing every origin in production.
|
|
"""
|
|
|
|
raw = (self.cors_origins or "").strip()
|
|
if not raw:
|
|
return []
|
|
if raw == "*":
|
|
return [
|
|
"http://localhost:3000",
|
|
"http://localhost:8000",
|
|
]
|
|
return [origin.strip() for origin in raw.split(",") if origin.strip()]
|
|
|
|
@property
|
|
def scan_key_overrides(self) -> dict[str, str]:
|
|
"""Return scan key overrides from config.json.
|
|
|
|
Maps folder names to provider keys for cases where auto-generated
|
|
keys from folder names are incorrect.
|
|
|
|
Returns:
|
|
Dict mapping folder names to provider keys.
|
|
"""
|
|
from src.server.services.config_service import ConfigService
|
|
try:
|
|
config_service = ConfigService()
|
|
config = config_service.load_config()
|
|
return config.scan_key_overrides or {}
|
|
except Exception:
|
|
return {}
|
|
|
|
|
|
settings = Settings()
|