diff --git a/src/utils/externalSubtitles.ts b/src/utils/externalSubtitles.ts index d3b33819..ffaf75ac 100644 --- a/src/utils/externalSubtitles.ts +++ b/src/utils/externalSubtitles.ts @@ -212,6 +212,88 @@ export async function scrapeOpenSubtitlesCaptions( } } +export async function scrapeFebboxCaptions( + imdbId: string, + season?: number, + episode?: number, +): Promise { + try { + let url: string; + if (season && episode) { + url = `https://fed-subs.pstream.mov/tv/${imdbId}/s${season}/e${episode}`; + } else { + url = `https://fed-subs.pstream.mov/movie/${imdbId}`; + } + + // console.log("Searching Febbox subtitles with URL:", url); + + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Febbox API returned ${response.status}`); + } + + const data = await response.json(); + + // Check for error response + if (data.error) { + console.log("Febbox API error:", data.error); + return []; + } + + // Check if subtitles exist + if (!data.subtitles || typeof data.subtitles !== "object") { + console.log("No subtitles found in Febbox response"); + return []; + } + + const febboxCaptions: CaptionListItem[] = []; + + // Iterate through all available languages + for (const [languageName, subtitleData] of Object.entries(data.subtitles)) { + if (typeof subtitleData === "object" && subtitleData !== null) { + const subtitle = subtitleData as { + subtitle_link: string; + subtitle_name: string; + }; + + if (subtitle.subtitle_link) { + const language = labelToLanguageCode(languageName); + const fileExtension = subtitle.subtitle_link + .split(".") + .pop() + ?.toLowerCase(); + + // Determine subtitle type based on file extension + let type: string = "srt"; + if (fileExtension === "vtt") { + type = "vtt"; + } else if (fileExtension === "sub") { + type = "sub"; + } + + febboxCaptions.push({ + id: subtitle.subtitle_link, + language, + url: subtitle.subtitle_link, + type, + needsProxy: false, + opensubtitles: true, + display: subtitle.subtitle_name, + source: "febbox", + }); + } + } + } + + console.log(`Found ${febboxCaptions.length} Febbox subtitles`); + return febboxCaptions; + } catch (error) { + console.error("Error fetching Febbox subtitles:", error); + return []; + } +} + export async function scrapeExternalSubtitles( meta: PlayerMeta, ): Promise { @@ -227,25 +309,32 @@ export async function scrapeExternalSubtitles( const episode = meta.episode?.number; const tmdbId = meta.tmdbId; - // Fetch both Wyzie and OpenSubtitles captions with timeouts - const [wyzieCaptions, openSubsCaptions] = await Promise.all([ - Promise.race([ - scrapeWyzieCaptions(tmdbId, imdbId, season, episode), - timeout(2000, "Wyzie"), - ]), - Promise.race([ - scrapeOpenSubtitlesCaptions(imdbId, season, episode), - timeout(5000, "OpenSubtitles"), - ]), - ]); + // Fetch Wyzie, OpenSubtitles, and Febbox captions with timeouts + const [wyzieCaptions, openSubsCaptions, febboxCaptions] = await Promise.all( + [ + Promise.race([ + scrapeWyzieCaptions(tmdbId, imdbId, season, episode), + timeout(2000, "Wyzie"), + ]), + Promise.race([ + scrapeOpenSubtitlesCaptions(imdbId, season, episode), + timeout(5000, "OpenSubtitles"), + ]), + Promise.race([ + scrapeFebboxCaptions(imdbId, season, episode), + timeout(3000, "Febbox"), + ]), + ], + ); const allCaptions: CaptionListItem[] = []; if (wyzieCaptions) allCaptions.push(...wyzieCaptions); if (openSubsCaptions) allCaptions.push(...openSubsCaptions); + if (febboxCaptions) allCaptions.push(...febboxCaptions); console.log( - `Found ${allCaptions.length} external captions (Wyzie: ${wyzieCaptions?.length || 0}, OpenSubtitles: ${openSubsCaptions?.length || 0})`, + `Found ${allCaptions.length} external captions (Wyzie: ${wyzieCaptions?.length || 0}, OpenSubtitles: ${openSubsCaptions?.length || 0}, Febbox: ${febboxCaptions?.length || 0})`, ); return allCaptions;