Skip to content

Commit f72b58b

Browse files
feat: update catalogs on user request with better mechanism (#70)
1 parent 487121e commit f72b58b

File tree

10 files changed

+218
-276
lines changed

10 files changed

+218
-276
lines changed

.env.example

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
TMDB_API_KEY=<your_tmdb_api_key>
2-
PORT=8000
3-
ADDON_ID=com.bimal.watchly
4-
ADDON_NAME=Watchly
52
REDIS_URL=redis://redis:6379/0
6-
TOKEN_TTL_SECONDS=0
7-
ANNOUNCEMENT_HTML=
3+
TOKEN_SALT=change-me # generate secure salt...
4+
5+
# app setup
6+
APP_ENV="development" # available values are ["development", "production"]
87
HOST_NAME=<your_addon_url>
9-
RECOMMENDATION_SOURCE_ITEMS_LIMIT=10 # fetches recent watched/loved 10 movies and series to recommend based on those
10-
TOKEN_SALT=change-me
11-
# generate some very long random string preferrably using cryptography libraries
8+
PORT=8000
9+
10+
# redis
11+
REDIS_MAX_CONNECTIONS=20
12+
REDIS_CONNECTIONS_THRESHOLD=100
13+
1214
# UPDATER
13-
CATALOG_UPDATE_MODE=cron # Available options: cron, interval
14-
# cron updates catalogs at specified times
15-
# interval updates in specific intervals
16-
CATALOG_UPDATE_CRON_SCHEDULES=[{"hour": 12, "minute": 0, "id": "catalog_refresh_noon"},{"hour": 0, "minute": 0, "id": "catalog_refresh_midnight"}]
15+
AUTO_UPDATE_CATALOGS=True
1716
CATALOG_REFRESH_INTERVAL_SECONDS=6*60*60
17+
18+
# AI Catalog name generation
19+
GEMINI_API_KEY=<your_gemini_api_key>
20+
DEFAULT_GEMINI_MODEL="gemma-3-27b-it"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ logs/
4646
# python notebooks
4747
*/ipynb_checkpoints/
4848
*.ipynb
49+
.vercel

app/api/endpoints/catalogs.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
from loguru import logger
55

66
from app.api.endpoints.manifest import get_config_id
7+
from app.core.config import settings
78
from app.core.security import redact_token
89
from app.core.settings import UserSettings, get_default_settings
9-
from app.services.catalog_updater import refresh_catalogs_for_credentials
10+
from app.services.catalog_updater import catalog_updater
1011
from app.services.recommendation.engine import RecommendationEngine
1112
from app.services.stremio.service import StremioBundle
1213
from app.services.token_store import token_store
@@ -80,6 +81,10 @@ async def get_catalog(type: str, id: str, response: Response, token: str):
8081
if not credentials:
8182
raise HTTPException(status_code=401, detail="Invalid or expired token. Please reconfigure the addon.")
8283

84+
# Trigger lazy update if needed
85+
if settings.AUTO_UPDATE_CATALOGS:
86+
await catalog_updater.trigger_update(token, credentials)
87+
8388
bundle = StremioBundle()
8489
try:
8590
# 1. Resolve Auth Key (with potential fallback to login)
@@ -200,17 +205,3 @@ def _get_limits() -> tuple[int, int]:
200205
raise HTTPException(status_code=500, detail=str(e))
201206
finally:
202207
await bundle.close()
203-
204-
205-
@router.get("/{token}/catalog/update")
206-
async def update_catalogs(token: str):
207-
"""
208-
Update the catalogs for the addon. This is a manual endpoint to update the catalogs.
209-
"""
210-
# Decode credentials from path
211-
credentials = await token_store.get_user_data(token)
212-
213-
logger.info(f"[{redact_token(token)}] Updating catalogs in response to manual request")
214-
updated = await refresh_catalogs_for_credentials(token, credentials)
215-
logger.info(f"Manual catalog update completed: {updated}")
216-
return {"success": updated}

app/api/endpoints/manifest.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from app.core.settings import UserSettings, get_default_settings
77
from app.core.version import __version__
88
from app.services.catalog import DynamicCatalogService
9+
from app.services.catalog_updater import get_config_id
910
from app.services.stremio.service import StremioBundle
1011
from app.services.token_store import token_store
1112
from app.services.translation import translation_service
@@ -69,21 +70,6 @@ async def build_dynamic_catalogs(bundle: StremioBundle, auth_key: str, user_sett
6970
return await dynamic_catalog_service.get_dynamic_catalogs(library_items, user_settings)
7071

7172

72-
def get_config_id(catalog) -> str | None:
73-
catalog_id = catalog.get("id", "")
74-
if catalog_id.startswith("watchly.theme."):
75-
return "watchly.theme"
76-
if catalog_id.startswith("watchly.loved."):
77-
return "watchly.loved"
78-
if catalog_id.startswith("watchly.watched."):
79-
return "watchly.watched"
80-
if catalog_id.startswith("watchly.item."):
81-
return "watchly.item"
82-
if catalog_id.startswith("watchly.rec"):
83-
return "watchly.rec"
84-
return catalog_id
85-
86-
8773
async def _manifest_handler(response: Response, token: str):
8874
response.headers["Cache-Control"] = "public, max-age=300" # 5 minutes
8975

app/api/endpoints/tokens.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from datetime import datetime, timezone
2+
13
from fastapi import APIRouter, HTTPException, Request
24
from loguru import logger
35
from pydantic import BaseModel, Field
@@ -89,6 +91,11 @@ async def create_token(payload: TokenRequest, request: Request) -> TokenResponse
8991
"email": resolved_email or email or "",
9092
"settings": user_settings.model_dump(),
9193
}
94+
if existing_data:
95+
payload_to_store["last_updated"] = existing_data.get("last_updated")
96+
else:
97+
payload_to_store["last_updated"] = datetime.now(timezone.utc).isoformat()
98+
9299
if email and password:
93100
payload_to_store["password"] = password
94101

@@ -101,12 +108,13 @@ async def create_token(payload: TokenRequest, request: Request) -> TokenResponse
101108
# Maybe generate manifest and check if catalogs exist and if not raise error?
102109
expires_in = settings.TOKEN_TTL_SECONDS if settings.TOKEN_TTL_SECONDS > 0 else None
103110

111+
await bundle.close()
112+
104113
return TokenResponse(
105114
token=token,
106115
manifestUrl=manifest_url,
107116
expiresInSeconds=expires_in,
108117
)
109-
await bundle.close()
110118

111119

112120
async def get_stremio_user_data(payload: TokenRequest) -> tuple[str, str]:

app/core/app.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,18 @@
1010
from loguru import logger
1111

1212
from app.api.main import api_router
13-
from app.services.catalog_updater import BackgroundCatalogUpdater
1413
from app.services.token_store import token_store
1514

1615
from .config import settings
1716
from .version import __version__
1817

19-
# Global catalog updater instance
20-
catalog_updater: BackgroundCatalogUpdater | None = None
21-
2218

2319
@asynccontextmanager
2420
async def lifespan(app: FastAPI):
2521
"""
2622
Manage application lifespan events (startup/shutdown).
2723
"""
28-
global catalog_updater
29-
# Startup
30-
if settings.AUTO_UPDATE_CATALOGS:
31-
catalog_updater = BackgroundCatalogUpdater()
32-
catalog_updater.start()
3324
yield
34-
35-
# Shutdown
36-
if catalog_updater:
37-
await catalog_updater.stop()
38-
catalog_updater = None
39-
logger.info("Background catalog updates stopped")
40-
# Close shared token store Redis client
4125
try:
4226
await token_store.close()
4327
logger.info("TokenStore Redis client closed")

app/core/config.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ class Settings(BaseSettings):
3131
TOKEN_TTL_SECONDS: int = 0 # 0 = never expire
3232
ANNOUNCEMENT_HTML: str = ""
3333
AUTO_UPDATE_CATALOGS: bool = True
34-
CATALOG_UPDATE_MODE: Literal["cron", "interval"] = "cron" # "cron" for fixed times, "interval" for periodic
35-
CATALOG_UPDATE_CRON_SCHEDULES: list[dict] = ({"hour": 0, "minute": 0, "id": "catalog_refresh_midnight"},)
36-
CATALOG_REFRESH_INTERVAL_SECONDS: int = 6 * 60 * 60 # 6 hours (used when CATALOG_UPDATE_MODE="interval")
34+
CATALOG_REFRESH_INTERVAL_SECONDS: int = 43200 # 12 hours
3735
APP_ENV: Literal["development", "production", "vercel"] = "development"
3836
HOST_NAME: str = "https://1ccea4301587-watchly.baby-beamup.club"
3937

app/core/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.3.2"
1+
__version__ = "1.3.3"

0 commit comments

Comments
 (0)