tangled
alpha
login
or
join now
dunkirk.sh
/
pstream-ng
1
fork
atom
pstream is dead; long live pstream
taciturnaxolotl.github.io/pstream-ng/
1
fork
atom
overview
issues
pulls
pipelines
new trakt /discover endpoint for featured carousel
Pas
9 months ago
eaf23995
0f1ce281
+105
-45
2 changed files
expand all
collapse all
unified
split
src
backend
metadata
traktApi.ts
pages
discover
components
FeaturedCarousel.tsx
+13
-1
src/backend/metadata/traktApi.ts
···
29
29
30
30
export const TRAKT_BASE_URL = "https://fed-airdate.pstream.org";
31
31
32
32
+
export interface TraktDiscoverResponse {
33
33
+
movie_tmdb_ids: number[];
34
34
+
tv_tmdb_ids: number[];
35
35
+
count: number;
36
36
+
}
37
37
+
32
38
// Pagination utility
33
39
export function paginateResults(
34
40
results: TraktLatestResponse,
···
47
53
}
48
54
49
55
// Base function to fetch from Trakt API
50
50
-
async function fetchFromTrakt(endpoint: string): Promise<TraktLatestResponse> {
56
56
+
async function fetchFromTrakt<T = TraktLatestResponse>(
57
57
+
endpoint: string,
58
58
+
): Promise<T> {
51
59
const response = await fetch(`${TRAKT_BASE_URL}${endpoint}`);
52
60
if (!response.ok) {
53
61
throw new Error(`Failed to fetch from ${endpoint}: ${response.statusText}`);
···
93
101
// Popular content
94
102
export const getPopularTVShows = () => fetchFromTrakt("/populartv");
95
103
export const getPopularMovies = () => fetchFromTrakt("/popularmovies");
104
104
+
105
105
+
// Discovery content
106
106
+
export const getDiscoverContent = () =>
107
107
+
fetchFromTrakt<TraktDiscoverResponse>("/discover");
96
108
97
109
// Type conversion utilities
98
110
export function convertToMediaType(type: TraktContentType): MWMediaType {
+92
-44
src/pages/discover/components/FeaturedCarousel.tsx
···
8
8
import { get, getMediaLogo } from "@/backend/metadata/tmdb";
9
9
import {
10
10
TraktReleaseResponse,
11
11
+
getDiscoverContent,
11
12
getReleaseDetails,
12
13
} from "@/backend/metadata/traktApi";
13
14
import { TMDBContentTypes } from "@/backend/metadata/types/tmdb";
···
202
203
logoFetchController.current.abort(); // Cancel any in-progress logo fetches
203
204
}
204
205
try {
205
205
-
if (effectiveCategory === "movies") {
206
206
-
// First get the list of popular movies
207
207
-
const listData = await get<any>("/movie/popular", {
208
208
-
api_key: conf().TMDB_READ_API_KEY,
209
209
-
language: formattedLanguage,
210
210
-
});
206
206
+
if (effectiveCategory === "movies" || effectiveCategory === "tvshows") {
207
207
+
// First try to get IDs from Trakt discover endpoint
208
208
+
try {
209
209
+
const discoverData = await getDiscoverContent();
211
210
212
212
-
// Then fetch full details for each movie to get external_ids
213
213
-
const moviePromises = listData.results
214
214
-
.slice(0, FETCH_QUANTITY)
215
215
-
.map((movie: any) =>
216
216
-
get<any>(`/movie/${movie.id}`, {
211
211
+
let tmdbIds: number[] = [];
212
212
+
if (effectiveCategory === "movies") {
213
213
+
tmdbIds = discoverData.movie_tmdb_ids;
214
214
+
} else {
215
215
+
tmdbIds = discoverData.tv_tmdb_ids;
216
216
+
}
217
217
+
218
218
+
// Then fetch full details for each movie/show to get external_ids
219
219
+
const detailPromises = tmdbIds.map((id) =>
220
220
+
get<any>(
221
221
+
`/${effectiveCategory === "movies" ? "movie" : "tv"}/${id}`,
222
222
+
{
223
223
+
api_key: conf().TMDB_READ_API_KEY,
224
224
+
language: formattedLanguage,
225
225
+
append_to_response: "external_ids",
226
226
+
},
227
227
+
),
228
228
+
);
229
229
+
230
230
+
const details = await Promise.all(detailPromises);
231
231
+
const mediaItems = details.map((item) => ({
232
232
+
...item,
233
233
+
type:
234
234
+
effectiveCategory === "movies" ? "movie" : ("show" as const),
235
235
+
}));
236
236
+
237
237
+
// Take the first SLIDE_QUANTITY items
238
238
+
setMedia(mediaItems.slice(0, SLIDE_QUANTITY));
239
239
+
} catch (traktError) {
240
240
+
console.error(
241
241
+
"Falling back to TMDB method",
242
242
+
"Error fetching from Trakt discover:",
243
243
+
traktError,
244
244
+
);
245
245
+
246
246
+
// Fallback to TMDB method
247
247
+
if (effectiveCategory === "movies") {
248
248
+
// First get the list of popular movies
249
249
+
const listData = await get<any>("/movie/popular", {
217
250
api_key: conf().TMDB_READ_API_KEY,
218
251
language: formattedLanguage,
219
219
-
append_to_response: "external_ids",
220
220
-
}),
221
221
-
);
252
252
+
});
222
253
223
223
-
const movieDetails = await Promise.all(moviePromises);
224
224
-
const allMovies = movieDetails.map((movie) => ({
225
225
-
...movie,
226
226
-
type: "movie" as const,
227
227
-
}));
254
254
+
// Then fetch full details for each movie to get external_ids
255
255
+
const moviePromises = listData.results
256
256
+
.slice(0, FETCH_QUANTITY)
257
257
+
.map((movie: any) =>
258
258
+
get<any>(`/movie/${movie.id}`, {
259
259
+
api_key: conf().TMDB_READ_API_KEY,
260
260
+
language: formattedLanguage,
261
261
+
append_to_response: "external_ids",
262
262
+
}),
263
263
+
);
228
264
229
229
-
// Shuffle
230
230
-
const shuffledMovies = [...allMovies].sort(() => 0.5 - Math.random());
231
231
-
setMedia(shuffledMovies.slice(0, SLIDE_QUANTITY));
232
232
-
} else if (effectiveCategory === "tvshows") {
233
233
-
// First get the list of popular shows
234
234
-
const listData = await get<any>("/tv/popular", {
235
235
-
api_key: conf().TMDB_READ_API_KEY,
236
236
-
language: formattedLanguage,
237
237
-
});
265
265
+
const movieDetails = await Promise.all(moviePromises);
266
266
+
const allMovies = movieDetails.map((movie) => ({
267
267
+
...movie,
268
268
+
type: "movie" as const,
269
269
+
}));
238
270
239
239
-
// Then fetch full details for each show to get external_ids
240
240
-
const showPromises = listData.results
241
241
-
.slice(0, FETCH_QUANTITY)
242
242
-
.map((show: any) =>
243
243
-
get<any>(`/tv/${show.id}`, {
271
271
+
// Shuffle
272
272
+
const shuffledMovies = [...allMovies].sort(
273
273
+
() => 0.5 - Math.random(),
274
274
+
);
275
275
+
setMedia(shuffledMovies.slice(0, SLIDE_QUANTITY));
276
276
+
} else if (effectiveCategory === "tvshows") {
277
277
+
// First get the list of popular shows
278
278
+
const listData = await get<any>("/tv/popular", {
244
279
api_key: conf().TMDB_READ_API_KEY,
245
280
language: formattedLanguage,
246
246
-
append_to_response: "external_ids",
247
247
-
}),
248
248
-
);
281
281
+
});
282
282
+
283
283
+
// Then fetch full details for each show to get external_ids
284
284
+
const showPromises = listData.results
285
285
+
.slice(0, FETCH_QUANTITY)
286
286
+
.map((show: any) =>
287
287
+
get<any>(`/tv/${show.id}`, {
288
288
+
api_key: conf().TMDB_READ_API_KEY,
289
289
+
language: formattedLanguage,
290
290
+
append_to_response: "external_ids",
291
291
+
}),
292
292
+
);
249
293
250
250
-
const showDetails = await Promise.all(showPromises);
251
251
-
const allShows = showDetails.map((show) => ({
252
252
-
...show,
253
253
-
type: "show" as const,
254
254
-
}));
294
294
+
const showDetails = await Promise.all(showPromises);
295
295
+
const allShows = showDetails.map((show) => ({
296
296
+
...show,
297
297
+
type: "show" as const,
298
298
+
}));
255
299
256
256
-
// Shuffle
257
257
-
const shuffledShows = [...allShows].sort(() => 0.5 - Math.random());
258
258
-
setMedia(shuffledShows.slice(0, SLIDE_QUANTITY));
300
300
+
// Shuffle
301
301
+
const shuffledShows = [...allShows].sort(
302
302
+
() => 0.5 - Math.random(),
303
303
+
);
304
304
+
setMedia(shuffledShows.slice(0, SLIDE_QUANTITY));
305
305
+
}
306
306
+
}
259
307
} else if (effectiveCategory === "editorpicks") {
260
308
// Shuffle editor picks Ids
261
309
const allMovieIds = EDITOR_PICKS_MOVIES.map((item) => ({