···11VITE_TMDB_READ_API_KEY=...
22VITE_OPENSEARCH_ENABLED=false
33-VITE_ENABLE_TRAKT=false
4354# make sure the cors proxy url does NOT have a slash at the end
65VITE_CORS_PROXY_URL=...
+11-213
src/backend/metadata/traktApi.ts
···11-import { conf } from "@/setup/config";
21import { SimpleCache } from "@/utils/cache";
33-import { getTurnstileToken } from "@/utils/turnstile";
4253import { getMediaDetails } from "./tmdb";
64import { TMDBContentTypes, TMDBMovieData } from "./types/tmdb";
···5149traktCache.setCompare((a, b) => a.endpoint === b.endpoint);
5250traktCache.initialize();
53515454-// Authentication state - only track concurrent requests
5555-let isAuthenticating = false;
5656-let authToken: string | null = null;
5757-let tokenExpiry: Date | null = null;
5858-5959-/**
6060- * Clears the authentication token
6161- */
6262-function clearAuthToken(): void {
6363- authToken = null;
6464- tokenExpiry = null;
6565- localStorage.removeItem("trakt_auth_token");
6666- localStorage.removeItem("trakt_token_expiry");
6767-}
6868-6969-/**
7070- * Stores the authentication token in memory and localStorage
7171- */
7272-function storeAuthToken(token: string, expiresAt: string): void {
7373- const expiryDate = new Date(expiresAt);
7474- if (Number.isNaN(expiryDate.getTime())) {
7575- console.error("Invalid expiry date format:", expiresAt);
7676- return;
7777- }
7878-7979- authToken = token;
8080- tokenExpiry = expiryDate;
8181-8282- // Store in localStorage for persistence
8383- localStorage.setItem("trakt_auth_token", token);
8484- localStorage.setItem("trakt_token_expiry", expiresAt);
8585-}
8686-8787-/**
8888- * Checks if user is authenticated by checking token validity
8989- */
9090-function isAuthenticated(): boolean {
9191- // Check memory first
9292- if (authToken && tokenExpiry && tokenExpiry > new Date()) {
9393- return true;
9494- }
9595-9696- // Check localStorage
9797- const storedToken = localStorage.getItem("trakt_auth_token");
9898- const storedExpiry = localStorage.getItem("trakt_token_expiry");
9999-100100- if (storedToken && storedExpiry) {
101101- const expiryDate = new Date(storedExpiry);
102102- if (expiryDate > new Date()) {
103103- authToken = storedToken;
104104- tokenExpiry = expiryDate;
105105- return true;
106106- }
107107- // Token expired, clear it
108108- clearAuthToken();
109109- }
110110-111111- return false;
112112-}
113113-114114-/**
115115- * Authenticates with the Trakt API using Cloudflare Turnstile
116116- * Stores the auth token for use in API requests
117117- */
118118-async function authenticateWithTurnstile(): Promise<void> {
119119- // Prevent concurrent authentication attempts
120120- if (isAuthenticating) {
121121- // Wait for existing authentication to complete
122122- await new Promise<void>((resolve) => {
123123- const checkAuth = () => {
124124- if (!isAuthenticating) {
125125- resolve();
126126- } else {
127127- setTimeout(checkAuth, 100);
128128- }
129129- };
130130- checkAuth();
131131- });
132132- return;
133133- }
134134-135135- isAuthenticating = true;
136136-137137- try {
138138- const turnstileToken = await getTurnstileToken("0x4AAAAAAB6ocCCpurfWRZyC");
139139-140140- // Authenticate with the API
141141- const response = await fetch(`${TRAKT_BASE_URL}/auth`, {
142142- method: "POST",
143143- headers: {
144144- "Content-Type": "application/json",
145145- },
146146- body: JSON.stringify({
147147- token: turnstileToken,
148148- }),
149149- });
150150-151151- if (!response.ok) {
152152- throw new Error(`Authentication failed: ${response.statusText}`);
153153- }
154154-155155- const result = await response.json();
156156-157157- if (!result.success) {
158158- throw new Error(result.message || "Authentication failed");
159159- }
160160-161161- // Store the auth token
162162- storeAuthToken(result.auth_token, result.expires_at);
163163- } finally {
164164- isAuthenticating = false;
165165- }
166166-}
167167-16852// Base function to fetch from Trakt API
16953async function fetchFromTrakt<T = TraktListResponse>(
17054 endpoint: string,
17155): Promise<T> {
172172- // Check if Trakt is enabled
173173- if (!conf().ENABLE_TRAKT) {
174174- throw new Error("Trakt API is not enabled, using tmdb lists instead.");
175175- }
176176-17756 // Check cache first
17857 const cacheKey: TraktCacheKey = { endpoint };
17958 const cachedResult = traktCache.get(cacheKey);
···18160 return cachedResult as T;
18261 }
18362184184- // Ensure we're authenticated
185185- if (!isAuthenticated()) {
186186- await authenticateWithTurnstile();
187187- }
188188-189189- // Make the API request with authorization header
190190- const headers: Record<string, string> = {};
191191- if (authToken) {
192192- headers.Authorization = `Bearer ${authToken}`;
193193- }
194194-195195- let response = await fetch(`${TRAKT_BASE_URL}${endpoint}`, {
196196- headers,
197197- });
198198-199199- // If request fails, try re-authenticating and retry once
6363+ // Make the API request
6464+ const response = await fetch(`${TRAKT_BASE_URL}${endpoint}`);
20065 if (!response.ok) {
201201- // If 401, clear token and re-authenticate
202202- if (response.status === 401) {
203203- clearAuthToken();
204204- }
205205-206206- // Re-authenticate and retry
207207- await authenticateWithTurnstile();
208208-209209- // Rebuild headers after re-authentication
210210- const retryHeaders: Record<string, string> = {};
211211- if (authToken) {
212212- retryHeaders.Authorization = `Bearer ${authToken}`;
213213- }
214214-215215- response = await fetch(`${TRAKT_BASE_URL}${endpoint}`, {
216216- headers: retryHeaders,
217217- });
218218-219219- // If retry also fails, throw error
220220- if (!response.ok) {
221221- throw new Error(
222222- `Failed to fetch from ${endpoint}: ${response.statusText}`,
223223- );
224224- }
6666+ throw new Error(`Failed to fetch from ${endpoint}: ${response.statusText}`);
22567 }
226226-22768 const result = await response.json();
2286922970 // Cache the result for 1 hour (3600 seconds)
···24384 url += `/${season}/${episode}`;
24485 }
24586246246- // Check if Trakt is enabled
247247- if (!conf().ENABLE_TRAKT) {
248248- throw new Error("Trakt API is not enabled");
249249- }
250250-25187 // Check cache first
25288 const cacheKey: TraktCacheKey = { endpoint: url };
25389 const cachedResult = traktCache.get(cacheKey);
···25591 return cachedResult as TraktReleaseResponse;
25692 }
25793258258- // Ensure we're authenticated
259259- if (!isAuthenticated()) {
260260- await authenticateWithTurnstile();
261261- }
262262-263263- // Make the API request with authorization header
264264- const headers: Record<string, string> = {};
265265- if (authToken) {
266266- headers.Authorization = `Bearer ${authToken}`;
267267- }
268268-269269- let response = await fetch(`${TRAKT_BASE_URL}${url}`, {
270270- headers,
271271- });
272272-273273- // If request fails, try re-authenticating and retry once
9494+ // Make the API request
9595+ const response = await fetch(`${TRAKT_BASE_URL}${url}`);
27496 if (!response.ok) {
275275- // If 401, clear token and re-authenticate
276276- if (response.status === 401) {
277277- clearAuthToken();
278278- }
279279-280280- // Re-authenticate and retry
281281- await authenticateWithTurnstile();
282282-283283- // Rebuild headers after re-authentication
284284- const retryHeaders: Record<string, string> = {};
285285- if (authToken) {
286286- retryHeaders.Authorization = `Bearer ${authToken}`;
287287- }
288288-289289- response = await fetch(`${TRAKT_BASE_URL}${url}`, {
290290- headers: retryHeaders,
291291- });
292292-293293- // If retry also fails, throw error
294294- if (!response.ok) {
295295- throw new Error(
296296- `Failed to fetch release details: ${response.statusText}`,
297297- );
298298- }
9797+ throw new Error(`Failed to fetch release details: ${response.statusText}`);
29998 }
300300-30199 const result = await response.json();
302100303101 // Cache the result for 1 hour (3600 seconds)
···402200403201 const lists: CuratedMovieList[] = [];
404202405405- for (const listConfig of listConfigs) {
203203+ for (const config of listConfigs) {
406204 try {
407407- const response = await fetchFromTrakt(listConfig.endpoint);
205205+ const response = await fetchFromTrakt(config.endpoint);
408206 lists.push({
409409- listName: listConfig.name,
410410- listSlug: listConfig.slug,
207207+ listName: config.name,
208208+ listSlug: config.slug,
411209 tmdbIds: response.movie_tmdb_ids.slice(0, 30), // Limit to first 30 items
412210 count: Math.min(response.movie_tmdb_ids.length, 30), // Update count to reflect the limit
413211 });
414212 } catch (error) {
415415- console.error(`Failed to fetch ${listConfig.name}:`, error);
213213+ console.error(`Failed to fetch ${config.name}:`, error);
416214 }
417215 }
418216