From ed9211f67dffb05c521a6acbbea7f040773cb96e Mon Sep 17 00:00:00 2001 From: TPN Date: Sun, 12 May 2024 14:32:52 +0530 Subject: [PATCH 1/7] added addMissingCaptions util --- src/utils/opensubtitles.ts | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/utils/opensubtitles.ts diff --git a/src/utils/opensubtitles.ts b/src/utils/opensubtitles.ts new file mode 100644 index 0000000..643183d --- /dev/null +++ b/src/utils/opensubtitles.ts @@ -0,0 +1,46 @@ +import { Caption, labelToLanguageCode, removeDuplicatedLanguages } from '@/providers/captions'; +import { IndividualEmbedRunnerOptions } from '@/runners/individualRunner'; +import { ProviderRunnerOptions } from '@/runners/runner'; + +export async function addMissingCaptions( + captions: Caption[], + ops: ProviderRunnerOptions | IndividualEmbedRunnerOptions, + media: string, +): Promise { + try { + const [imdbId, season, episode] = atob(media) + .split('.') + .map((x, i) => (i === 0 ? x : Number(x) || null)); + if (!imdbId) return captions; + const Res: { LanguageName: string; SubDownloadLink: string; SubFormat: 'srt' | 'vtt' }[] = await ops.proxiedFetcher( + `https://rest.opensubtitles.org/search/${season && episode ? `episode-${episode}/` : ''}imdbid-${(imdbId as string).slice(2)}${season && episode ? `/season-${season}` : ''}`, + { + headers: { + 'X-User-Agent': 'VLSub 0.10.2', + }, + }, + ); + + const Captions: Caption[] = []; + for (const caption of Res) { + const url = caption.SubDownloadLink.replace('.gz', '').replace('download/', 'download/subencoding-utf8/'); + const language = labelToLanguageCode(caption.LanguageName); + if (!url || !language) continue; + + // check if the stream already has the language + const existingCaption = captions.find((x) => x.language === language); + if (existingCaption) Captions.push(existingCaption); + else + Captions.push({ + id: url, + url, + type: caption.SubFormat || 'srt', + hasCorsRestrictions: false, + language, + }); + } + return removeDuplicatedLanguages(Captions); + } catch { + return captions; + } +} From 5689e6319e5db58c7679f269743c5f9cd0c9bd6f Mon Sep 17 00:00:00 2001 From: TPN Date: Sun, 12 May 2024 14:38:07 +0530 Subject: [PATCH 2/7] setup --- src/runners/individualRunner.ts | 26 +++++++++++++++++++++++++- src/runners/runner.ts | 20 ++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/runners/individualRunner.ts b/src/runners/individualRunner.ts index b309180..7a5d9a2 100644 --- a/src/runners/individualRunner.ts +++ b/src/runners/individualRunner.ts @@ -6,6 +6,7 @@ import { EmbedOutput, SourcererOutput } from '@/providers/base'; import { ProviderList } from '@/providers/get'; import { ScrapeContext } from '@/utils/context'; import { NotFoundError } from '@/utils/errors'; +import { addMissingCaptions } from '@/utils/opensubtitles'; import { isValidStream, validatePlayableStreams } from '@/utils/valid'; export type IndividualSourceRunnerOptions = { @@ -66,6 +67,10 @@ export async function scrapeInvidualSource( return true; }); + // opensubtitles + for (const embed of output.embeds) + embed.url = `${embed.url}${btoa('MEDIA=')}${btoa(`${ops.media.imdbId}${ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : ''}`)}`; + if ((!output.stream || output.stream.length === 0) && output.embeds.length === 0) throw new NotFoundError('No streams found'); @@ -73,6 +78,17 @@ export async function scrapeInvidualSource( if (output.stream && output.stream.length > 0 && output.embeds.length === 0) { const playableStreams = await validatePlayableStreams(output.stream, ops, sourceScraper.id); if (playableStreams.length === 0) throw new NotFoundError('No playable streams found'); + + // opensubtitles + for (const playableStream of playableStreams) { + playableStream.captions = await addMissingCaptions( + playableStream.captions, + ops, + btoa( + `${ops.media.imdbId}${ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : ''}`, + ), + ); + } output.stream = playableStreams; } return output; @@ -94,10 +110,14 @@ export async function scrapeIndividualEmbed( const embedScraper = list.embeds.find((v) => ops.id === v.id); if (!embedScraper) throw new Error('Embed with ID not found'); + let url = ops.url; + let media; + if (ops.url.includes(btoa('MEDIA='))) [url, media] = url.split(btoa('MEDIA=')); + const output = await embedScraper.scrape({ fetcher: ops.fetcher, proxiedFetcher: ops.proxiedFetcher, - url: ops.url, + url, progress(val) { ops.events?.update?.({ id: embedScraper.id, @@ -114,6 +134,10 @@ export async function scrapeIndividualEmbed( const playableStreams = await validatePlayableStreams(output.stream, ops, embedScraper.id); if (playableStreams.length === 0) throw new NotFoundError('No playable streams found'); + + if (media) + for (const playableStream of playableStreams) + playableStream.captions = await addMissingCaptions(playableStream.captions, ops, media); output.stream = playableStreams; return output; diff --git a/src/runners/runner.ts b/src/runners/runner.ts index c5f5de3..cb5b52f 100644 --- a/src/runners/runner.ts +++ b/src/runners/runner.ts @@ -8,6 +8,7 @@ import { Stream } from '@/providers/streams'; import { ScrapeContext } from '@/utils/context'; import { NotFoundError } from '@/utils/errors'; import { reorderOnIdList } from '@/utils/list'; +import { addMissingCaptions } from '@/utils/opensubtitles'; import { isValidStream, validatePlayableStream } from '@/utils/valid'; export type RunOutput = { @@ -106,6 +107,16 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt if (output.stream?.[0]) { const playableStream = await validatePlayableStream(output.stream[0], ops, source.id); if (!playableStream) throw new NotFoundError('No streams found'); + + // opensubtitles + playableStream.captions = await addMissingCaptions( + playableStream.captions, + ops, + btoa( + `${ops.media.imdbId}${ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : ''}`, + ), + ); + return { sourceId: source.id, stream: playableStream, @@ -153,6 +164,15 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt } const playableStream = await validatePlayableStream(embedOutput.stream[0], ops, embed.embedId); if (!playableStream) throw new NotFoundError('No streams found'); + + // opensubtitles + playableStream.captions = await addMissingCaptions( + playableStream.captions, + ops, + btoa( + `${ops.media.imdbId}${ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : ''}`, + ), + ); embedOutput.stream = [playableStream]; } catch (error) { const updateParams: UpdateEvent = { From 3efbee4acd7219607177855bbc60148a45471602 Mon Sep 17 00:00:00 2001 From: TPN Date: Thu, 23 May 2024 16:55:53 +0530 Subject: [PATCH 3/7] smov setup --- src/providers/captions.ts | 1 + src/runners/individualRunner.ts | 7 ++++--- src/runners/runner.ts | 6 +++--- src/utils/opensubtitles.ts | 16 ++++++++-------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/providers/captions.ts b/src/providers/captions.ts index 92e5db3..d64dcc2 100644 --- a/src/providers/captions.ts +++ b/src/providers/captions.ts @@ -9,6 +9,7 @@ export type CaptionType = keyof typeof captionTypes; export type Caption = { type: CaptionType; id: string; // only unique per stream + opensubtitles?: boolean; url: string; hasCorsRestrictions: boolean; language: string; diff --git a/src/runners/individualRunner.ts b/src/runners/individualRunner.ts index 7a5d9a2..ee0247c 100644 --- a/src/runners/individualRunner.ts +++ b/src/runners/individualRunner.ts @@ -6,7 +6,7 @@ import { EmbedOutput, SourcererOutput } from '@/providers/base'; import { ProviderList } from '@/providers/get'; import { ScrapeContext } from '@/utils/context'; import { NotFoundError } from '@/utils/errors'; -import { addMissingCaptions } from '@/utils/opensubtitles'; +import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles'; import { isValidStream, validatePlayableStreams } from '@/utils/valid'; export type IndividualSourceRunnerOptions = { @@ -81,7 +81,7 @@ export async function scrapeInvidualSource( // opensubtitles for (const playableStream of playableStreams) { - playableStream.captions = await addMissingCaptions( + playableStream.captions = await addOpenSubtitlesCaptions( playableStream.captions, ops, btoa( @@ -137,7 +137,8 @@ export async function scrapeIndividualEmbed( if (media) for (const playableStream of playableStreams) - playableStream.captions = await addMissingCaptions(playableStream.captions, ops, media); + playableStream.captions = await addOpenSubtitlesCaptions(playableStream.captions, ops, media); + output.stream = playableStreams; return output; diff --git a/src/runners/runner.ts b/src/runners/runner.ts index cb5b52f..86fbf4d 100644 --- a/src/runners/runner.ts +++ b/src/runners/runner.ts @@ -8,7 +8,7 @@ import { Stream } from '@/providers/streams'; import { ScrapeContext } from '@/utils/context'; import { NotFoundError } from '@/utils/errors'; import { reorderOnIdList } from '@/utils/list'; -import { addMissingCaptions } from '@/utils/opensubtitles'; +import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles'; import { isValidStream, validatePlayableStream } from '@/utils/valid'; export type RunOutput = { @@ -109,7 +109,7 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt if (!playableStream) throw new NotFoundError('No streams found'); // opensubtitles - playableStream.captions = await addMissingCaptions( + playableStream.captions = await addOpenSubtitlesCaptions( playableStream.captions, ops, btoa( @@ -166,7 +166,7 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt if (!playableStream) throw new NotFoundError('No streams found'); // opensubtitles - playableStream.captions = await addMissingCaptions( + playableStream.captions = await addOpenSubtitlesCaptions( playableStream.captions, ops, btoa( diff --git a/src/utils/opensubtitles.ts b/src/utils/opensubtitles.ts index 643183d..719ca02 100644 --- a/src/utils/opensubtitles.ts +++ b/src/utils/opensubtitles.ts @@ -2,7 +2,7 @@ import { Caption, labelToLanguageCode, removeDuplicatedLanguages } from '@/provi import { IndividualEmbedRunnerOptions } from '@/runners/individualRunner'; import { ProviderRunnerOptions } from '@/runners/runner'; -export async function addMissingCaptions( +export async function addOpenSubtitlesCaptions( captions: Caption[], ops: ProviderRunnerOptions | IndividualEmbedRunnerOptions, media: string, @@ -21,25 +21,25 @@ export async function addMissingCaptions( }, ); - const Captions: Caption[] = []; + const openSubtilesCaptions: Caption[] = []; for (const caption of Res) { const url = caption.SubDownloadLink.replace('.gz', '').replace('download/', 'download/subencoding-utf8/'); const language = labelToLanguageCode(caption.LanguageName); if (!url || !language) continue; - - // check if the stream already has the language - const existingCaption = captions.find((x) => x.language === language); - if (existingCaption) Captions.push(existingCaption); else - Captions.push({ + openSubtilesCaptions.push({ id: url, + opensubtitles: true, url, type: caption.SubFormat || 'srt', hasCorsRestrictions: false, language, }); } - return removeDuplicatedLanguages(Captions); + return { + ...captions, + ...removeDuplicatedLanguages(openSubtilesCaptions), + }; } catch { return captions; } From ccfd5a2da0559129a8c5ce1fe28c3bc990174bc5 Mon Sep 17 00:00:00 2001 From: TPN Date: Thu, 23 May 2024 14:29:35 +0000 Subject: [PATCH 4/7] fix --- src/utils/opensubtitles.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/opensubtitles.ts b/src/utils/opensubtitles.ts index 719ca02..bdfc795 100644 --- a/src/utils/opensubtitles.ts +++ b/src/utils/opensubtitles.ts @@ -36,10 +36,10 @@ export async function addOpenSubtitlesCaptions( language, }); } - return { + return [ ...captions, ...removeDuplicatedLanguages(openSubtilesCaptions), - }; + ]; } catch { return captions; } From f0c992f7fe9e72ad8f061faf1448955cfae751c6 Mon Sep 17 00:00:00 2001 From: TPN Date: Thu, 23 May 2024 20:05:22 +0530 Subject: [PATCH 5/7] stupid eslint --- src/utils/opensubtitles.ts | 42 ++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/utils/opensubtitles.ts b/src/utils/opensubtitles.ts index bdfc795..e380261 100644 --- a/src/utils/opensubtitles.ts +++ b/src/utils/opensubtitles.ts @@ -1,29 +1,44 @@ -import { Caption, labelToLanguageCode, removeDuplicatedLanguages } from '@/providers/captions'; -import { IndividualEmbedRunnerOptions } from '@/runners/individualRunner'; -import { ProviderRunnerOptions } from '@/runners/runner'; +import { + Caption, + labelToLanguageCode, + removeDuplicatedLanguages, +} from "@/providers/captions"; +import { IndividualEmbedRunnerOptions } from "@/runners/individualRunner"; +import { ProviderRunnerOptions } from "@/runners/runner"; export async function addOpenSubtitlesCaptions( captions: Caption[], ops: ProviderRunnerOptions | IndividualEmbedRunnerOptions, - media: string, + media: string ): Promise { try { const [imdbId, season, episode] = atob(media) - .split('.') + .split(".") .map((x, i) => (i === 0 ? x : Number(x) || null)); if (!imdbId) return captions; - const Res: { LanguageName: string; SubDownloadLink: string; SubFormat: 'srt' | 'vtt' }[] = await ops.proxiedFetcher( - `https://rest.opensubtitles.org/search/${season && episode ? `episode-${episode}/` : ''}imdbid-${(imdbId as string).slice(2)}${season && episode ? `/season-${season}` : ''}`, + const Res: { + LanguageName: string; + SubDownloadLink: string; + SubFormat: "srt" | "vtt"; + }[] = await ops.proxiedFetcher( + `https://rest.opensubtitles.org/search/${ + season && episode ? `episode-${episode}/` : "" + }imdbid-${(imdbId as string).slice(2)}${ + season && episode ? `/season-${season}` : "" + }`, { headers: { - 'X-User-Agent': 'VLSub 0.10.2', + "X-User-Agent": "VLSub 0.10.2", }, - }, + } ); const openSubtilesCaptions: Caption[] = []; for (const caption of Res) { - const url = caption.SubDownloadLink.replace('.gz', '').replace('download/', 'download/subencoding-utf8/'); + const url = caption.SubDownloadLink.replace(".gz", "").replace( + "download/", + "download/subencoding-utf8/" + ); const language = labelToLanguageCode(caption.LanguageName); if (!url || !language) continue; else @@ -31,15 +46,12 @@ export async function addOpenSubtitlesCaptions( id: url, opensubtitles: true, url, - type: caption.SubFormat || 'srt', + type: caption.SubFormat || "srt", hasCorsRestrictions: false, language, }); } - return [ - ...captions, - ...removeDuplicatedLanguages(openSubtilesCaptions), - ]; + return [...captions, ...removeDuplicatedLanguages(openSubtilesCaptions)]; } catch { return captions; } From e5d1822a713ea3b5b14172318a9d41768975e26e Mon Sep 17 00:00:00 2001 From: TPN Date: Thu, 23 May 2024 20:10:37 +0530 Subject: [PATCH 6/7] prettier --- src/providers/captions.ts | 6 +- src/runners/individualRunner.ts | 96 ++++++++++++++++++---------- src/runners/runner.ts | 109 ++++++++++++++++++++------------ 3 files changed, 134 insertions(+), 77 deletions(-) diff --git a/src/providers/captions.ts b/src/providers/captions.ts index d64dcc2..a5b5906 100644 --- a/src/providers/captions.ts +++ b/src/providers/captions.ts @@ -1,8 +1,8 @@ -import ISO6391 from 'iso-639-1'; +import ISO6391 from "iso-639-1"; export const captionTypes = { - srt: 'srt', - vtt: 'vtt', + srt: "srt", + vtt: "vtt", }; export type CaptionType = keyof typeof captionTypes; diff --git a/src/runners/individualRunner.ts b/src/runners/individualRunner.ts index ee0247c..5c1c790 100644 --- a/src/runners/individualRunner.ts +++ b/src/runners/individualRunner.ts @@ -1,13 +1,13 @@ -import { IndividualScraperEvents } from '@/entrypoint/utils/events'; -import { ScrapeMedia } from '@/entrypoint/utils/media'; -import { FeatureMap, flagsAllowedInFeatures } from '@/entrypoint/utils/targets'; -import { UseableFetcher } from '@/fetchers/types'; -import { EmbedOutput, SourcererOutput } from '@/providers/base'; -import { ProviderList } from '@/providers/get'; -import { ScrapeContext } from '@/utils/context'; -import { NotFoundError } from '@/utils/errors'; -import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles'; -import { isValidStream, validatePlayableStreams } from '@/utils/valid'; +import { IndividualScraperEvents } from "@/entrypoint/utils/events"; +import { ScrapeMedia } from "@/entrypoint/utils/media"; +import { FeatureMap, flagsAllowedInFeatures } from "@/entrypoint/utils/targets"; +import { UseableFetcher } from "@/fetchers/types"; +import { EmbedOutput, SourcererOutput } from "@/providers/base"; +import { ProviderList } from "@/providers/get"; +import { ScrapeContext } from "@/utils/context"; +import { NotFoundError } from "@/utils/errors"; +import { addOpenSubtitlesCaptions } from "@/utils/opensubtitles"; +import { isValidStream, validatePlayableStreams } from "@/utils/valid"; export type IndividualSourceRunnerOptions = { features: FeatureMap; @@ -20,12 +20,14 @@ export type IndividualSourceRunnerOptions = { export async function scrapeInvidualSource( list: ProviderList, - ops: IndividualSourceRunnerOptions, + ops: IndividualSourceRunnerOptions ): Promise { const sourceScraper = list.sources.find((v) => ops.id === v.id); - if (!sourceScraper) throw new Error('Source with ID not found'); - if (ops.media.type === 'movie' && !sourceScraper.scrapeMovie) throw new Error('Source is not compatible with movies'); - if (ops.media.type === 'show' && !sourceScraper.scrapeShow) throw new Error('Source is not compatible with shows'); + if (!sourceScraper) throw new Error("Source with ID not found"); + if (ops.media.type === "movie" && !sourceScraper.scrapeMovie) + throw new Error("Source is not compatible with movies"); + if (ops.media.type === "show" && !sourceScraper.scrapeShow) + throw new Error("Source is not compatible with shows"); const contextBase: ScrapeContext = { fetcher: ops.fetcher, @@ -34,18 +36,18 @@ export async function scrapeInvidualSource( ops.events?.update?.({ id: sourceScraper.id, percentage: val, - status: 'pending', + status: "pending", }); }, }; let output: SourcererOutput | null = null; - if (ops.media.type === 'movie' && sourceScraper.scrapeMovie) + if (ops.media.type === "movie" && sourceScraper.scrapeMovie) output = await sourceScraper.scrapeMovie({ ...contextBase, media: ops.media, }); - else if (ops.media.type === 'show' && sourceScraper.scrapeShow) + else if (ops.media.type === "show" && sourceScraper.scrapeShow) output = await sourceScraper.scrapeShow({ ...contextBase, media: ops.media, @@ -58,7 +60,7 @@ export async function scrapeInvidualSource( .filter((stream) => flagsAllowedInFeatures(ops.features, stream.flags)); } - if (!output) throw new Error('output is null'); + if (!output) throw new Error("output is null"); // filter output with only valid embeds that are not disabled output.embeds = output.embeds.filter((embed) => { @@ -69,15 +71,29 @@ export async function scrapeInvidualSource( // opensubtitles for (const embed of output.embeds) - embed.url = `${embed.url}${btoa('MEDIA=')}${btoa(`${ops.media.imdbId}${ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : ''}`)}`; + embed.url = `${embed.url}${btoa("MEDIA=")}${btoa( + `${ops.media.imdbId}${ + ops.media.type === "show" + ? `.${ops.media.season.number}.${ops.media.episode.number}` + : "" + }` + )}`; - if ((!output.stream || output.stream.length === 0) && output.embeds.length === 0) - throw new NotFoundError('No streams found'); + if ( + (!output.stream || output.stream.length === 0) && + output.embeds.length === 0 + ) + throw new NotFoundError("No streams found"); // only check for playable streams if there are streams, and if there are no embeds if (output.stream && output.stream.length > 0 && output.embeds.length === 0) { - const playableStreams = await validatePlayableStreams(output.stream, ops, sourceScraper.id); - if (playableStreams.length === 0) throw new NotFoundError('No playable streams found'); + const playableStreams = await validatePlayableStreams( + output.stream, + ops, + sourceScraper.id + ); + if (playableStreams.length === 0) + throw new NotFoundError("No playable streams found"); // opensubtitles for (const playableStream of playableStreams) { @@ -85,8 +101,12 @@ export async function scrapeInvidualSource( playableStream.captions, ops, btoa( - `${ops.media.imdbId}${ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : ''}`, - ), + `${ops.media.imdbId}${ + ops.media.type === "show" + ? `.${ops.media.season.number}.${ops.media.episode.number}` + : "" + }` + ) ); } output.stream = playableStreams; @@ -105,14 +125,15 @@ export type IndividualEmbedRunnerOptions = { export async function scrapeIndividualEmbed( list: ProviderList, - ops: IndividualEmbedRunnerOptions, + ops: IndividualEmbedRunnerOptions ): Promise { const embedScraper = list.embeds.find((v) => ops.id === v.id); - if (!embedScraper) throw new Error('Embed with ID not found'); + if (!embedScraper) throw new Error("Embed with ID not found"); let url = ops.url; let media; - if (ops.url.includes(btoa('MEDIA='))) [url, media] = url.split(btoa('MEDIA=')); + if (ops.url.includes(btoa("MEDIA="))) + [url, media] = url.split(btoa("MEDIA=")); const output = await embedScraper.scrape({ fetcher: ops.fetcher, @@ -122,7 +143,7 @@ export async function scrapeIndividualEmbed( ops.events?.update?.({ id: embedScraper.id, percentage: val, - status: 'pending', + status: "pending", }); }, }); @@ -130,14 +151,23 @@ export async function scrapeIndividualEmbed( output.stream = output.stream .filter((stream) => isValidStream(stream)) .filter((stream) => flagsAllowedInFeatures(ops.features, stream.flags)); - if (output.stream.length === 0) throw new NotFoundError('No streams found'); + if (output.stream.length === 0) throw new NotFoundError("No streams found"); - const playableStreams = await validatePlayableStreams(output.stream, ops, embedScraper.id); - if (playableStreams.length === 0) throw new NotFoundError('No playable streams found'); + const playableStreams = await validatePlayableStreams( + output.stream, + ops, + embedScraper.id + ); + if (playableStreams.length === 0) + throw new NotFoundError("No playable streams found"); if (media) for (const playableStream of playableStreams) - playableStream.captions = await addOpenSubtitlesCaptions(playableStream.captions, ops, media); + playableStream.captions = await addOpenSubtitlesCaptions( + playableStream.captions, + ops, + media + ); output.stream = playableStreams; diff --git a/src/runners/runner.ts b/src/runners/runner.ts index 86fbf4d..f05830f 100644 --- a/src/runners/runner.ts +++ b/src/runners/runner.ts @@ -1,15 +1,15 @@ -import { FullScraperEvents, UpdateEvent } from '@/entrypoint/utils/events'; -import { ScrapeMedia } from '@/entrypoint/utils/media'; -import { FeatureMap, flagsAllowedInFeatures } from '@/entrypoint/utils/targets'; -import { UseableFetcher } from '@/fetchers/types'; -import { EmbedOutput, SourcererOutput } from '@/providers/base'; -import { ProviderList } from '@/providers/get'; -import { Stream } from '@/providers/streams'; -import { ScrapeContext } from '@/utils/context'; -import { NotFoundError } from '@/utils/errors'; -import { reorderOnIdList } from '@/utils/list'; -import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles'; -import { isValidStream, validatePlayableStream } from '@/utils/valid'; +import { FullScraperEvents, UpdateEvent } from "@/entrypoint/utils/events"; +import { ScrapeMedia } from "@/entrypoint/utils/media"; +import { FeatureMap, flagsAllowedInFeatures } from "@/entrypoint/utils/targets"; +import { UseableFetcher } from "@/fetchers/types"; +import { EmbedOutput, SourcererOutput } from "@/providers/base"; +import { ProviderList } from "@/providers/get"; +import { Stream } from "@/providers/streams"; +import { ScrapeContext } from "@/utils/context"; +import { NotFoundError } from "@/utils/errors"; +import { reorderOnIdList } from "@/utils/list"; +import { addOpenSubtitlesCaptions } from "@/utils/opensubtitles"; +import { isValidStream, validatePlayableStream } from "@/utils/valid"; export type RunOutput = { sourceId: string; @@ -38,15 +38,20 @@ export type ProviderRunnerOptions = { media: ScrapeMedia; }; -export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOptions): Promise { - const sources = reorderOnIdList(ops.sourceOrder ?? [], list.sources).filter((source) => { - if (ops.media.type === 'movie') return !!source.scrapeMovie; - if (ops.media.type === 'show') return !!source.scrapeShow; - return false; - }); +export async function runAllProviders( + list: ProviderList, + ops: ProviderRunnerOptions +): Promise { + const sources = reorderOnIdList(ops.sourceOrder ?? [], list.sources).filter( + (source) => { + if (ops.media.type === "movie") return !!source.scrapeMovie; + if (ops.media.type === "show") return !!source.scrapeShow; + return false; + } + ); const embeds = reorderOnIdList(ops.embedOrder ?? [], list.embeds); const embedIds = embeds.map((embed) => embed.id); - let lastId = ''; + let lastId = ""; const contextBase: ScrapeContext = { fetcher: ops.fetcher, @@ -55,7 +60,7 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt ops.events?.update?.({ id: lastId, percentage: val, - status: 'pending', + status: "pending", }); }, }; @@ -71,12 +76,12 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt // run source scrapers let output: SourcererOutput | null = null; try { - if (ops.media.type === 'movie' && source.scrapeMovie) + if (ops.media.type === "movie" && source.scrapeMovie) output = await source.scrapeMovie({ ...contextBase, media: ops.media, }); - else if (ops.media.type === 'show' && source.scrapeShow) + else if (ops.media.type === "show" && source.scrapeShow) output = await source.scrapeShow({ ...contextBase, media: ops.media, @@ -84,16 +89,18 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt if (output) { output.stream = (output.stream ?? []) .filter(isValidStream) - .filter((stream) => flagsAllowedInFeatures(ops.features, stream.flags)); + .filter((stream) => + flagsAllowedInFeatures(ops.features, stream.flags) + ); } if (!output || (!output.stream?.length && !output.embeds.length)) { - throw new NotFoundError('No streams found'); + throw new NotFoundError("No streams found"); } } catch (error) { const updateParams: UpdateEvent = { id: source.id, percentage: 100, - status: error instanceof NotFoundError ? 'notfound' : 'failure', + status: error instanceof NotFoundError ? "notfound" : "failure", reason: error instanceof NotFoundError ? error.message : undefined, error: error instanceof NotFoundError ? undefined : error, }; @@ -101,20 +108,28 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt ops.events?.update?.(updateParams); continue; } - if (!output) throw new Error('Invalid media type'); + if (!output) throw new Error("Invalid media type"); // return stream is there are any if (output.stream?.[0]) { - const playableStream = await validatePlayableStream(output.stream[0], ops, source.id); - if (!playableStream) throw new NotFoundError('No streams found'); + const playableStream = await validatePlayableStream( + output.stream[0], + ops, + source.id + ); + if (!playableStream) throw new NotFoundError("No streams found"); // opensubtitles playableStream.captions = await addOpenSubtitlesCaptions( playableStream.captions, ops, btoa( - `${ops.media.imdbId}${ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : ''}`, - ), + `${ops.media.imdbId}${ + ops.media.type === "show" + ? `.${ops.media.season.number}.${ops.media.episode.number}` + : "" + }` + ) ); return { @@ -129,12 +144,14 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt const e = list.embeds.find((v) => v.id === embed.embedId); return e && !e.disabled; }) - .sort((a, b) => embedIds.indexOf(a.embedId) - embedIds.indexOf(b.embedId)); + .sort( + (a, b) => embedIds.indexOf(a.embedId) - embedIds.indexOf(b.embedId) + ); if (sortedEmbeds.length > 0) { ops.events?.discoverEmbeds?.({ embeds: sortedEmbeds.map((embed, i) => ({ - id: [source.id, i].join('-'), + id: [source.id, i].join("-"), embedScraperId: embed.embedId, })), sourceId: source.id, @@ -143,10 +160,10 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt for (const [ind, embed] of sortedEmbeds.entries()) { const scraper = embeds.find((v) => v.id === embed.embedId); - if (!scraper) throw new Error('Invalid embed returned'); + if (!scraper) throw new Error("Invalid embed returned"); // run embed scraper - const id = [source.id, ind].join('-'); + const id = [source.id, ind].join("-"); ops.events?.start?.(id); lastId = id; @@ -158,27 +175,37 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt }); embedOutput.stream = embedOutput.stream .filter(isValidStream) - .filter((stream) => flagsAllowedInFeatures(ops.features, stream.flags)); + .filter((stream) => + flagsAllowedInFeatures(ops.features, stream.flags) + ); if (embedOutput.stream.length === 0) { - throw new NotFoundError('No streams found'); + throw new NotFoundError("No streams found"); } - const playableStream = await validatePlayableStream(embedOutput.stream[0], ops, embed.embedId); - if (!playableStream) throw new NotFoundError('No streams found'); + const playableStream = await validatePlayableStream( + embedOutput.stream[0], + ops, + embed.embedId + ); + if (!playableStream) throw new NotFoundError("No streams found"); // opensubtitles playableStream.captions = await addOpenSubtitlesCaptions( playableStream.captions, ops, btoa( - `${ops.media.imdbId}${ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : ''}`, - ), + `${ops.media.imdbId}${ + ops.media.type === "show" + ? `.${ops.media.season.number}.${ops.media.episode.number}` + : "" + }` + ) ); embedOutput.stream = [playableStream]; } catch (error) { const updateParams: UpdateEvent = { id: source.id, percentage: 100, - status: error instanceof NotFoundError ? 'notfound' : 'failure', + status: error instanceof NotFoundError ? "notfound" : "failure", reason: error instanceof NotFoundError ? error.message : undefined, error: error instanceof NotFoundError ? undefined : error, }; From f09c99b02113c2d06df4dbd0147da3c4c56a0525 Mon Sep 17 00:00:00 2001 From: TPN Date: Thu, 23 May 2024 14:46:57 +0000 Subject: [PATCH 7/7] i hate lint --- src/providers/captions.ts | 6 +- src/runners/individualRunner.ts | 96 +++++++++++----------------- src/runners/runner.ts | 109 +++++++++++++------------------- src/utils/opensubtitles.ts | 33 ++++------ 4 files changed, 94 insertions(+), 150 deletions(-) diff --git a/src/providers/captions.ts b/src/providers/captions.ts index a5b5906..d64dcc2 100644 --- a/src/providers/captions.ts +++ b/src/providers/captions.ts @@ -1,8 +1,8 @@ -import ISO6391 from "iso-639-1"; +import ISO6391 from 'iso-639-1'; export const captionTypes = { - srt: "srt", - vtt: "vtt", + srt: 'srt', + vtt: 'vtt', }; export type CaptionType = keyof typeof captionTypes; diff --git a/src/runners/individualRunner.ts b/src/runners/individualRunner.ts index 5c1c790..da7fca9 100644 --- a/src/runners/individualRunner.ts +++ b/src/runners/individualRunner.ts @@ -1,13 +1,13 @@ -import { IndividualScraperEvents } from "@/entrypoint/utils/events"; -import { ScrapeMedia } from "@/entrypoint/utils/media"; -import { FeatureMap, flagsAllowedInFeatures } from "@/entrypoint/utils/targets"; -import { UseableFetcher } from "@/fetchers/types"; -import { EmbedOutput, SourcererOutput } from "@/providers/base"; -import { ProviderList } from "@/providers/get"; -import { ScrapeContext } from "@/utils/context"; -import { NotFoundError } from "@/utils/errors"; -import { addOpenSubtitlesCaptions } from "@/utils/opensubtitles"; -import { isValidStream, validatePlayableStreams } from "@/utils/valid"; +import { IndividualScraperEvents } from '@/entrypoint/utils/events'; +import { ScrapeMedia } from '@/entrypoint/utils/media'; +import { FeatureMap, flagsAllowedInFeatures } from '@/entrypoint/utils/targets'; +import { UseableFetcher } from '@/fetchers/types'; +import { EmbedOutput, SourcererOutput } from '@/providers/base'; +import { ProviderList } from '@/providers/get'; +import { ScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; +import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles'; +import { isValidStream, validatePlayableStreams } from '@/utils/valid'; export type IndividualSourceRunnerOptions = { features: FeatureMap; @@ -20,14 +20,12 @@ export type IndividualSourceRunnerOptions = { export async function scrapeInvidualSource( list: ProviderList, - ops: IndividualSourceRunnerOptions + ops: IndividualSourceRunnerOptions, ): Promise { const sourceScraper = list.sources.find((v) => ops.id === v.id); - if (!sourceScraper) throw new Error("Source with ID not found"); - if (ops.media.type === "movie" && !sourceScraper.scrapeMovie) - throw new Error("Source is not compatible with movies"); - if (ops.media.type === "show" && !sourceScraper.scrapeShow) - throw new Error("Source is not compatible with shows"); + if (!sourceScraper) throw new Error('Source with ID not found'); + if (ops.media.type === 'movie' && !sourceScraper.scrapeMovie) throw new Error('Source is not compatible with movies'); + if (ops.media.type === 'show' && !sourceScraper.scrapeShow) throw new Error('Source is not compatible with shows'); const contextBase: ScrapeContext = { fetcher: ops.fetcher, @@ -36,18 +34,18 @@ export async function scrapeInvidualSource( ops.events?.update?.({ id: sourceScraper.id, percentage: val, - status: "pending", + status: 'pending', }); }, }; let output: SourcererOutput | null = null; - if (ops.media.type === "movie" && sourceScraper.scrapeMovie) + if (ops.media.type === 'movie' && sourceScraper.scrapeMovie) output = await sourceScraper.scrapeMovie({ ...contextBase, media: ops.media, }); - else if (ops.media.type === "show" && sourceScraper.scrapeShow) + else if (ops.media.type === 'show' && sourceScraper.scrapeShow) output = await sourceScraper.scrapeShow({ ...contextBase, media: ops.media, @@ -60,7 +58,7 @@ export async function scrapeInvidualSource( .filter((stream) => flagsAllowedInFeatures(ops.features, stream.flags)); } - if (!output) throw new Error("output is null"); + if (!output) throw new Error('output is null'); // filter output with only valid embeds that are not disabled output.embeds = output.embeds.filter((embed) => { @@ -71,29 +69,19 @@ export async function scrapeInvidualSource( // opensubtitles for (const embed of output.embeds) - embed.url = `${embed.url}${btoa("MEDIA=")}${btoa( + embed.url = `${embed.url}${btoa('MEDIA=')}${btoa( `${ops.media.imdbId}${ - ops.media.type === "show" - ? `.${ops.media.season.number}.${ops.media.episode.number}` - : "" - }` + ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : '' + }`, )}`; - if ( - (!output.stream || output.stream.length === 0) && - output.embeds.length === 0 - ) - throw new NotFoundError("No streams found"); + if ((!output.stream || output.stream.length === 0) && output.embeds.length === 0) + throw new NotFoundError('No streams found'); // only check for playable streams if there are streams, and if there are no embeds if (output.stream && output.stream.length > 0 && output.embeds.length === 0) { - const playableStreams = await validatePlayableStreams( - output.stream, - ops, - sourceScraper.id - ); - if (playableStreams.length === 0) - throw new NotFoundError("No playable streams found"); + const playableStreams = await validatePlayableStreams(output.stream, ops, sourceScraper.id); + if (playableStreams.length === 0) throw new NotFoundError('No playable streams found'); // opensubtitles for (const playableStream of playableStreams) { @@ -102,11 +90,9 @@ export async function scrapeInvidualSource( ops, btoa( `${ops.media.imdbId}${ - ops.media.type === "show" - ? `.${ops.media.season.number}.${ops.media.episode.number}` - : "" - }` - ) + ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : '' + }`, + ), ); } output.stream = playableStreams; @@ -125,15 +111,14 @@ export type IndividualEmbedRunnerOptions = { export async function scrapeIndividualEmbed( list: ProviderList, - ops: IndividualEmbedRunnerOptions + ops: IndividualEmbedRunnerOptions, ): Promise { const embedScraper = list.embeds.find((v) => ops.id === v.id); - if (!embedScraper) throw new Error("Embed with ID not found"); + if (!embedScraper) throw new Error('Embed with ID not found'); let url = ops.url; let media; - if (ops.url.includes(btoa("MEDIA="))) - [url, media] = url.split(btoa("MEDIA=")); + if (ops.url.includes(btoa('MEDIA='))) [url, media] = url.split(btoa('MEDIA=')); const output = await embedScraper.scrape({ fetcher: ops.fetcher, @@ -143,7 +128,7 @@ export async function scrapeIndividualEmbed( ops.events?.update?.({ id: embedScraper.id, percentage: val, - status: "pending", + status: 'pending', }); }, }); @@ -151,23 +136,14 @@ export async function scrapeIndividualEmbed( output.stream = output.stream .filter((stream) => isValidStream(stream)) .filter((stream) => flagsAllowedInFeatures(ops.features, stream.flags)); - if (output.stream.length === 0) throw new NotFoundError("No streams found"); + if (output.stream.length === 0) throw new NotFoundError('No streams found'); - const playableStreams = await validatePlayableStreams( - output.stream, - ops, - embedScraper.id - ); - if (playableStreams.length === 0) - throw new NotFoundError("No playable streams found"); + const playableStreams = await validatePlayableStreams(output.stream, ops, embedScraper.id); + if (playableStreams.length === 0) throw new NotFoundError('No playable streams found'); if (media) for (const playableStream of playableStreams) - playableStream.captions = await addOpenSubtitlesCaptions( - playableStream.captions, - ops, - media - ); + playableStream.captions = await addOpenSubtitlesCaptions(playableStream.captions, ops, media); output.stream = playableStreams; diff --git a/src/runners/runner.ts b/src/runners/runner.ts index f05830f..e6dea21 100644 --- a/src/runners/runner.ts +++ b/src/runners/runner.ts @@ -1,15 +1,15 @@ -import { FullScraperEvents, UpdateEvent } from "@/entrypoint/utils/events"; -import { ScrapeMedia } from "@/entrypoint/utils/media"; -import { FeatureMap, flagsAllowedInFeatures } from "@/entrypoint/utils/targets"; -import { UseableFetcher } from "@/fetchers/types"; -import { EmbedOutput, SourcererOutput } from "@/providers/base"; -import { ProviderList } from "@/providers/get"; -import { Stream } from "@/providers/streams"; -import { ScrapeContext } from "@/utils/context"; -import { NotFoundError } from "@/utils/errors"; -import { reorderOnIdList } from "@/utils/list"; -import { addOpenSubtitlesCaptions } from "@/utils/opensubtitles"; -import { isValidStream, validatePlayableStream } from "@/utils/valid"; +import { FullScraperEvents, UpdateEvent } from '@/entrypoint/utils/events'; +import { ScrapeMedia } from '@/entrypoint/utils/media'; +import { FeatureMap, flagsAllowedInFeatures } from '@/entrypoint/utils/targets'; +import { UseableFetcher } from '@/fetchers/types'; +import { EmbedOutput, SourcererOutput } from '@/providers/base'; +import { ProviderList } from '@/providers/get'; +import { Stream } from '@/providers/streams'; +import { ScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; +import { reorderOnIdList } from '@/utils/list'; +import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles'; +import { isValidStream, validatePlayableStream } from '@/utils/valid'; export type RunOutput = { sourceId: string; @@ -38,20 +38,15 @@ export type ProviderRunnerOptions = { media: ScrapeMedia; }; -export async function runAllProviders( - list: ProviderList, - ops: ProviderRunnerOptions -): Promise { - const sources = reorderOnIdList(ops.sourceOrder ?? [], list.sources).filter( - (source) => { - if (ops.media.type === "movie") return !!source.scrapeMovie; - if (ops.media.type === "show") return !!source.scrapeShow; - return false; - } - ); +export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOptions): Promise { + const sources = reorderOnIdList(ops.sourceOrder ?? [], list.sources).filter((source) => { + if (ops.media.type === 'movie') return !!source.scrapeMovie; + if (ops.media.type === 'show') return !!source.scrapeShow; + return false; + }); const embeds = reorderOnIdList(ops.embedOrder ?? [], list.embeds); const embedIds = embeds.map((embed) => embed.id); - let lastId = ""; + let lastId = ''; const contextBase: ScrapeContext = { fetcher: ops.fetcher, @@ -60,7 +55,7 @@ export async function runAllProviders( ops.events?.update?.({ id: lastId, percentage: val, - status: "pending", + status: 'pending', }); }, }; @@ -76,12 +71,12 @@ export async function runAllProviders( // run source scrapers let output: SourcererOutput | null = null; try { - if (ops.media.type === "movie" && source.scrapeMovie) + if (ops.media.type === 'movie' && source.scrapeMovie) output = await source.scrapeMovie({ ...contextBase, media: ops.media, }); - else if (ops.media.type === "show" && source.scrapeShow) + else if (ops.media.type === 'show' && source.scrapeShow) output = await source.scrapeShow({ ...contextBase, media: ops.media, @@ -89,18 +84,16 @@ export async function runAllProviders( if (output) { output.stream = (output.stream ?? []) .filter(isValidStream) - .filter((stream) => - flagsAllowedInFeatures(ops.features, stream.flags) - ); + .filter((stream) => flagsAllowedInFeatures(ops.features, stream.flags)); } if (!output || (!output.stream?.length && !output.embeds.length)) { - throw new NotFoundError("No streams found"); + throw new NotFoundError('No streams found'); } } catch (error) { const updateParams: UpdateEvent = { id: source.id, percentage: 100, - status: error instanceof NotFoundError ? "notfound" : "failure", + status: error instanceof NotFoundError ? 'notfound' : 'failure', reason: error instanceof NotFoundError ? error.message : undefined, error: error instanceof NotFoundError ? undefined : error, }; @@ -108,16 +101,12 @@ export async function runAllProviders( ops.events?.update?.(updateParams); continue; } - if (!output) throw new Error("Invalid media type"); + if (!output) throw new Error('Invalid media type'); // return stream is there are any if (output.stream?.[0]) { - const playableStream = await validatePlayableStream( - output.stream[0], - ops, - source.id - ); - if (!playableStream) throw new NotFoundError("No streams found"); + const playableStream = await validatePlayableStream(output.stream[0], ops, source.id); + if (!playableStream) throw new NotFoundError('No streams found'); // opensubtitles playableStream.captions = await addOpenSubtitlesCaptions( @@ -125,11 +114,9 @@ export async function runAllProviders( ops, btoa( `${ops.media.imdbId}${ - ops.media.type === "show" - ? `.${ops.media.season.number}.${ops.media.episode.number}` - : "" - }` - ) + ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : '' + }`, + ), ); return { @@ -144,14 +131,12 @@ export async function runAllProviders( const e = list.embeds.find((v) => v.id === embed.embedId); return e && !e.disabled; }) - .sort( - (a, b) => embedIds.indexOf(a.embedId) - embedIds.indexOf(b.embedId) - ); + .sort((a, b) => embedIds.indexOf(a.embedId) - embedIds.indexOf(b.embedId)); if (sortedEmbeds.length > 0) { ops.events?.discoverEmbeds?.({ embeds: sortedEmbeds.map((embed, i) => ({ - id: [source.id, i].join("-"), + id: [source.id, i].join('-'), embedScraperId: embed.embedId, })), sourceId: source.id, @@ -160,10 +145,10 @@ export async function runAllProviders( for (const [ind, embed] of sortedEmbeds.entries()) { const scraper = embeds.find((v) => v.id === embed.embedId); - if (!scraper) throw new Error("Invalid embed returned"); + if (!scraper) throw new Error('Invalid embed returned'); // run embed scraper - const id = [source.id, ind].join("-"); + const id = [source.id, ind].join('-'); ops.events?.start?.(id); lastId = id; @@ -175,18 +160,12 @@ export async function runAllProviders( }); embedOutput.stream = embedOutput.stream .filter(isValidStream) - .filter((stream) => - flagsAllowedInFeatures(ops.features, stream.flags) - ); + .filter((stream) => flagsAllowedInFeatures(ops.features, stream.flags)); if (embedOutput.stream.length === 0) { - throw new NotFoundError("No streams found"); + throw new NotFoundError('No streams found'); } - const playableStream = await validatePlayableStream( - embedOutput.stream[0], - ops, - embed.embedId - ); - if (!playableStream) throw new NotFoundError("No streams found"); + const playableStream = await validatePlayableStream(embedOutput.stream[0], ops, embed.embedId); + if (!playableStream) throw new NotFoundError('No streams found'); // opensubtitles playableStream.captions = await addOpenSubtitlesCaptions( @@ -194,18 +173,16 @@ export async function runAllProviders( ops, btoa( `${ops.media.imdbId}${ - ops.media.type === "show" - ? `.${ops.media.season.number}.${ops.media.episode.number}` - : "" - }` - ) + ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : '' + }`, + ), ); embedOutput.stream = [playableStream]; } catch (error) { const updateParams: UpdateEvent = { id: source.id, percentage: 100, - status: error instanceof NotFoundError ? "notfound" : "failure", + status: error instanceof NotFoundError ? 'notfound' : 'failure', reason: error instanceof NotFoundError ? error.message : undefined, error: error instanceof NotFoundError ? undefined : error, }; diff --git a/src/utils/opensubtitles.ts b/src/utils/opensubtitles.ts index e380261..559536c 100644 --- a/src/utils/opensubtitles.ts +++ b/src/utils/opensubtitles.ts @@ -1,44 +1,35 @@ -import { - Caption, - labelToLanguageCode, - removeDuplicatedLanguages, -} from "@/providers/captions"; -import { IndividualEmbedRunnerOptions } from "@/runners/individualRunner"; -import { ProviderRunnerOptions } from "@/runners/runner"; +import { Caption, labelToLanguageCode, removeDuplicatedLanguages } from '@/providers/captions'; +import { IndividualEmbedRunnerOptions } from '@/runners/individualRunner'; +import { ProviderRunnerOptions } from '@/runners/runner'; export async function addOpenSubtitlesCaptions( captions: Caption[], ops: ProviderRunnerOptions | IndividualEmbedRunnerOptions, - media: string + media: string, ): Promise { try { const [imdbId, season, episode] = atob(media) - .split(".") + .split('.') .map((x, i) => (i === 0 ? x : Number(x) || null)); if (!imdbId) return captions; const Res: { LanguageName: string; SubDownloadLink: string; - SubFormat: "srt" | "vtt"; + SubFormat: 'srt' | 'vtt'; }[] = await ops.proxiedFetcher( `https://rest.opensubtitles.org/search/${ - season && episode ? `episode-${episode}/` : "" - }imdbid-${(imdbId as string).slice(2)}${ - season && episode ? `/season-${season}` : "" - }`, + season && episode ? `episode-${episode}/` : '' + }imdbid-${(imdbId as string).slice(2)}${season && episode ? `/season-${season}` : ''}`, { headers: { - "X-User-Agent": "VLSub 0.10.2", + 'X-User-Agent': 'VLSub 0.10.2', }, - } + }, ); const openSubtilesCaptions: Caption[] = []; for (const caption of Res) { - const url = caption.SubDownloadLink.replace(".gz", "").replace( - "download/", - "download/subencoding-utf8/" - ); + const url = caption.SubDownloadLink.replace('.gz', '').replace('download/', 'download/subencoding-utf8/'); const language = labelToLanguageCode(caption.LanguageName); if (!url || !language) continue; else @@ -46,7 +37,7 @@ export async function addOpenSubtitlesCaptions( id: url, opensubtitles: true, url, - type: caption.SubFormat || "srt", + type: caption.SubFormat || 'srt', hasCorsRestrictions: false, language, });