diff --git a/.gitignore b/.gitignore index c0db26b..4770bab 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ wheels/ # Virtual environments .venv venv/ +# Secrets +src/secrets.py # Test-generated files src/test/datasets/sample_mediafiles/ .pytest_cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md index cd01c47..2ba82e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **GitHub Actions CI** (`ci.yml`): runs tests + mypy on Python 3.11 and 3.12 for every push to `main` and all PRs -- **GitHub Actions Release** (`release.yml`): triggered by `v*.*.*` tags — runs tests, builds wheel + tarball, publishes packages as GitHub Release assets +- **GitHub Actions Release** (`release.yml`): triggered by `v*.*.*` tags — runs tests, builds wheel + tarball, publishes packages as GitHub Release assets (`moma-X.Y.Z.whl`, `moma-latest.whl`, etc.) +- **TMDB credentials in Settings**: `tmdb_api_key` and `tmdb_access_token` are now stored in `~/.config/moma/config.json` and editable via the Settings screen (`p`) + +### Changed +- Removed hardcoded TMDB API keys from `src/secrets.py`; `secrets.py` is now gitignored ### Future Plans See [docs/REFACTORING_PROGRESS.md](docs/REFACTORING_PROGRESS.md) and [docs/ToDo.md](docs/ToDo.md) for upcoming features and improvements. diff --git a/src/extractors/tmdb_extractor.py b/src/extractors/tmdb_extractor.py index d31613f..3fd72bf 100644 --- a/src/extractors/tmdb_extractor.py +++ b/src/extractors/tmdb_extractor.py @@ -6,7 +6,6 @@ import requests import logging from pathlib import Path from typing import Dict, Optional, Tuple, Any -from ..secrets import TMDB_API_KEY, TMDB_ACCESS_TOKEN from ..cache import Cache from ..settings import Settings @@ -38,14 +37,23 @@ class TMDBExtractor: base_url = "https://api.themoviedb.org/3" url = f"{base_url}{endpoint}" + api_key = self.settings.get("tmdb_api_key", "") + access_token = self.settings.get("tmdb_access_token", "") + + if not api_key and not access_token: + logging.warning("TMDB API key and access token are not configured") + return None + headers = { - "Authorization": f"Bearer {TMDB_ACCESS_TOKEN}", "accept": "application/json" } - + if access_token: + headers["Authorization"] = f"Bearer {access_token}" + if params is None: params = {} - params['api_key'] = TMDB_API_KEY + if api_key: + params['api_key'] = api_key try: response = requests.get(url, headers=headers, params=params, timeout=10) diff --git a/src/settings.py b/src/settings.py index 9464341..3415d6c 100644 --- a/src/settings.py +++ b/src/settings.py @@ -16,6 +16,8 @@ class Settings: "cache_ttl_extractors": 21600, # 6 hours in seconds "cache_ttl_tmdb": 21600, # 6 hours in seconds "cache_ttl_posters": 2592000, # 30 days in seconds + "tmdb_api_key": "", # TMDB API key + "tmdb_access_token": "", # TMDB Bearer access token } _instance: Optional['Settings'] = None @@ -80,12 +82,15 @@ class Settings: def set(self, key: str, value: Any) -> None: """Set a setting value and save.""" if key in self.DEFAULTS: - # Basic type checking - if isinstance(value, type(self.DEFAULTS[key])): - self._settings[key] = value - self.save() - else: + # Basic type checking (both empty string and non-empty string are valid for str defaults) + default = self.DEFAULTS[key] + if isinstance(default, bool): + if not isinstance(value, bool): + raise ValueError(f"Invalid type for setting {key}") + elif not isinstance(value, type(default)): raise ValueError(f"Invalid type for setting {key}") + self._settings[key] = value + self.save() else: raise KeyError(f"Unknown setting: {key}") diff --git a/src/views/settings_screen.py b/src/views/settings_screen.py index 613a88f..451ee35 100644 --- a/src/views/settings_screen.py +++ b/src/views/settings_screen.py @@ -78,6 +78,12 @@ Configure application settings. yield Static("Cache TTL - Posters (days):", classes="label") yield Input(value=str(settings.get("cache_ttl_posters") // 86400), id="ttl_posters", classes="input_field") + yield Static("TMDB API Key:", classes="label") + yield Input(value=settings.get("tmdb_api_key", ""), id="tmdb_api_key", password=False, classes="input_field") + + yield Static("TMDB Access Token (Bearer):", classes="label") + yield Input(value=settings.get("tmdb_access_token", ""), id="tmdb_access_token", password=True, classes="input_field") + with Horizontal(id="buttons"): yield Button("Save", id="save") yield Button("Cancel", id="cancel") @@ -146,6 +152,11 @@ Configure application settings. self.app.settings.set("cache_ttl_tmdb", ttl_tmdb) # type: ignore self.app.settings.set("cache_ttl_posters", ttl_posters) # type: ignore + tmdb_api_key = self.query_one("#tmdb_api_key", Input).value.strip() + tmdb_access_token = self.query_one("#tmdb_access_token", Input).value.strip() + self.app.settings.set("tmdb_api_key", tmdb_api_key) # type: ignore + self.app.settings.set("tmdb_access_token", tmdb_access_token) # type: ignore + self.app.notify("Settings saved!", severity="information", timeout=2) # type: ignore except ValueError: self.app.notify("Invalid TTL values. Please enter numbers only.", severity="error", timeout=3) # type: ignore