Create TurboVidPlay.ts
This extractor in a lot of cases uses fake PNG bytes in every ts segment header but also it uses normal links that are directly playable and no need for mediflow. The code supports both cases where it check the link if contains /data/ it playes directly if not it send it to mediaflow for more decoding. I already pr the extractor code support on mediaflow and the author merge it.
This commit is contained in:
parent
973eb06584
commit
57cd8a3d37
1 changed files with 121 additions and 0 deletions
121
src/extractor/TurboVidPlay.ts
Normal file
121
src/extractor/TurboVidPlay.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import * as cheerio from 'cheerio';
|
||||
import { NotFoundError } from '../error';
|
||||
import { Context, Format, Meta, UrlResult } from '../types';
|
||||
import {
|
||||
buildMediaFlowProxyExtractorStreamUrl,
|
||||
supportsMediaFlowProxy,
|
||||
} from '../utils';
|
||||
import { Extractor } from './Extractor';
|
||||
|
||||
export class TurboVidPlay extends Extractor {
|
||||
public readonly id = 'TurboVidPlay';
|
||||
public readonly label = 'TurboVidPlay';
|
||||
public override readonly ttl = 10800000;
|
||||
public override viaMediaFlowProxy = true;
|
||||
|
||||
private domains = [
|
||||
'turboviplay.com',
|
||||
'emturbovid.com',
|
||||
'tuborstb.co',
|
||||
'javggvideo.xyz',
|
||||
'stbturbo.xyz',
|
||||
'turbovidhls.com',
|
||||
];
|
||||
|
||||
public supports(ctx: Context, url: URL): boolean {
|
||||
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<UrlResult[]> {
|
||||
|
||||
// ---- Correct CustomRequestInit ----
|
||||
const headers: Record<string, string> = { 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')) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
// ---- 2. Extract media URL ----
|
||||
const match = html.match(/(?:urlPlay|data-hash)\s*=\s*['"](?<url>[^"']+)/);
|
||||
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 variantMatches = [...playlistText.matchAll(
|
||||
/#EXT-X-STREAM-INF[^\n]*\n([^\n]+)/g
|
||||
)];
|
||||
|
||||
for (const m of variantMatches) {
|
||||
const variantUrl = m[1];
|
||||
if (!variantUrl) continue;
|
||||
|
||||
if (variantUrl.includes('/data/')) {
|
||||
const base = new URL(masterUrl);
|
||||
|
||||
directDataStream =
|
||||
variantUrl.startsWith('http')
|
||||
? variantUrl
|
||||
: variantUrl.startsWith('/')
|
||||
? new URL(variantUrl, base.origin).href
|
||||
: new URL(variantUrl, base.href).href;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
|
||||
// ---- 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 },
|
||||
}];
|
||||
}
|
||||
|
||||
// ---- 6. OTHERWISE → pass EMBED URL to MediaFlow ----
|
||||
const proxiedUrl = await buildMediaFlowProxyExtractorStreamUrl(
|
||||
ctx,
|
||||
this.fetcher,
|
||||
this.id,
|
||||
url,
|
||||
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 },
|
||||
}];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue