11from async_lru import alru_cache
2- from fastapi import Response
2+ from fastapi import HTTPException , Response
33from fastapi .routing import APIRouter
44
55from app .core .config import settings
6- from app .core .settings import UserSettings , decode_settings
6+ from app .core .settings import UserSettings , get_default_settings
77from app .core .version import __version__
88from app .services .catalog import DynamicCatalogService
99from app .services .stremio_service import StremioService
10+ from app .services .token_store import token_store
1011from app .services .translation import translation_service
11- from app .utils import resolve_user_credentials
1212
1313router = APIRouter ()
1414
@@ -55,27 +55,17 @@ def get_base_manifest(user_settings: UserSettings | None = None):
5555 }
5656
5757
58- # Cache catalog definitions for 1 hour (3600s)
5958# Cache catalog definitions for 1 hour (3600s)
6059@alru_cache (maxsize = 1000 , ttl = 3600 )
61- async def fetch_catalogs (token : str | None = None , settings_str : str | None = None ):
62- if not token :
63- return []
60+ async def fetch_catalogs (token : str ):
61+ credentials = await token_store .get_user_data (token )
6462
65- credentials = await resolve_user_credentials (token )
66-
67- if settings_str :
68- user_settings = decode_settings (settings_str )
69- elif credentials .get ("settings" ):
63+ if credentials .get ("settings" ):
7064 user_settings = UserSettings (** credentials ["settings" ])
7165 else :
72- user_settings = None
66+ user_settings = get_default_settings ()
7367
74- stremio_service = StremioService (
75- username = credentials .get ("username" ) or "" ,
76- password = credentials .get ("password" ) or "" ,
77- auth_key = credentials .get ("authKey" ),
78- )
68+ stremio_service = StremioService (auth_key = credentials .get ("authKey" ))
7969
8070 # Note: get_library_items is expensive, but we need it to determine *which* genre catalogs to show.
8171 library_items = await stremio_service .get_library_items ()
@@ -88,75 +78,56 @@ async def fetch_catalogs(token: str | None = None, settings_str: str | None = No
8878 return catalogs
8979
9080
91- async def _manifest_handler (response : Response , token : str | None , settings_str : str | None ):
92- """Stremio manifest handler."""
93- # Cache manifest for 1 day (86400 seconds)
81+ def get_config_id (catalog ) -> str | None :
82+ catalog_id = catalog .get ("id" , "" )
83+ if catalog_id .startswith ("watchly.theme." ):
84+ return "watchly.theme"
85+ if catalog_id .startswith ("watchly.loved." ):
86+ return "watchly.loved"
87+ if catalog_id .startswith ("watchly.watched." ):
88+ return "watchly.watched"
89+ if catalog_id .startswith ("watchly.item." ):
90+ return "watchly.item"
91+ if catalog_id .startswith ("watchly.rec" ):
92+ return "watchly.rec"
93+ return catalog_id
94+
95+
96+ async def _manifest_handler (response : Response , token : str ):
9497 response .headers ["Cache-Control" ] = "public, max-age=86400"
9598
99+ if not token :
100+ raise HTTPException (status_code = 401 , detail = "Missing token. Please reconfigure the addon." )
101+
96102 user_settings = None
97- if settings_str :
98- user_settings = decode_settings (settings_str )
99- elif token :
100- try :
101- creds = await resolve_user_credentials (token )
102- if creds .get ("settings" ):
103- user_settings = UserSettings (** creds ["settings" ])
104- except Exception :
105- # Fallback to defaults if token resolution fails (or let it fail later in fetch_catalogs)
106- pass
103+ try :
104+ creds = await token_store .get_user_data (token )
105+ if creds .get ("settings" ):
106+ user_settings = UserSettings (** creds ["settings" ])
107+ except Exception :
108+ raise HTTPException (status_code = 401 , detail = "Invalid or expired token. Please reconfigure the addon." )
107109
108110 base_manifest = get_base_manifest (user_settings )
109111
112+ # translate to target language
110113 if user_settings and user_settings .language :
111114 for cat in base_manifest .get ("catalogs" , []):
112115 if cat .get ("name" ):
113116 cat ["name" ] = await translation_service .translate (cat ["name" ], user_settings .language )
114117
115- if token :
116- # We pass settings_str to fetch_catalogs so it can cache different versions
117- # We COPY the lists to avoid modifying cached objects or base_manifest defaults
118- fetched_catalogs = await fetch_catalogs (token , settings_str )
119-
120- # Create a new list with copies of all catalogs
121- all_catalogs = [c .copy () for c in base_manifest ["catalogs" ]] + [c .copy () for c in fetched_catalogs ]
122-
123- if user_settings :
124- # Create a lookup for order index
125- order_map = {c .id : i for i , c in enumerate (user_settings .catalogs )}
126-
127- # Sort. Items not in map go to end.
128- # Extract config id from catalog id for matching with user settings
129- def get_config_id (catalog ):
130- catalog_id = catalog .get ("id" , "" )
131- if catalog_id .startswith ("watchly.theme." ):
132- return "watchly.theme"
133- if catalog_id .startswith ("watchly.loved." ):
134- return "watchly.loved"
135- if catalog_id .startswith ("watchly.watched." ):
136- return "watchly.watched"
137- if catalog_id .startswith ("watchly.item." ):
138- return "watchly.item"
139- if catalog_id .startswith ("watchly.rec" ):
140- return "watchly.rec"
141- return catalog_id
142-
143- all_catalogs .sort (key = lambda x : order_map .get (get_config_id (x ), 999 ))
144-
145- base_manifest ["catalogs" ] = all_catalogs
118+ fetched_catalogs = await fetch_catalogs (token )
146119
147- return base_manifest
120+ all_catalogs = [c .copy () for c in base_manifest ["catalogs" ]] + [c .copy () for c in fetched_catalogs ]
121+
122+ if user_settings :
123+ order_map = {c .id : i for i , c in enumerate (user_settings .catalogs )}
124+ all_catalogs .sort (key = lambda x : order_map .get (get_config_id (x ), 999 ))
148125
126+ base_manifest ["catalogs" ] = all_catalogs
149127
150- @router .get ("/manifest.json" )
151- async def manifest_root (response : Response ):
152- return await _manifest_handler (response , None , None )
128+ return base_manifest
153129
154130
155131@router .get ("/{token}/manifest.json" )
156132async def manifest_token (response : Response , token : str ):
157- return await _manifest_handler (response , token , None )
158-
159-
160- @router .get ("/{settings_str}/{token}/manifest.json" )
161- async def manifest_settings (response : Response , settings_str : str , token : str ):
162- return await _manifest_handler (response , token , settings_str )
133+ return await _manifest_handler (response , token )
0 commit comments