diff --git a/src/extractor/ExtractorRegistry.ts b/src/extractor/ExtractorRegistry.ts index a3ec959..90b2769 100644 --- a/src/extractor/ExtractorRegistry.ts +++ b/src/extractor/ExtractorRegistry.ts @@ -9,6 +9,7 @@ import { Fsst } from './Fsst'; import { SuperVideo } from './SuperVideo'; import { KinoGer } from './KinoGer'; import { Soaper } from './Soaper'; +import { VidSrc } from './VidSrc'; import { ExternalUrl } from './ExternalUrl'; import { NotFoundError } from '../error'; @@ -26,6 +27,7 @@ export class ExtractorRegistry { new SuperVideo(fetcher), new KinoGer(fetcher), new Soaper(fetcher), + new VidSrc(fetcher), new ExternalUrl(fetcher), // fallback extractor which must come last ]; this.urlResultCache = new TTLCache({ max: 1024 }); diff --git a/src/extractor/VidSrc.ts b/src/extractor/VidSrc.ts new file mode 100644 index 0000000..12e4efd --- /dev/null +++ b/src/extractor/VidSrc.ts @@ -0,0 +1,61 @@ +import * as cheerio from 'cheerio'; +import slugify from 'slugify'; +import { Extractor } from './types'; +import { Fetcher, guessFromPlaylist } from '../utils'; +import { Context, Meta, UrlResult } from '../types'; + +export class VidSrc implements Extractor { + readonly id = 'vidsrc'; + + readonly label = 'VidSrc'; + + readonly ttl = 900000; // 15m + + private readonly fetcher: Fetcher; + + constructor(fetcher: Fetcher) { + this.fetcher = fetcher; + } + + readonly supports = (_ctx: Context, url: URL): boolean => null !== url.host.match(/vidsrc/); + + readonly normalize = (url: URL): URL => url; + + readonly extract = async (ctx: Context, url: URL, meta: Meta): Promise => { + const html = await this.fetcher.text(ctx, url); + + const $ = cheerio.load(html); + + const iframeUrl = new URL(($('#player_iframe').attr('src') as string).replace(/^\/\//, 'https://')); + const title = $('title').text().trim(); + + return Promise.all( + $('.server') + .map((_i, el) => ({ serverName: $(el).text(), dataHash: $(el).data('hash') })) + .toArray() + .filter(({ serverName }) => serverName === 'CloudStream Pro') + .map(async ({ serverName, dataHash }) => { + const iframeHtml = await this.fetcher.text(ctx, new URL(`/rcp/${dataHash}`, iframeUrl.origin)); + const srcMatch = iframeHtml.match(`src:\\s?'(.*)'`) as string[]; + + const playerHtml = await this.fetcher.text(ctx, new URL(srcMatch[1] as string, iframeUrl.origin)); + const fileMatch = playerHtml.match(`file:\\s?'(.*)'`) as string[]; + + const m3u8Url = new URL(fileMatch[1] as string); + const height = await guessFromPlaylist(ctx, this.fetcher, m3u8Url, { noReferer: true }); + + return { + url: m3u8Url, + label: `${this.label} (${serverName})`, + sourceId: `${this.id}_${slugify(serverName)}_${meta.countryCode.toLowerCase()}`, + ttl: this.ttl, + meta: { + ...meta, + title, + ...(height && { height }), + }, + }; + }), + ); + }; +} diff --git a/src/handler/VidSrc.test.ts b/src/handler/VidSrc.test.ts new file mode 100644 index 0000000..2f19448 --- /dev/null +++ b/src/handler/VidSrc.test.ts @@ -0,0 +1,29 @@ +import winston from 'winston'; +import { VidSrc } from './VidSrc'; +import { Fetcher, ImdbId } from '../utils'; +import { ExtractorRegistry } from '../extractor'; +import { Context } from '../types'; +jest.mock('../utils/Fetcher'); + +const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] }); +// @ts-expect-error No constructor args needed +const fetcher = new Fetcher(); +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { en: 'on' } }; + +describe('VidSrc', () => { + let handler: VidSrc; + + beforeEach(() => { + handler = new VidSrc(fetcher, new ExtractorRegistry(logger, fetcher)); + }); + + test('handle imdb black mirror s4e2', async () => { + const streams = await handler.handle(ctx, 'series', new ImdbId('tt2085059', 4, 2)); + expect(streams).toMatchSnapshot(); + }); + + test('handle imdb full metal jacket', async () => { + const streams = await handler.handle(ctx, 'series', new ImdbId('tt0093058', undefined, undefined)); + expect(streams).toMatchSnapshot(); + }); +}); diff --git a/src/handler/VidSrc.ts b/src/handler/VidSrc.ts new file mode 100644 index 0000000..23e0bcf --- /dev/null +++ b/src/handler/VidSrc.ts @@ -0,0 +1,37 @@ +import { ContentType } from 'stremio-addon-sdk'; +import { Handler } from './types'; +import { Fetcher, getImdbId, Id } from '../utils'; +import { ExtractorRegistry } from '../extractor'; +import { Context, CountryCode } from '../types'; + +export class VidSrc implements Handler { + readonly id = 'vidsrc'; + + readonly label = 'VidSrc'; + + readonly contentTypes: ContentType[] = ['movie', 'series']; + + readonly countryCodes: CountryCode[] = ['en']; + + private readonly baseUrl = 'https://vidsrc.xyz'; + + private readonly fetcher: Fetcher; + private readonly extractorRegistry: ExtractorRegistry; + + constructor(fetcher: Fetcher, extractorRegistry: ExtractorRegistry) { + this.fetcher = fetcher; + this.extractorRegistry = extractorRegistry; + } + + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const imdbId = await getImdbId(ctx, this.fetcher, id); + + const embedUrl = imdbId.season + ? new URL(`/embed/tv/${imdbId.id}/${imdbId.season}/${imdbId.episode}`, this.baseUrl) + : new URL(`/embed/movie/${imdbId.id}`, this.baseUrl); + + return [ + await this.extractorRegistry.handle(ctx, embedUrl, { countryCode: 'en' }), + ]; + }; +} diff --git a/src/handler/__snapshots__/VidSrc.test.ts.snap b/src/handler/__snapshots__/VidSrc.test.ts.snap new file mode 100644 index 0000000..b12cdff --- /dev/null +++ b/src/handler/__snapshots__/VidSrc.test.ts.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VidSrc handle imdb black mirror s4e2 1`] = ` +[ + [ + { + "label": "VidSrc (CloudStream Pro)", + "meta": { + "countryCode": "en", + "height": 1040, + "title": "Black Mirror (2011) S04E02", + }, + "sourceId": "vidsrc_CloudStream-Pro_en", + "ttl": 900000, + "url": "https://tmstr5.shadowlandschronicles.com/pl/H4sIAAAAAAAAAxXK226DIAAA0F8CrGvd27oCq1UcyP1NQdtUaozptsyvX3aez9DBQxgjgBmAPeqK_Yh2eTeG4tAPMCDwysHNaeDbevKVMM.XgMWps2XtTWGkLR8M.feQ2KroeeXG_db4vCq82.T8lkerZ0OTlLLsWuA2lnwT1FK7e6r0B5nEHL_4pNAgme2sJj28_kT0f0kuya0Sj6W6wONRnkrpAHP9vZwEzRE3T8JOohmwtwMlTimNeiw2BQWRsM4CKZ3PFuUyT1usLy6LWb3F1s3p0hPRSpqqxibcPpaG3.Mq5qS4vn4bqm.NTp8KFzOfmBykWP4AirgCPSEBAAA-/master.m3u8", + }, + ], +] +`; + +exports[`VidSrc handle imdb full metal jacket 1`] = ` +[ + [ + { + "label": "VidSrc (CloudStream Pro)", + "meta": { + "countryCode": "en", + "height": 714, + "title": "Full Metal Jacket (1987)", + }, + "sourceId": "vidsrc_CloudStream-Pro_en", + "ttl": 900000, + "url": "https://tmstr4.shadowlandschronicles.com/pl/H4sIAAAAAAAAAwXB7XqCIBgA0FtCsa_97IMcFSa.gPBPwc1H0Fm5Wl79zllVS4eRdZv10uJkuarrJF7Yr8at1lWyRvZDC7cFP3IT2YU4PHAmiGKB59C1Z41bbUQ7czDQ.AgpMv6csHtqNTFGzMMGessV_bOSvRkadab4riBhWx0nxuPxDtFnXMSjqAOdM5AtK3lyjjxiyD.LYAaRbqvC8_MF_L0Bsq8GeuGHxUlLU9rSZapvkSTyUfsNkcEJi8eb9jRpRPJkMTO6H4mV33cnaCqP_LeIpw46iVlHrxDp2c6G5ILtbeoC9I42itxy_3plqev50N5FLDHfTS94T1AQKXUZrmwOA1dtVIFFOaDNP7JZ1PhBAQAA/master.m3u8", + }, + ], +] +`; diff --git a/src/handler/index.ts b/src/handler/index.ts index 6899816..a7e1e8f 100644 --- a/src/handler/index.ts +++ b/src/handler/index.ts @@ -8,4 +8,5 @@ export * from './MostraGuarda'; export * from './Soaper'; export * from './StreamKiste'; export * from './VerHdLink'; +export * from './VidSrc'; export * from './types'; diff --git a/src/index.ts b/src/index.ts index 9675525..773a17a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import { import { ExtractorRegistry } from './extractor'; import { ConfigureController, ManifestController, StreamController } from './controller'; import { envGet, envIsProd, Fetcher, StreamResolver, tmdbFetch, TmdbId } from './utils'; +import { VidSrc } from './handler/VidSrc'; const logger = winston.createLogger({ transports: [ @@ -34,6 +35,7 @@ const extractorRegistry = new ExtractorRegistry(logger, fetcher); const handlers: Handler[] = [ // EN new Soaper(fetcher, extractorRegistry), + new VidSrc(fetcher, extractorRegistry), // ES / MX new CineHDPlus(fetcher, extractorRegistry), new VerHdLink(fetcher, extractorRegistry), diff --git a/src/utils/Fetcher.ts b/src/utils/Fetcher.ts index 48abd82..befd23c 100644 --- a/src/utils/Fetcher.ts +++ b/src/utils/Fetcher.ts @@ -40,8 +40,9 @@ interface FlareSolverrResult { version: string; } -type CustomRequestInit = RequestInit & { +export type CustomRequestInit = RequestInit & { noFlareSolverr?: boolean; + noReferer?: boolean; queueLimit?: number; queueErrorLimit?: number; timeout?: number; @@ -78,6 +79,8 @@ export class Fetcher { public readonly getInit = (ctx: Context, url: URL, init?: CustomRequestInit): CustomRequestInit => { const cookieString = this.cookieJar.getCookieStringSync(url.href); + const noReferer = init?.noReferer ?? false; + return { ...init, headers: { @@ -86,7 +89,7 @@ export class Fetcher { ...(cookieString && { Cookie: cookieString }), 'Forwarded': `for=${ctx.ip}`, 'Priority': 'u=0', - 'Referer': `${ctx.referer?.href ?? url.origin}`, + ...(!noReferer && { Referer: `${ctx.referer?.href ?? url.origin}` }), 'User-Agent': this.hostUserAgentMap.get(url.host) ?? 'node', 'X-Forwarded-For': ctx.ip, 'X-Forwarded-Proto': url.protocol.slice(0, -1), diff --git a/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-22dfb6243754ee59b8e62cb0c06ac720 b/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-22dfb6243754ee59b8e62cb0c06ac720 new file mode 100644 index 0000000..a2f1bb9 --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-22dfb6243754ee59b8e62cb0c06ac720 @@ -0,0 +1,748 @@ + + + +Full Metal Jacket (1987) + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-3a02f8d98afb2b2eaf85b732c4776e35 b/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-3a02f8d98afb2b2eaf85b732c4776e35 new file mode 100644 index 0000000..f03b3dc --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-3a02f8d98afb2b2eaf85b732c4776e35 @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + +
+
+
+ Full Metal Jacket (1987)
+ +
+
+ +
+ + + + + + + + + + + + + + + + + diff --git a/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-9eecc630b5a9d2845dbdce7f20cbf283 b/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-9eecc630b5a9d2845dbdce7f20cbf283 new file mode 100644 index 0000000..8ce6d89 --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-9eecc630b5a9d2845dbdce7f20cbf283 @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + +
+
+
+ Black Mirror (2011) S04E02
+ +
+
+ +
+ + + + + + + + + + + + + + + + + diff --git a/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-af0f048b02d186cb1fe183043113e979 b/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-af0f048b02d186cb1fe183043113e979 new file mode 100644 index 0000000..b630b2c --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:cloudnestra.com-af0f048b02d186cb1fe183043113e979 @@ -0,0 +1,748 @@ + + + +Black Mirror (2011) S04E02 + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/utils/__fixtures__/Fetcher/https:tmstr4.shadowlandschronicles.com-a246b190099b237f66484f918b0fd94d b/src/utils/__fixtures__/Fetcher/https:tmstr4.shadowlandschronicles.com-a246b190099b237f66484f918b0fd94d new file mode 100644 index 0000000..cbfe0eb --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:tmstr4.shadowlandschronicles.com-a246b190099b237f66484f918b0fd94d @@ -0,0 +1,6 @@ +#EXTM3U +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-STREAM-INF:BANDWIDTH=926804,CODECS="mp4a.40.2,avc1.42c01e",RESOLUTION=640x358,FRAME-RATE=24,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE +/pl/H4sIAAAAAAAAAwXB7XqCIBgA0FtCsa_97IMcFSa.gPBPwc1H0Fm5Wl79zllVS4eRdZv10uJkuarrJF7Yr8at1lWyRvZDC7cFP3IT2YU4PHAmiGKB59C1Z41bbUQ7czDQ.AgpMv6csHtqNTFGzMMGessV_bOSvRkadab4riBhWx0nxuPxDtFnXMSjqAOdM5AtK3lyjjxiyD.LYAaRbqvC8_MF_L0Bsq8GeuGHxUlLU9rSZapvkSTyUfsNkcEJi8eb9jRpRPJkMTO6H4mV33cnaCqP_LeIpw46iVlHrxDp2c6G5ILtbeoC9I42itxy_3plqev50N5FLDHfTS94T1AQKXUZrmwOA1dtVIFFOaDNP7JZ1PhBAQAA/f2a0a2e3ac935bed168e40c6c28105d7/index.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2987782,CODECS="mp4a.40.2,avc1.42c01f",RESOLUTION=1280x714,FRAME-RATE=24,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE +/pl/H4sIAAAAAAAAAwXB7XqCIBgA0FtCsa_97IMcFSa.gPBPwc1H0Fm5Wl79zllVS4eRdZv10uJkuarrJF7Yr8at1lWyRvZDC7cFP3IT2YU4PHAmiGKB59C1Z41bbUQ7czDQ.AgpMv6csHtqNTFGzMMGessV_bOSvRkadab4riBhWx0nxuPxDtFnXMSjqAOdM5AtK3lyjjxiyD.LYAaRbqvC8_MF_L0Bsq8GeuGHxUlLU9rSZapvkSTyUfsNkcEJi8eb9jRpRPJkMTO6H4mV33cnaCqP_LeIpw46iVlHrxDp2c6G5ILtbeoC9I42itxy_3plqev50N5FLDHfTS94T1AQKXUZrmwOA1dtVIFFOaDNP7JZ1PhBAQAA/ea18a1816b821e1ba4ee5d4a76b0b7cf/index.m3u8 \ No newline at end of file diff --git a/src/utils/__fixtures__/Fetcher/https:tmstr5.shadowlandschronicles.com-6e26dd536959398a2f6ade766182ee4a b/src/utils/__fixtures__/Fetcher/https:tmstr5.shadowlandschronicles.com-6e26dd536959398a2f6ade766182ee4a new file mode 100644 index 0000000..56122d5 --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:tmstr5.shadowlandschronicles.com-6e26dd536959398a2f6ade766182ee4a @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-STREAM-INF:BANDWIDTH=884887,CODECS="mp4a.40.2,avc1.42c01e",RESOLUTION=640x346,FRAME-RATE=25,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE +/pl/H4sIAAAAAAAAAxXK226DIAAA0F8CrGvd27oCq1UcyP1NQdtUaozptsyvX3aez9DBQxgjgBmAPeqK_Yh2eTeG4tAPMCDwysHNaeDbevKVMM.XgMWps2XtTWGkLR8M.feQ2KroeeXG_db4vCq82.T8lkerZ0OTlLLsWuA2lnwT1FK7e6r0B5nEHL_4pNAgme2sJj28_kT0f0kuya0Sj6W6wONRnkrpAHP9vZwEzRE3T8JOohmwtwMlTimNeiw2BQWRsM4CKZ3PFuUyT1usLy6LWb3F1s3p0hPRSpqqxibcPpaG3.Mq5qS4vn4bqm.NTp8KFzOfmBykWP4AirgCPSEBAAA-/e2f2579229bebcfbb0eafde12cbc1bf9/index.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=2911865,CODECS="mp4a.40.2,avc1.42c01f",RESOLUTION=1280x694,FRAME-RATE=25,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE +/pl/H4sIAAAAAAAAAxXK226DIAAA0F8CrGvd27oCq1UcyP1NQdtUaozptsyvX3aez9DBQxgjgBmAPeqK_Yh2eTeG4tAPMCDwysHNaeDbevKVMM.XgMWps2XtTWGkLR8M.feQ2KroeeXG_db4vCq82.T8lkerZ0OTlLLsWuA2lnwT1FK7e6r0B5nEHL_4pNAgme2sJj28_kT0f0kuya0Sj6W6wONRnkrpAHP9vZwEzRE3T8JOohmwtwMlTimNeiw2BQWRsM4CKZ3PFuUyT1usLy6LWb3F1s3p0hPRSpqqxibcPpaG3.Mq5qS4vn4bqm.NTp8KFzOfmBykWP4AirgCPSEBAAA-/0e258eeccefba231686dc455bae2c1be/index.m3u8 +#EXT-X-STREAM-INF:BANDWIDTH=5301648,CODECS="mp4a.40.2,avc1.42c028",RESOLUTION=1920x1040,FRAME-RATE=25,VIDEO-RANGE=SDR,CLOSED-CAPTIONS=NONE +/pl/H4sIAAAAAAAAAxXK226DIAAA0F8CrGvd27oCq1UcyP1NQdtUaozptsyvX3aez9DBQxgjgBmAPeqK_Yh2eTeG4tAPMCDwysHNaeDbevKVMM.XgMWps2XtTWGkLR8M.feQ2KroeeXG_db4vCq82.T8lkerZ0OTlLLsWuA2lnwT1FK7e6r0B5nEHL_4pNAgme2sJj28_kT0f0kuya0Sj6W6wONRnkrpAHP9vZwEzRE3T8JOohmwtwMlTimNeiw2BQWRsM4CKZ3PFuUyT1usLy6LWb3F1s3p0hPRSpqqxibcPpaG3.Mq5qS4vn4bqm.NTp8KFzOfmBykWP4AirgCPSEBAAA-/f4afc016a800ba375016f4bc9771890f/index.m3u8 \ No newline at end of file diff --git a/src/utils/__fixtures__/Fetcher/https:vidsrc.xyzembedmoviett0093058 b/src/utils/__fixtures__/Fetcher/https:vidsrc.xyzembedmoviett0093058 new file mode 100644 index 0000000..690c55b --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:vidsrc.xyzembedmoviett0093058 @@ -0,0 +1,268 @@ + + + +Full Metal Jacket (1987) + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + diff --git a/src/utils/__fixtures__/Fetcher/https:vidsrc.xyzembedtvtt208505942 b/src/utils/__fixtures__/Fetcher/https:vidsrc.xyzembedtvtt208505942 new file mode 100644 index 0000000..aa637a8 --- /dev/null +++ b/src/utils/__fixtures__/Fetcher/https:vidsrc.xyzembedtvtt208505942 @@ -0,0 +1,268 @@ + + + +Black Mirror (2011) S04E02 + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + diff --git a/src/utils/__mocks__/Fetcher.ts b/src/utils/__mocks__/Fetcher.ts index 494295c..2b860d9 100644 --- a/src/utils/__mocks__/Fetcher.ts +++ b/src/utils/__mocks__/Fetcher.ts @@ -1,6 +1,7 @@ import fs from 'node:fs'; import slugify from 'slugify'; import winston from 'winston'; +import crypto from 'crypto'; import { Context } from '../../types'; import { envGet } from '../env'; const { Fetcher } = jest.requireActual('../Fetcher'); @@ -13,23 +14,31 @@ class MockedFetcher { } readonly text = async (ctx: Context, url: URL, init?: RequestInit): Promise => { - const path = `${__dirname}/../__fixtures__/Fetcher/${slugify(url.href)}`; + const path = `${__dirname}/../__fixtures__/Fetcher/${this.slugifyUrl(url)}`; return this.fetch(path, ctx, url, init); }; readonly textPost = async (ctx: Context, url: URL, body: string, init?: RequestInit): Promise => { - const path = `${__dirname}/../__fixtures__/Fetcher/post-${slugify(url.href)}-${slugify(body)}`; + const path = `${__dirname}/../__fixtures__/Fetcher/post-${this.slugifyUrl(url)}-${slugify(body)}`; return this.fetch(path, ctx, url, { ...init, method: 'POST', body }); }; readonly head = async (ctx: Context, url: URL, init?: RequestInit): Promise => { - const path = `${__dirname}/../__fixtures__/Fetcher/head-${slugify(url.href)}`; + const path = `${__dirname}/../__fixtures__/Fetcher/head-${this.slugifyUrl(url)}`; return JSON.parse(await this.fetch(path, ctx, url, { ...init, method: 'HEAD' })); }; + private readonly slugifyUrl = (url: URL): string => { + if (url.href.length > 255) { + return slugify(`${url.origin}-${crypto.createHash('md5').update(url.href).digest('hex')}`); + } + + return slugify(url.href); + }; + private readonly fetch = async (path: string, ctx: Context, url: URL, init?: RequestInit): Promise => { const errorPath = `${path}.error`; diff --git a/src/utils/height.ts b/src/utils/height.ts index 86ecf7c..191a1a7 100644 --- a/src/utils/height.ts +++ b/src/utils/height.ts @@ -1,4 +1,4 @@ -import { Fetcher } from './Fetcher'; +import { CustomRequestInit, Fetcher } from './Fetcher'; import { Context } from '../types'; export const guessFromTitle = (title: string): number | undefined => { @@ -10,8 +10,8 @@ export const guessFromTitle = (title: string): number | undefined => { return undefined; }; -export const guessFromPlaylist = async (ctx: Context, fetcher: Fetcher, url: URL): Promise => { - const m3u8Data = await fetcher.text(ctx, url); +export const guessFromPlaylist = async (ctx: Context, fetcher: Fetcher, url: URL, init?: CustomRequestInit): Promise => { + const m3u8Data = await fetcher.text(ctx, url, init); const heights = Array.from(m3u8Data.matchAll(/\d+x(\d+)|(\d+)p/g)) .map(heightMatch => heightMatch[1] ?? heightMatch[2])