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,19 @@ 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 )
62+ if not credentials :
63+ raise HTTPException (status_code = 401 , detail = "Invalid or expired token. Please reconfigure the addon." )
6464
65- credentials = await resolve_user_credentials (token )
66-
67- if settings_str :
68- user_settings = decode_settings (settings_str )
69- elif credentials .get ("settings" ):
65+ if credentials .get ("settings" ):
7066 user_settings = UserSettings (** credentials ["settings" ])
7167 else :
72- user_settings = None
68+ user_settings = get_default_settings ()
7369
74- stremio_service = StremioService (
75- username = credentials .get ("username" ) or "" ,
76- password = credentials .get ("password" ) or "" ,
77- auth_key = credentials .get ("authKey" ),
78- )
70+ stremio_service = StremioService (auth_key = credentials .get ("authKey" ))
7971
8072 # Note: get_library_items is expensive, but we need it to determine *which* genre catalogs to show.
8173 library_items = await stremio_service .get_library_items ()
@@ -88,75 +80,56 @@ async def fetch_catalogs(token: str | None = None, settings_str: str | None = No
8880 return catalogs
8981
9082
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)
83+ def get_config_id (catalog ) -> str | None :
84+ catalog_id = catalog .get ("id" , "" )
85+ if catalog_id .startswith ("watchly.theme." ):
86+ return "watchly.theme"
87+ if catalog_id .startswith ("watchly.loved." ):
88+ return "watchly.loved"
89+ if catalog_id .startswith ("watchly.watched." ):
90+ return "watchly.watched"
91+ if catalog_id .startswith ("watchly.item." ):
92+ return "watchly.item"
93+ if catalog_id .startswith ("watchly.rec" ):
94+ return "watchly.rec"
95+ return catalog_id
96+
97+
98+ async def _manifest_handler (response : Response , token : str ):
9499 response .headers ["Cache-Control" ] = "public, max-age=86400"
95100
101+ if not token :
102+ raise HTTPException (status_code = 401 , detail = "Missing token. Please reconfigure the addon." )
103+
96104 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
105+ try :
106+ creds = await token_store .get_user_data (token )
107+ if creds .get ("settings" ):
108+ user_settings = UserSettings (** creds ["settings" ])
109+ except Exception :
110+ raise HTTPException (status_code = 401 , detail = "Invalid or expired token. Please reconfigure the addon." )
107111
108112 base_manifest = get_base_manifest (user_settings )
109113
114+ # translate to target language
110115 if user_settings and user_settings .language :
111116 for cat in base_manifest .get ("catalogs" , []):
112117 if cat .get ("name" ):
113118 cat ["name" ] = await translation_service .translate (cat ["name" ], user_settings .language )
114119
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
120+ fetched_catalogs = await fetch_catalogs (token )
146121
147- return base_manifest
122+ all_catalogs = [c .copy () for c in base_manifest ["catalogs" ]] + [c .copy () for c in fetched_catalogs ]
123+
124+ if user_settings :
125+ order_map = {c .id : i for i , c in enumerate (user_settings .catalogs )}
126+ all_catalogs .sort (key = lambda x : order_map .get (get_config_id (x ), 999 ))
148127
128+ base_manifest ["catalogs" ] = all_catalogs
149129
150- @router .get ("/manifest.json" )
151- async def manifest_root (response : Response ):
152- return await _manifest_handler (response , None , None )
130+ return base_manifest
153131
154132
155133@router .get ("/{token}/manifest.json" )
156134async 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 )
135+ return await _manifest_handler (response , token )
0 commit comments