diff --git a/src/extractor/TurboVidPlay.ts b/src/extractor/TurboVidPlay.ts index 9344057..5ec534b 100644 --- a/src/extractor/TurboVidPlay.ts +++ b/src/extractor/TurboVidPlay.ts @@ -23,44 +23,62 @@ export class TurboVidPlay extends Extractor { ]; public supports(ctx: Context, url: URL): boolean { - return this.domains.some(d => url.host.includes(d)) && - supportsMediaFlowProxy(ctx); + return ( + this.domains.some(d => url.host.includes(d)) + && supportsMediaFlowProxy(ctx) + ); } public override normalize(url: URL): URL { return new URL(url.href.replace(/\/(e|d|embed-)\//, '/t/')); } - protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise { + protected async extractInternal( + ctx: Context, + url: URL, + meta: Meta, + ): Promise { + const headers: Record = { + Referer: url.origin, + }; - // ---- Correct CustomRequestInit ---- - const headers: Record = { Referer: url.origin }; - - // ---- 1. Fetch embed HTML ---- const html = await this.fetcher.text(ctx, url, headers); - if (!html || html.includes('File Not Found') || html.includes('Pending in queue')) { + if ( + !html + || html.includes('File Not Found') + || html.includes('Pending in queue') + ) { throw new NotFoundError(); } - // ---- 2. Extract media URL ---- const match = html.match(/(?:urlPlay|data-hash)\s*=\s*['"](?[^"']+)/); - const mediaUrl = match?.groups?.['url']; - if (!mediaUrl) throw new NotFoundError('Video link not found'); - const masterUrl = - mediaUrl.startsWith('//') ? 'https:' + mediaUrl : - mediaUrl.startsWith('/') ? url.origin + mediaUrl : - mediaUrl; + const mediaUrl + = match?.groups?.url; + + if (!mediaUrl) { + throw new NotFoundError('Video link not found'); + } + + const masterUrl + = mediaUrl.startsWith('//') + ? `https:${mediaUrl}` + : mediaUrl.startsWith('/') + ? url.origin + mediaUrl + : mediaUrl; - // ---- 3. Look for /data/ variants ---- let directDataStream: string | null = null; try { - const playlistText = await this.fetcher.text(ctx, new URL(masterUrl), headers); + const playlistText = await this.fetcher.text( + ctx, + new URL(masterUrl), + headers, + ); - const variantMatches = [...playlistText.matchAll( - /#EXT-X-STREAM-INF[^\n]*\n([^\n]+)/g - )]; + const variantMatches = [ + ...playlistText.matchAll(/#EXT-X-STREAM-INF[^\n]*\n([^\n]+)/g), + ]; for (const m of variantMatches) { const variantUrl = m[1]; @@ -69,8 +87,8 @@ export class TurboVidPlay extends Extractor { if (variantUrl.includes('/data/')) { const base = new URL(masterUrl); - directDataStream = - variantUrl.startsWith('http') + directDataStream + = variantUrl.startsWith('http') ? variantUrl : variantUrl.startsWith('/') ? new URL(variantUrl, base.origin).href @@ -80,42 +98,50 @@ export class TurboVidPlay extends Extractor { } } } catch { + // intentionally ignored } - // ---- 4. Title ---- const $ = cheerio.load(html); const title = $('title').text().trim() || this.label; - // ---- 5. DIRECT if /data/ found ---- if (directDataStream) { - return [{ - url: new URL(directDataStream), - format: Format.hls, - label: this.label, - sourceId: `${this.id}_${meta.countryCodes?.join('_') ?? 'all'}`, - ttl: this.ttl, - requestHeaders: headers, - meta: { ...meta, title }, - }]; + return [ + { + url: new URL(directDataStream), + format: Format.hls, + label: this.label, + sourceId: `${this.id}_${meta.countryCodes?.join('_') ?? 'all'}`, + ttl: this.ttl, + requestHeaders: headers, + meta: { + ...meta, + title, + }, + }, + ]; } - // ---- 6. OTHERWISE → pass EMBED URL to MediaFlow ---- const proxiedUrl = await buildMediaFlowProxyExtractorStreamUrl( ctx, this.fetcher, this.id, url, - headers + headers, ); - return [{ - url: proxiedUrl, - format: Format.hls, - label: this.label, - sourceId: `${this.id}_${meta.countryCodes?.join('_') ?? 'all'}`, - ttl: this.ttl, - requestHeaders: headers, - meta: { ...meta, title }, - }]; + return [ + { + url: proxiedUrl, + format: Format.hls, + label: this.label, + sourceId: `${this.id}_${meta.countryCodes?.join('_') ?? 'all'}`, + ttl: this.ttl, + requestHeaders: headers, + meta: { + ...meta, + title, + }, + }, + ]; } }