55from redis import exceptions as redis_exceptions
66
77from app .core .config import settings
8- from app .core .settings import CatalogConfig , UserSettings , encode_settings , get_default_settings
8+ from app .core .settings import CatalogConfig , UserSettings , get_default_settings
99from app .services .catalog_updater import refresh_catalogs_for_credentials
1010from app .services .stremio_service import StremioService
1111from app .services .token_store import token_store
1515
1616
1717class TokenRequest (BaseModel ):
18+ watchly_username : str | None = Field (default = None , description = "Watchly account (user/id)" )
19+ watchly_password : str | None = Field (default = None , description = "Watchly account password" )
1820 username : str | None = Field (default = None , description = "Stremio username or email" )
1921 password : str | None = Field (default = None , description = "Stremio password" )
2022 authKey : str | None = Field (default = None , description = "Existing Stremio auth key" )
@@ -77,34 +79,95 @@ async def _verify_credentials_or_raise(payload: dict) -> str:
7779
7880@router .post ("/" , response_model = TokenResponse )
7981async def create_token (payload : TokenRequest , request : Request ) -> TokenResponse :
80- username = payload .username .strip () if payload .username else None
81- password = payload .password
82- auth_key = payload .authKey .strip () if payload .authKey else None
82+ # Stremio Credentials
83+ stremio_username = payload .username .strip () if payload .username else None
84+ stremio_password = payload .password
85+ stremio_auth_key = payload .authKey .strip () if payload .authKey else None
86+
87+ # Watchly Credentials (The new flow)
88+ watchly_username = payload .watchly_username .strip () if payload .watchly_username else None
89+ watchly_password = payload .watchly_password
90+
8391 rpdb_key = payload .rpdb_key .strip () if payload .rpdb_key else None
8492
85- if auth_key and auth_key .startswith ('"' ) and auth_key .endswith ('"' ):
86- auth_key = auth_key [1 :- 1 ].strip ()
93+ if stremio_auth_key and stremio_auth_key .startswith ('"' ) and stremio_auth_key .endswith ('"' ):
94+ stremio_auth_key = stremio_auth_key [1 :- 1 ].strip ()
95+
96+ # Construct Settings
97+ default_settings = get_default_settings ()
8798
88- if username and not password :
89- raise HTTPException (status_code = 400 , detail = "Password is required when a username is provided." )
99+ user_settings = UserSettings (
100+ language = payload .language or default_settings .language ,
101+ catalogs = payload .catalogs if payload .catalogs else default_settings .catalogs ,
102+ rpdb_key = rpdb_key ,
103+ excluded_movie_genres = payload .excluded_movie_genres ,
104+ excluded_series_genres = payload .excluded_series_genres ,
105+ )
90106
91- if password and not username :
107+ # Logic to handle "Update Mode" (Watchly credentials only)
108+ is_update_mode = (watchly_username and watchly_password ) and not (
109+ stremio_username or stremio_password or stremio_auth_key
110+ )
111+
112+ if is_update_mode :
113+ # User is trying to update settings using only Watchly credentials
114+ # We must retrieve their existing Stremio credentials from the store
115+ temp_payload_for_derivation = {
116+ "watchly_username" : watchly_username ,
117+ "watchly_password" : watchly_password ,
118+ "username" : None ,
119+ "password" : None ,
120+ "authKey" : None ,
121+ }
122+ derived_token = token_store .derive_token (temp_payload_for_derivation )
123+ existing_data = await token_store .get_payload (derived_token )
124+
125+ if not existing_data :
126+ raise HTTPException (
127+ status_code = 404 ,
128+ detail = "Account not found. Please start as a New User to connect Stremio." ,
129+ )
130+
131+ # Hydrate Stremio credentials from existing data
132+ stremio_username = existing_data .get ("username" )
133+ stremio_password = existing_data .get ("password" )
134+ stremio_auth_key = existing_data .get ("authKey" )
135+
136+ # Regular Validation Logic
137+ if stremio_username and not stremio_password :
138+ raise HTTPException (status_code = 400 , detail = "Stremio password is required when username is provided." )
139+
140+ if stremio_password and not stremio_username :
92141 raise HTTPException (
93142 status_code = 400 ,
94- detail = "Username /email is required when a password is provided." ,
143+ detail = "Stremio username /email is required when password is provided." ,
95144 )
96145
97- if not auth_key and not (username and password ):
146+ if not stremio_auth_key and not (stremio_username and stremio_password ):
98147 raise HTTPException (
99148 status_code = 400 ,
100- detail = "Provide either a Stremio auth key or both username and password." ,
149+ detail = "Provide either a Stremio auth key or both Stremio username and password." ,
150+ )
151+
152+ # if creating a new account, check if the Watchly ID is already taken.
153+ if watchly_username and not is_update_mode :
154+ derived_token = token_store .derive_token (
155+ {"watchly_username" : watchly_username , "watchly_password" : watchly_password }
101156 )
157+ if await token_store .get_payload (derived_token ):
158+ raise HTTPException (
159+ status_code = 409 ,
160+ detail = "This Watchly ID is already in use. Please choose a different one or log in as an Existing User." , # noqa: E501
161+ )
102162
103- # We only store credentials in Redis, settings go into URL
163+ # Payload to store includes BOTH Watchly and Stremio credentials + User Settings
104164 payload_to_store = {
105- "username" : username ,
106- "password" : password ,
107- "authKey" : auth_key ,
165+ "watchly_username" : watchly_username ,
166+ "watchly_password" : watchly_password ,
167+ "username" : stremio_username ,
168+ "password" : stremio_password ,
169+ "authKey" : stremio_auth_key ,
170+ "settings" : user_settings .model_dump (),
108171 }
109172
110173 verified_auth_key = await _verify_credentials_or_raise (payload_to_store )
@@ -125,20 +188,6 @@ async def create_token(payload: TokenRequest, request: Request) -> TokenResponse
125188 detail = "Token storage is temporarily unavailable. Please try again once Redis is reachable." ,
126189 ) from exc
127190
128- # Construct Settings
129- default_settings = get_default_settings ()
130-
131- user_settings = UserSettings (
132- language = payload .language or default_settings .language ,
133- catalogs = payload .catalogs if payload .catalogs else default_settings .catalogs ,
134- rpdb_key = rpdb_key ,
135- excluded_movie_genres = payload .excluded_movie_genres ,
136- excluded_series_genres = payload .excluded_series_genres ,
137- )
138-
139- # encode_settings now includes the "settings:" prefix
140- encoded_settings = encode_settings (user_settings )
141-
142191 if created :
143192 try :
144193 await refresh_catalogs_for_credentials (
@@ -153,8 +202,8 @@ async def create_token(payload: TokenRequest, request: Request) -> TokenResponse
153202 ) from exc
154203
155204 base_url = settings .HOST_NAME
156- # New URL structure
157- manifest_url = f"{ base_url } /{ encoded_settings } / { token } /manifest.json"
205+ # New URL structure (Settings stored in Token)
206+ manifest_url = f"{ base_url } /{ token } /manifest.json"
158207
159208 expires_in = settings .TOKEN_TTL_SECONDS if settings .TOKEN_TTL_SECONDS > 0 else None
160209
@@ -165,26 +214,64 @@ async def create_token(payload: TokenRequest, request: Request) -> TokenResponse
165214 )
166215
167216
217+ @router .post ("/verify" , status_code = 200 )
218+ async def verify_user (payload : TokenRequest ):
219+ """Verify if a Watchly user exists."""
220+ watchly_username = payload .watchly_username .strip () if payload .watchly_username else None
221+ watchly_password = payload .watchly_password
222+
223+ if not watchly_username or not watchly_password :
224+ raise HTTPException (status_code = 400 , detail = "Watchly username and password required." )
225+
226+ payload_to_derive = {
227+ "watchly_username" : watchly_username ,
228+ "watchly_password" : watchly_password ,
229+ "username" : None ,
230+ "password" : None ,
231+ "authKey" : None ,
232+ }
233+
234+ token = token_store .derive_token (payload_to_derive )
235+ exists = await token_store .get_payload (token )
236+
237+ if not exists :
238+ raise HTTPException (status_code = 404 , detail = "Account not found." )
239+
240+ return {"found" : True , "token" : token , "settings" : exists .get ("settings" )}
241+
242+
168243@router .delete ("/" , status_code = 200 )
169244async def delete_token (payload : TokenRequest ):
170245 """Delete a token based on provided credentials."""
171- username = payload .username .strip () if payload .username else None
172- password = payload .password
173- auth_key = payload .authKey .strip () if payload .authKey else None
174-
175- if auth_key and auth_key .startswith ('"' ) and auth_key .endswith ('"' ):
176- auth_key = auth_key [1 :- 1 ].strip ()
177-
178- if not auth_key and not (username and password ):
246+ # Stremio Credentials
247+ stremio_username = payload .username .strip () if payload .username else None
248+ stremio_password = payload .password
249+ stremio_auth_key = payload .authKey .strip () if payload .authKey else None
250+
251+ # Watchly Credentials
252+ watchly_username = payload .watchly_username .strip () if payload .watchly_username else None
253+ watchly_password = payload .watchly_password
254+
255+ if stremio_auth_key and stremio_auth_key .startswith ('"' ) and stremio_auth_key .endswith ('"' ):
256+ stremio_auth_key = stremio_auth_key [1 :- 1 ].strip ()
257+
258+ # Need either Watchly creds OR Stremio creds (for legacy)
259+ if (
260+ not (watchly_username and watchly_password )
261+ and not stremio_auth_key
262+ and not (stremio_username and stremio_password )
263+ ):
179264 raise HTTPException (
180265 status_code = 400 ,
181- detail = "Provide either a Stremio auth key or both username and password to delete account." ,
266+ detail = "Provide Watchly credentials ( or Stremio credentials for legacy accounts) to delete account." ,
182267 )
183268
184269 payload_to_derive = {
185- "username" : username ,
186- "password" : password ,
187- "authKey" : auth_key ,
270+ "watchly_username" : watchly_username ,
271+ "watchly_password" : watchly_password ,
272+ "username" : stremio_username ,
273+ "password" : stremio_password ,
274+ "authKey" : stremio_auth_key ,
188275 }
189276
190277 try :
0 commit comments