@@ -121,9 +121,43 @@ async def _login_for_auth_key(self) -> str:
121121 raise
122122
123123 async def get_auth_key (self ) -> str :
124- """Return the cached auth key."""
124+ """Return a valid auth key; refresh transparently if expired and credentials exist."""
125+ # If we have a key, validate it via GetUser
126+ if self ._auth_key :
127+ try :
128+ await self .get_user_info (self ._auth_key )
129+ return self ._auth_key
130+ except Exception :
131+ # fall through to attempt refresh
132+ pass
133+
134+ # Refresh using username/password if available
135+ if self .username and self .password :
136+ fresh_key = await self ._login_for_auth_key ()
137+
138+ # Persist refreshed key back to Redis, best-effort
139+ try :
140+ from app .services .token_store import token_store # lazy import to avoid cycles
141+
142+ try :
143+ info = await self .get_user_info (fresh_key )
144+ uid = info .get ("user_id" )
145+ except Exception :
146+ uid = None
147+ if uid :
148+ existing = await token_store .get_user_data (uid )
149+ if existing :
150+ updated = existing .copy ()
151+ updated ["authKey" ] = fresh_key
152+ await token_store .store_user_data (uid , updated )
153+ except Exception :
154+ pass
155+
156+ return fresh_key
157+
158+ # No credentials to refresh; if we still have no key, error
125159 if not self ._auth_key :
126- raise ValueError ("Stremio auth key is missing." )
160+ raise ValueError ("Stremio auth key is missing and cannot be refreshed ." )
127161 return self ._auth_key
128162
129163 async def is_loved (self , auth_key : str , imdb_id : str , media_type : str ) -> tuple [bool , bool ]:
@@ -176,15 +210,16 @@ async def get_liked_items(self, auth_token: str, media_type: str) -> list[str]:
176210 logger .warning (f"Failed to fetch liked items: { e } " )
177211 return []
178212
179- async def get_user_info (self ) -> dict [str , str ]:
180- """Fetch user ID and email using the auth key."""
181- if not self ._auth_key :
213+ async def get_user_info (self , auth_key : str | None = None ) -> dict [str , str ]:
214+ """Fetch user ID and email using the provided auth key (or self._auth_key)."""
215+ key = auth_key or self ._auth_key
216+ if not key :
182217 raise ValueError ("Stremio auth key is missing." )
183218
184219 url = f"{ self .base_url } /api/getUser"
185220 payload = {
186221 "type" : "GetUser" ,
187- "authKey" : self . _auth_key ,
222+ "authKey" : key ,
188223 }
189224
190225 try :
@@ -212,7 +247,8 @@ async def get_user_info(self) -> dict[str, str]:
212247
213248 async def get_user_email (self ) -> str :
214249 """Fetch user email using the auth key."""
215- user_info = await self .get_user_info ()
250+ auth_key = await self .get_auth_key ()
251+ user_info = await self .get_user_info (auth_key )
216252 return user_info .get ("email" , "" )
217253
218254 async def get_library_items (self ) -> dict [str , list [dict ]]:
@@ -221,10 +257,6 @@ async def get_library_items(self) -> dict[str, list[dict]]:
221257 Returns a dict with 'watched' and 'loved' keys.
222258 """
223259
224- if not self ._auth_key :
225- logger .warning ("Stremio auth key not configured" )
226- return {"watched" : [], "loved" : []}
227-
228260 try :
229261 # Get auth token
230262 auth_key = await self .get_auth_key ()
@@ -321,7 +353,11 @@ def _sort_key(x: dict):
321353
322354 added_items .append (item )
323355
324- logger .info (f"Found { len (added_items )} added (unwatched) and { len (removed_items )} removed library items" )
356+ logger .info (
357+ "Found %s added (unwatched) and %s removed library items" ,
358+ len (added_items ),
359+ len (removed_items ),
360+ )
325361 # Prepare result
326362 result = {
327363 "watched" : watched_items ,
@@ -413,7 +449,12 @@ async def _post_with_retries(self, client: httpx.AsyncClient, url: str, json: di
413449 attempts += 1
414450 backoff = (2 ** (attempts - 1 )) + random .uniform (0 , 0.25 )
415451 logger .warning (
416- f"Stremio POST { url } failed with { status } ; retry { attempts } /{ max_tries } in" f" { backoff :.2f} s"
452+ "Stremio POST %s failed with %s; retry %s/%s in %.2fs" ,
453+ url ,
454+ status ,
455+ attempts ,
456+ max_tries ,
457+ backoff ,
417458 )
418459 await asyncio .sleep (backoff )
419460 last_exc = e
@@ -422,7 +463,14 @@ async def _post_with_retries(self, client: httpx.AsyncClient, url: str, json: di
422463 except httpx .RequestError as e :
423464 attempts += 1
424465 backoff = (2 ** (attempts - 1 )) + random .uniform (0 , 0.25 )
425- logger .warning (f"Stremio POST { url } request error: { e } ; retry { attempts } /{ max_tries } in { backoff :.2f} s" )
466+ logger .warning (
467+ "Stremio POST %s request error: %s; retry %s/%s in %.2fs" ,
468+ url ,
469+ e ,
470+ attempts ,
471+ max_tries ,
472+ backoff ,
473+ )
426474 await asyncio .sleep (backoff )
427475 last_exc = e
428476 continue
@@ -446,7 +494,12 @@ async def _get_with_retries(
446494 attempts += 1
447495 backoff = (2 ** (attempts - 1 )) + random .uniform (0 , 0.25 )
448496 logger .warning (
449- f"Stremio GET { url } failed with { status } ; retry { attempts } /{ max_tries } in" f" { backoff :.2f} s"
497+ "Stremio GET %s failed with %s; retry %s/%s in %.2fs" ,
498+ url ,
499+ status ,
500+ attempts ,
501+ max_tries ,
502+ backoff ,
450503 )
451504 await asyncio .sleep (backoff )
452505 last_exc = e
@@ -455,7 +508,14 @@ async def _get_with_retries(
455508 except httpx .RequestError as e :
456509 attempts += 1
457510 backoff = (2 ** (attempts - 1 )) + random .uniform (0 , 0.25 )
458- logger .warning (f"Stremio GET { url } request error: { e } ; retry { attempts } /{ max_tries } in { backoff :.2f} s" )
511+ logger .warning (
512+ "Stremio GET %s request error: %s; retry %s/%s in %.2fs" ,
513+ url ,
514+ e ,
515+ attempts ,
516+ max_tries ,
517+ backoff ,
518+ )
459519 await asyncio .sleep (backoff )
460520 last_exc = e
461521 continue
0 commit comments