feat: Trakt OAuth integration + stable dynamic catalog IDs#133
feat: Trakt OAuth integration + stable dynamic catalog IDs#133mistertomlinson wants to merge 3 commits intoTimilsinaBimal:mainfrom
Conversation
|
@mistertomlinson is attempting to deploy a commit to the Bimal Timilsina's projects Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Code Review
This pull request implements Trakt integration, enabling users to authenticate via Trakt and sync their watch history for personalized recommendations. It introduces a new Trakt OAuth flow, library normalization services, and a catalog stabilization mechanism using Redis to maintain consistent IDs across external apps. Review feedback highlights the need to move in-memory OAuth states to Redis for multi-worker compatibility, implement token refresh logic for background updates, and optimize API interactions through concurrency and connection pooling. Cleanup of unused constants and more secure JavaScript serialization were also recommended.
| # In-memory store for short-lived OAuth state values. | ||
| # This is per-process only; for multi-worker deployments Redis would be better, | ||
| # but CSRF protection is still improved versus nothing. | ||
| _oauth_states: dict[str, str] = {} |
There was a problem hiding this comment.
The _oauth_states dictionary is stored in-memory, which will cause authentication failures in multi-worker or distributed environments (e.g., Gunicorn/Uvicorn with multiple workers). Additionally, states for abandoned login attempts are never cleared, leading to a memory leak over time.
Consider using a shared store like Redis (which is already integrated into the project) with a short TTL (e.g., 5-10 minutes) to manage these state values.
| async def _refresh_trakt_catalogs( | ||
| self, token: str, credentials: dict[str, Any], update_timestamp: bool = True | ||
| ) -> bool: |
There was a problem hiding this comment.
The Trakt catalog refresh logic does not handle token expiration. Trakt access tokens typically expire after 90 days. If the access_token (stored as authKey) expires, background updates will fail.
It is recommended to check the trakt_expires_at timestamp and use the trakt_refresh_token to obtain a new access token via trakt_bundle.auth.refresh_token when the current one is near expiration.
| if value is None: | ||
| return "null" | ||
| escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") | ||
| return f'"{escaped}"' |
There was a problem hiding this comment.
Manual string escaping for JavaScript injection is fragile and potentially insecure. Using json.dumps is a safer and more standard way to serialize Python values for use in JavaScript templates.
| if value is None: | |
| return "null" | |
| escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") | |
| return f'"{escaped}"' | |
| import json | |
| return json.dumps(value) |
| movies = await self._get_history("movies") | ||
| shows = await self._get_history("shows") |
There was a problem hiding this comment.
Fetching movie and show history sequentially increases the total response time for the library fetch. These requests can be performed concurrently to improve performance.
| movies = await self._get_history("movies") | |
| shows = await self._get_history("shows") | |
| import asyncio | |
| movies, shows = await asyncio.gather(self._get_history("movies"), self._get_history("shows")) |
| # How many pages of history to pull (1 000 items / page) | ||
| MAX_PAGES = 10 |
|
|
||
| TOKEN_URL = "https://api.trakt.tv/oauth/token" | ||
| AUTHORIZE_URL = "https://trakt.tv/oauth/authorize" | ||
| DEVICE_CODE_URL = "https://api.trakt.tv/oauth/device/code" |
| "grant_type": "authorization_code", | ||
| } | ||
| try: | ||
| async with httpx.AsyncClient(timeout=15.0) as client: |
|
I see there are some issues to address (I've also found another bug that needs fixing). Work in progress. |
- Move OAuth state storage from in-memory to Redis (multi-worker safe, auto-expiring) - Add Trakt token refresh logic in background catalog updater - Use json.dumps for safe JavaScript serialization in callback HTML - Fetch movie and show history concurrently with asyncio.gather - Remove unused DEVICE_CODE_URL constant and MAX_PAGES constant - Fix catalog slot map Redis key to use watchly: namespace prefix
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
TimilsinaBimal
left a comment
There was a problem hiding this comment.
I have not got time to review the code in details. But one of my concern is,
if someone wants to use stremio but with trakt account, this doesn't allow them to do that. as its either trakt or stremio.
maybe weshould also look into this.
could you check this PR: #126 I have also worked on similar functionalities, with both trakt and Simkl functionality but those always require Stremio login, we can keep stremio as optional on those cases. I cannot merge both of the PRs since they solve the exact same issue. So, I suggest you to look into that PR and see if we can merge these into one.
|
I'm still finding issues with my implementation. Specifically, the slots/ID system when using AIOStreams. So, it's a work in progress atm. Is Simultaneous Stremio and Trakt use important so the web UI can open Stremio to continue installation? Is the concern that this would be lost using Trakt only? Otherwise, I can't figure out why anyone would need to use both.
|
Well Some people want to use trakt history on their Stremio account. If they do that, then the current flow doesn't support it. You can either use Stremio or Trakt. Other than that, I don't have much concerns. |
Closes #132
Summary
Adds Trakt as an alternative login provider on the configure page, so users can track their watch history and get recommendations without needing a Stremio account. Also fixes a long-standing issue where dynamic catalog ordering couldn't be saved in Stremio clients like Nuvio.
Changes
Trakt Integration
token_storeencryptionTRAKT_CLIENT_ID/TRAKT_CLIENT_SECRETare not set on the serverStable Dynamic Catalog IDs (bonus fix)
watchly.watched.tt0209144andwatchly.theme.a:g27...are replaced with stable positional slot IDs (watchly.watched.slot0,watchly.theme.slot0) in the manifestSetup required (for server operators)
https://your_host/tokens/trakt/callbackTRAKT_CLIENT_ID=your_client_id
TRAKT_CLIENT_SECRET=your_client_secret
If these are not set, the Trakt tab is hidden and the addon works exactly as before.
Testing
Tested locally with a real Trakt account. Stremio login flow verified to be unaffected.