pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Update personalRecommendations.ts

Pas 5b73ca11 248da370

+115 -10
+115 -10
src/pages/discover/lib/personalRecommendations.ts
··· 1 - import { getRelatedMedia } from "@/backend/metadata/tmdb"; 1 + import { getMediaDetails, getRelatedMedia } from "@/backend/metadata/tmdb"; 2 2 import { TMDBContentTypes } from "@/backend/metadata/types/tmdb"; 3 3 import type { 4 + TMDBMovieData, 4 5 TMDBMovieSearchResult, 6 + TMDBShowData, 5 7 TMDBShowSearchResult, 6 8 } from "@/backend/metadata/types/tmdb"; 7 9 import type { DiscoverMedia } from "@/pages/discover/types/discover"; ··· 74 76 } 75 77 76 78 /** 79 + * Fetches similar items from the fed-similar API 80 + */ 81 + async function fetchFedSimilarItems( 82 + tmdbId: string, 83 + isTVShow: boolean, 84 + ): Promise<string[]> { 85 + try { 86 + const endpoint = isTVShow 87 + ? `https://fed-similar.up.railway.app/tv/${tmdbId}` 88 + : `https://fed-similar.up.railway.app/movie/${tmdbId}`; 89 + const response = await fetch(endpoint); 90 + if (!response.ok) return []; 91 + const items = await response.json(); 92 + return Array.isArray(items) ? items : []; 93 + } catch (error) { 94 + return []; 95 + } 96 + } 97 + 98 + /** 77 99 * Fetches personal recommendations by: 78 - * 1. Getting related media for up to MAX_HISTORY_FOR_RELATED history items, MAX_CURRENT_FOR_RELATED progress items, and MAX_BOOKMARK_FOR_RELATED bookmark 100 + * 1. Getting related media from fed-similar API and TMDB for history, progress, and bookmark items 79 101 * 2. Merging and deduping, excluding items already in history/progress/bookmarks 80 102 * 3. Adding up to MAX_BOOKMARK_REMINDERS bookmarked items as "reminders" 81 103 */ ··· 123 145 } 124 146 } 125 147 126 - const relatedPromises = sourceIds.map((id) => 148 + // Fetch from both fed-similar API and TMDB 149 + const fedSimilarPromises = sourceIds.map((id) => 150 + fetchFedSimilarItems(id, isTVShow), 151 + ); 152 + 153 + const tmdbPromises = sourceIds.map((id) => 127 154 getRelatedMedia(id, type, RELATED_PER_ITEM_LIMIT), 128 155 ); 129 156 130 - const relatedResults = await Promise.allSettled(relatedPromises); 157 + const [fedSimilarResults, tmdbResults] = await Promise.allSettled([ 158 + Promise.all(fedSimilarPromises), 159 + Promise.all(tmdbPromises), 160 + ]); 161 + 131 162 const merged: DiscoverMedia[] = []; 132 163 const seenIds = new Set<number>([]); 164 + const seenFedSimilarIds = new Set<string>(); 133 165 134 - for (const result of relatedResults) { 135 - if (result.status !== "fulfilled" || !result.value) continue; 136 - for (const item of result.value) { 137 - const idStr = String(item.id); 138 - if (excludeIds.has(idStr) || seenIds.has(item.id)) continue; 166 + // Process fed-similar results first (higher priority) 167 + if (fedSimilarResults.status === "fulfilled") { 168 + for (const fedSimilarItems of fedSimilarResults.value) { 169 + for (const tmdbId of fedSimilarItems) { 170 + if (excludeIds.has(tmdbId) || seenFedSimilarIds.has(tmdbId)) { 171 + continue; 172 + } 173 + seenFedSimilarIds.add(tmdbId); 174 + } 175 + } 176 + 177 + // Fetch full details for fed-similar items 178 + const fedSimilarDetailPromises = Array.from(seenFedSimilarIds) 179 + .slice(0, 20) 180 + .map((tmdbId) => getMediaDetails(tmdbId, type)); 181 + 182 + const fedSimilarDetails = await Promise.allSettled( 183 + fedSimilarDetailPromises, 184 + ); 185 + 186 + for (const result of fedSimilarDetails) { 187 + if (result.status !== "fulfilled" || !result.value) continue; 188 + const item = result.value as TMDBMovieData | TMDBShowData; 189 + if (seenIds.has(item.id)) continue; 139 190 seenIds.add(item.id); 140 - merged.push(toDiscoverMedia(item, isTVShow)); 191 + 192 + let searchItem: TMDBMovieSearchResult | TMDBShowSearchResult; 193 + if (isTVShow) { 194 + const showItem = item as TMDBShowData; 195 + searchItem = { 196 + adult: showItem.adult ?? false, 197 + backdrop_path: showItem.backdrop_path ?? "", 198 + id: showItem.id, 199 + name: showItem.name, 200 + original_language: showItem.original_language ?? "", 201 + original_name: showItem.original_name ?? "", 202 + overview: showItem.overview ?? "", 203 + poster_path: showItem.poster_path ?? "", 204 + media_type: TMDBContentTypes.TV, 205 + genre_ids: showItem.genres?.map((g) => g.id) ?? [], 206 + popularity: showItem.popularity ?? 0, 207 + first_air_date: showItem.first_air_date ?? "", 208 + vote_average: showItem.vote_average, 209 + vote_count: showItem.vote_count, 210 + origin_country: showItem.origin_country ?? [], 211 + }; 212 + } else { 213 + const movieItem = item as TMDBMovieData; 214 + searchItem = { 215 + adult: movieItem.adult ?? false, 216 + backdrop_path: movieItem.backdrop_path ?? "", 217 + id: movieItem.id, 218 + title: movieItem.title, 219 + original_language: movieItem.original_language ?? "", 220 + original_title: movieItem.original_title ?? "", 221 + overview: movieItem.overview ?? "", 222 + poster_path: movieItem.poster_path ?? "", 223 + media_type: TMDBContentTypes.MOVIE, 224 + genre_ids: movieItem.genres?.map((g) => g.id) ?? [], 225 + popularity: movieItem.popularity ?? 0, 226 + release_date: movieItem.release_date ?? "", 227 + video: movieItem.video ?? false, 228 + vote_average: movieItem.vote_average, 229 + vote_count: movieItem.vote_count, 230 + }; 231 + } 232 + 233 + merged.push(toDiscoverMedia(searchItem, isTVShow)); 234 + } 235 + } 236 + 237 + // Process TMDB results (lower priority) 238 + if (tmdbResults.status === "fulfilled") { 239 + for (const result of tmdbResults.value) { 240 + for (const item of result) { 241 + const idStr = String(item.id); 242 + if (excludeIds.has(idStr) || seenIds.has(item.id)) continue; 243 + seenIds.add(item.id); 244 + merged.push(toDiscoverMedia(item, isTVShow)); 245 + } 141 246 } 142 247 } 143 248