chore: rework referer header passing and mediaflow proxy URL creation
This commit is contained in:
parent
447153ca3c
commit
25b326ee48
36 changed files with 103 additions and 85 deletions
|
|
@ -24,7 +24,9 @@ export class DoodStream extends Extractor {
|
|||
};
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const html = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
const passMd5Match = html.match(/\/pass_md5\/[\w-]+\/([\w-]+)/);
|
||||
if (!passMd5Match) {
|
||||
|
|
@ -33,7 +35,7 @@ export class DoodStream extends Extractor {
|
|||
|
||||
const token = passMd5Match[1] as string;
|
||||
|
||||
const baseUrl = await this.fetcher.text(ctx, new URL(passMd5Match[0], url.origin));
|
||||
const baseUrl = await this.fetcher.text(ctx, new URL(passMd5Match[0], url.origin), { headers: { Referer: url.href } });
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
const title = $('title').text().trim().replace(/ - DoodStream$/, '').trim();
|
||||
|
|
@ -44,7 +46,7 @@ export class DoodStream extends Extractor {
|
|||
mp4Url = new URL(baseUrl);
|
||||
} else {
|
||||
mp4Url = new URL(`${baseUrl}${randomstring.generate(10)}?token=${token}&expiry=${Date.now()}`);
|
||||
bytes = await guessSizeFromMp4(ctx, this.fetcher, mp4Url, { headers: { Referer: url.origin } });
|
||||
bytes = await guessSizeFromMp4(ctx, this.fetcher, mp4Url, { headers: { Referer: url.href } });
|
||||
}
|
||||
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ export class Dropload extends Extractor {
|
|||
public override readonly normalize = (url: URL): URL => new URL(url.href.replace('/d/', '/').replace('/e/', '/').replace('/embed-', '/'));
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const html = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
if (html.includes('File Not Found') || html.includes('Pending in queue')) {
|
||||
throw new NotFoundError();
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ export class ExternalUrl extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
try {
|
||||
// Make sure the URL is accessible, but avoid causing noise and delays doing this
|
||||
await this.fetcher.head(ctx, url, { noFlareSolverr: true, timeout: 1000, headers: { Referer: url.origin } });
|
||||
await this.fetcher.head(ctx, url, { noFlareSolverr: true, timeout: 1000, headers });
|
||||
} catch (error) {
|
||||
/* istanbul ignore if */
|
||||
if (!(error instanceof BlockedError)) {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export abstract class Extractor {
|
|||
error,
|
||||
label: this.label,
|
||||
sourceId: `${this.id}`,
|
||||
...(meta && meta),
|
||||
meta,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ export class Fastream extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'Fastream', url);
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'Fastream', url, headers);
|
||||
|
||||
const downloadUrl = new URL(url.href.replace('/embed-', '/d/'));
|
||||
const html = await this.fetcher.text(ctx, downloadUrl);
|
||||
const html = await this.fetcher.text(ctx, downloadUrl, { headers });
|
||||
|
||||
const heightAndSizeMatch = html.match(/\d{3,}x(\d{3,}), ([\d.]+ ?[GM]B)/) as string[];
|
||||
const titleMatch = html.match(/>Download (.*?)</) as string[];
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export class FileLions extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const headers = { ...(meta.referer && { Referer: meta.referer }) };
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'FileLions', url, headers);
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ export class FileLions extends Extractor {
|
|||
ttl: this.ttl,
|
||||
meta: {
|
||||
...meta,
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl),
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, { headers: { Referer: url.href } }),
|
||||
...(sizeMatch && {
|
||||
bytes: bytes.parse(sizeMatch[1] as string) as number,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export class Fsst extends Extractor {
|
|||
};
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const headers = { Referer: meta.referer ?? url.origin };
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const html = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ export class HubCloud extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const redirectHtml = await this.fetcher.text(ctx, url);
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const redirectHtml = await this.fetcher.text(ctx, url, { headers });
|
||||
const redirectUrlMatch = redirectHtml.match(/var url ?= ?'(.*?)'/) as string[];
|
||||
|
||||
const linksHtml = await this.fetcher.text(ctx, new URL(redirectUrlMatch[1] as string));
|
||||
const linksHtml = await this.fetcher.text(ctx, new URL(redirectUrlMatch[1] as string), { headers: { Referer: url.href } });
|
||||
const $ = cheerio.load(linksHtml);
|
||||
|
||||
const urlResults = [
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export class KinoGer extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const headers = { 'Referer': meta.referer ?? url.origin, 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36' };
|
||||
const headers = { 'Referer': meta.referer ?? url.href, 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36' };
|
||||
|
||||
const hexData = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ export class KinoGer extends Extractor {
|
|||
ttl: this.ttl,
|
||||
meta: {
|
||||
...meta,
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url, { headers }),
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url, { headers: { Referer: url.href } }),
|
||||
title,
|
||||
},
|
||||
requestHeaders: {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Mixdrop } from './Mixdrop';
|
|||
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||
const extractorRegistry = new ExtractorRegistry(logger, [new Mixdrop(new FetcherMock(`${__dirname}/__fixtures__/Mixdrop`))]);
|
||||
|
||||
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow-proxy.test', mediaFlowProxyPassword: 'asdfg' });
|
||||
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow.test.org', mediaFlowProxyPassword: 'test' });
|
||||
|
||||
describe('Mixdrop', () => {
|
||||
test('mixdrop.my /e/', async () => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import bytes from 'bytes';
|
|||
import * as cheerio from 'cheerio';
|
||||
import { NotFoundError } from '../error';
|
||||
import { Context, Format, Meta, UrlResult } from '../types';
|
||||
import { buildMediaFlowProxyExtractorRedirectUrl, supportsMediaFlowProxy } from '../utils';
|
||||
import { buildMediaFlowProxyExtractorStreamUrl, supportsMediaFlowProxy } from '../utils';
|
||||
import { Extractor } from './Extractor';
|
||||
|
||||
export class Mixdrop extends Extractor {
|
||||
|
|
@ -19,8 +19,10 @@ export class Mixdrop extends Extractor {
|
|||
public override readonly normalize = (url: URL): URL => new URL(url.href.replace('/f/', '/e/'));
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const fileUrl = new URL(url.href.replace('/e/', '/f/'));
|
||||
const html = await this.fetcher.text(ctx, fileUrl);
|
||||
const html = await this.fetcher.text(ctx, fileUrl, { headers });
|
||||
|
||||
if (/can't find the (file|video)/.test(html)) {
|
||||
throw new NotFoundError();
|
||||
|
|
@ -33,7 +35,7 @@ export class Mixdrop extends Extractor {
|
|||
|
||||
return [
|
||||
{
|
||||
url: buildMediaFlowProxyExtractorRedirectUrl(ctx, 'Mixdrop', url),
|
||||
url: await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'Mixdrop', url, headers),
|
||||
format: Format.mp4,
|
||||
label: this.label,
|
||||
sourceId: `${this.id}_${meta.countryCodes?.join('_')}`,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ export class SaveFiles extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const html = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
if (/file was locked|file was deleted/i.test(html)) {
|
||||
throw new NotFoundError();
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export class Soaper extends Extractor {
|
|||
ttl: this.ttl,
|
||||
meta: {
|
||||
...meta,
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url),
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url, { headers: { Referer: url.href } }),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ export class StreamEmbed extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const html = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
const video = JSON.parse((html.match(/video ?= ?(.*);/) as string[])[1] as string);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@ const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow.test.org',
|
|||
|
||||
describe('Streamtape', () => {
|
||||
test('streamtape.com /e', async () => {
|
||||
expect(await extractorRegistry.handle(ctx, new URL('https://streamtape.com/e/84Kb3mkoYAiokmW'))).toMatchSnapshot();
|
||||
expect(await extractorRegistry.handle(ctx, new URL('https://streamtape.com/e/DjmZBG0K9gHkJ7z/WALL-E.2008.1080p.BrRip.x264.YIFY.mp4'))).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import * as cheerio from 'cheerio';
|
||||
import { Context, Format, Meta, UrlResult } from '../types';
|
||||
import {
|
||||
buildMediaFlowProxyExtractorRedirectUrl,
|
||||
buildMediaFlowProxyExtractorStreamUrl,
|
||||
supportsMediaFlowProxy,
|
||||
} from '../utils';
|
||||
import { guessSizeFromMp4 } from '../utils/size';
|
||||
|
|
@ -21,12 +21,14 @@ export class Streamtape extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const html = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
const title = $('meta[name="og:title"]').attr('content') as string;
|
||||
|
||||
const mp4Url = buildMediaFlowProxyExtractorRedirectUrl(ctx, 'Streamtape', url);
|
||||
const mp4Url = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'Streamtape', url, headers);
|
||||
|
||||
return [
|
||||
{
|
||||
|
|
@ -38,7 +40,7 @@ export class Streamtape extends Extractor {
|
|||
meta: {
|
||||
...meta,
|
||||
title,
|
||||
bytes: await guessSizeFromMp4(ctx, this.fetcher, mp4Url),
|
||||
bytes: await guessSizeFromMp4(ctx, this.fetcher, mp4Url, { headers: { Referer: url.href } }),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export class SuperVideo extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const headers = { Referer: meta.referer ?? url.origin };
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const html = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ export class SuperVideo extends Extractor {
|
|||
const size = heightAndSizeMatch ? bytes.parse(heightAndSizeMatch[2] as string) as number : undefined;
|
||||
const height = heightAndSizeMatch
|
||||
? parseInt(heightAndSizeMatch[1] as string)
|
||||
: await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url, { headers });
|
||||
: await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url, { headers: { Referer: url.href } });
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
const title = $('.download__title').text().trim();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as cheerio from 'cheerio';
|
||||
import { Context, Format, Meta, UrlResult } from '../types';
|
||||
import { buildMediaFlowProxyExtractorRedirectUrl, supportsMediaFlowProxy } from '../utils';
|
||||
import { buildMediaFlowProxyExtractorStreamUrl, supportsMediaFlowProxy } from '../utils';
|
||||
import { guessSizeFromMp4 } from '../utils/size';
|
||||
import { Extractor } from './Extractor';
|
||||
|
||||
|
|
@ -20,14 +20,16 @@ export class Uqload extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const html = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
const heightMatch = html.match(/\d{3,}x(\d{3,})/);
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
const title = $('h1').text().trim();
|
||||
|
||||
const mp4Url = buildMediaFlowProxyExtractorRedirectUrl(ctx, 'Uqload', url);
|
||||
const mp4Url = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'Uqload', url, headers);
|
||||
|
||||
return [
|
||||
{
|
||||
|
|
@ -39,7 +41,7 @@ export class Uqload extends Extractor {
|
|||
meta: {
|
||||
...meta,
|
||||
title,
|
||||
bytes: await guessSizeFromMp4(ctx, this.fetcher, mp4Url),
|
||||
bytes: await guessSizeFromMp4(ctx, this.fetcher, mp4Url, { headers: { Referer: url.href } }),
|
||||
...(heightMatch && {
|
||||
height: parseInt(heightMatch[1] as string),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export class VidSrc extends Extractor {
|
|||
ttl: this.ttl,
|
||||
meta: {
|
||||
...meta,
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url),
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url, { headers: { Referer: iframeUrl.href } }),
|
||||
title,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Context, CountryCode, Format, Meta, UrlResult } from '../types';
|
||||
import {
|
||||
buildMediaFlowProxyExtractorStreamUrl, guessHeightFromPlaylist,
|
||||
buildMediaFlowProxyExtractorStreamUrl, CustomRequestInit, guessHeightFromPlaylist,
|
||||
hasMultiEnabled,
|
||||
iso639FromCountryCode,
|
||||
supportsMediaFlowProxy,
|
||||
|
|
@ -20,7 +20,7 @@ export class VixSrc extends Extractor {
|
|||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'VixCloud', url);
|
||||
const countryCodes = await this.determineCountryCodesFromPlaylist(ctx, playlistUrl);
|
||||
const countryCodes = await this.determineCountryCodesFromPlaylist(ctx, playlistUrl, { headers: { Referer: url.href } });
|
||||
|
||||
if (!hasMultiEnabled(ctx.config) && !countryCodes.some(countryCode => countryCode in ctx.config)) {
|
||||
return [];
|
||||
|
|
@ -35,14 +35,14 @@ export class VixSrc extends Extractor {
|
|||
ttl: this.ttl,
|
||||
meta: {
|
||||
countryCodes,
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl),
|
||||
height: await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, { headers: { Referer: url.href } }),
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
private async determineCountryCodesFromPlaylist(ctx: Context, playlistUrl: URL): Promise<CountryCode[]> {
|
||||
const playlist = await this.fetcher.text(ctx, playlistUrl);
|
||||
private async determineCountryCodesFromPlaylist(ctx: Context, playlistUrl: URL, init?: CustomRequestInit): Promise<CountryCode[]> {
|
||||
const playlist = await this.fetcher.text(ctx, playlistUrl, init);
|
||||
|
||||
const countryCodes: CountryCode[] = [CountryCode.it];
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ export class YouTube extends Extractor {
|
|||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
const headers = { Referer: meta.referer ?? url.href };
|
||||
|
||||
const html = await this.fetcher.text(ctx, url, { headers });
|
||||
|
||||
const titleMatch = html.match(/"title":{"runs":\[{"text":"(.*?)"/) as string[];
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
{"destination_url":"https://s-delivery29.mxcontent.net/v2/knq0kj8waq44l8.mp4?s=85VD8DA80IfkbMa3zZR3XA&e=1758851244&_t=1758837735","request_headers":{"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36","referer":"https://mixdrop.my/e/knq0kj8waq44l8"},"mediaflow_proxy_url":"https://mediaflow.test.org/proxy/stream","query_params":{"api_password":"test"}}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"connection":"close","content-length":"1293552945","content-range":"bytes 0-1293552944/1293552945","content-type":"video/mp4","date":"Thu, 25 Sep 2025 19:52:25 GMT","etag":"\"6878008a-4d1a0d31\"","last-modified":"Wed, 16 Jul 2025 19:42:02 GMT","server":"nginx/1.24.0 (Ubuntu)"}
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"connection":"close","content-length":"1187340184","content-range":"bytes 0-1187340183/1187340184","content-type":"video/mp4","date":"Thu, 28 Aug 2025 19:09:50 GMT","etag":"\"68acb5cc-46c55f98\"","last-modified":"Mon, 25 Aug 2025 19:13:16 GMT","server":"nginx/1.24.0 (Ubuntu)"}
|
||||
1
src/extractor/__fixtures__/Streamtape/https:mediaflow.test.org-6a49ba4b2831cab213994ba51da632c7
generated
Normal file
1
src/extractor/__fixtures__/Streamtape/https:mediaflow.test.org-6a49ba4b2831cab213994ba51da632c7
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"destination_url":"https://streamtape.com/get_video?id=DjmZBG0K9gHkJ7z&expires=1758899945&ip=F0AOKRISKxSHDN&token=uQu83SOzkLwi","request_headers":{"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36","referer":"https://streamtape.com/e/DjmZBG0K9gHkJ7z/WALL-E.2008.1080p.BrRip.x264.YIFY.mp4"},"mediaflow_proxy_url":"https://mediaflow.test.org/proxy/stream","query_params":{"api_password":"test"}}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
{"connection":"close","content-length":"431690639","content-range":"bytes 0-431690638/431690639","content-type":"video/mp4","date":"Thu, 28 Aug 2025 19:12:32 GMT","etag":"\"680029e2-19bb138f\"","last-modified":"Wed, 16 Apr 2025 22:06:26 GMT","server":"nginx/1.24.0 (Ubuntu)"}
|
||||
{"connection":"close","content-length":"431690639","content-range":"bytes 0-431690638/431690639","content-type":"video/mp4","date":"Thu, 25 Sep 2025 19:55:43 GMT","etag":"\"680029e2-19bb138f\"","last-modified":"Wed, 16 Apr 2025 22:06:26 GMT","server":"nginx/1.24.0 (Ubuntu)"}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"destination_url":"https://m60.uqload.cx/3rfkr3ep2fw2q4drdinp7ppqco2xtt5ele76uwwvy24rwjscvdv3lq62jd3a/v.mp4","request_headers":{"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36","referer":"https://uqload.net/"},"mediaflow_proxy_url":"https://mediaflow.test.org/proxy/stream","query_params":{"api_password":"test"}}
|
||||
|
|
@ -21,12 +21,14 @@ exports[`ExtractorRegistry returns external URLs if enabled by config 1`] = `
|
|||
exports[`ExtractorRegistry returns external url for error 1`] = `
|
||||
[
|
||||
{
|
||||
"countryCodes": [],
|
||||
"error": [Error: Fetcher error: 403: Forbidden
|
||||
],
|
||||
"format": "unknown",
|
||||
"isExternal": true,
|
||||
"label": "Dropload",
|
||||
"meta": {
|
||||
"countryCodes": [],
|
||||
},
|
||||
"sourceId": "dropload",
|
||||
"url": "https://dropload.io/mocked-blocked.html",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ exports[`Mixdrop mixdrop.my /e/ 1`] = `
|
|||
},
|
||||
"sourceId": "mixdrop_",
|
||||
"ttl": 900000,
|
||||
"url": "https://mediaflow-proxy.test/extractor/video?host=Mixdrop&api_password=asdfg&d=https%3A%2F%2Fmixdrop.my%2Fe%2Fknq0kj8waq44l8&redirect_stream=true",
|
||||
"url": "https://mediaflow.test.org/proxy/stream?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fmixdrop.my%2Fe%2Fknq0kj8waq44l8&d=https%3A%2F%2Fs-delivery29.mxcontent.net%2Fv2%2Fknq0kj8waq44l8.mp4%3Fs%3D85VD8DA80IfkbMa3zZR3XA%26e%3D1758851244%26_t%3D1758837735",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ exports[`Streamtape streamtape.com /e 1`] = `
|
|||
"format": "mp4",
|
||||
"label": "Streamtape (via MediaFlow Proxy)",
|
||||
"meta": {
|
||||
"bytes": 1187340184,
|
||||
"bytes": 1293552945,
|
||||
"countryCodes": [],
|
||||
"title": "mickey-17-2025-[latino].mp4",
|
||||
"title": "WALL-E.2008.1080p.BrRip.x264.YIFY.mp4",
|
||||
},
|
||||
"sourceId": "streamtape_",
|
||||
"ttl": 900000,
|
||||
"url": "https://mediaflow.test.org/extractor/video?host=Streamtape&api_password=test&d=https%3A%2F%2Fstreamtape.com%2Fe%2F84Kb3mkoYAiokmW&redirect_stream=true",
|
||||
"url": "https://mediaflow.test.org/proxy/stream?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fstreamtape.com%2Fe%2FDjmZBG0K9gHkJ7z%2FWALL-E.2008.1080p.BrRip.x264.YIFY.mp4&d=https%3A%2F%2Fstreamtape.com%2Fget_video%3Fid%3DDjmZBG0K9gHkJ7z%26expires%3D1758899945%26ip%3DF0AOKRISKxSHDN%26token%3DuQu83SOzkLwi",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ exports[`Uqload uqload.net /embed- 1`] = `
|
|||
},
|
||||
"sourceId": "uqload_",
|
||||
"ttl": 900000,
|
||||
"url": "https://mediaflow.test.org/extractor/video?host=Uqload&api_password=test&d=https%3A%2F%2Fuqload.net%2Fz0xbr87oz637.html&redirect_stream=true",
|
||||
"url": "https://mediaflow.test.org/proxy/stream?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fuqload.net%2F&d=https%3A%2F%2Fm60.uqload.cx%2F3rfkr3ep2fw2q4drdinp7ppqco2xtt5ele76uwwvy24rwjscvdv3lq62jd3a%2Fv.mp4",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ exports[`VidSrc blocking issues are retried and fail if no tlds are left 1`] = `
|
|||
"format": "unknown",
|
||||
"isExternal": true,
|
||||
"label": "VidSrc",
|
||||
"meta": {},
|
||||
"sourceId": "vidsrc",
|
||||
"url": "https://vidsrc.xyz/embed/movie/tt33043892/1/1",
|
||||
},
|
||||
|
|
@ -56,6 +57,7 @@ exports[`VidSrc rate limit issues are retried and fail if no tlds are left 1`] =
|
|||
"format": "unknown",
|
||||
"isExternal": true,
|
||||
"label": "VidSrc",
|
||||
"meta": {},
|
||||
"sourceId": "vidsrc",
|
||||
"url": "https://vidsrc.xyz/embed/movie/tt33043892/1/1",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,29 +10,22 @@ interface ExtractResult {
|
|||
|
||||
export const supportsMediaFlowProxy = (ctx: Context): boolean => !!ctx.config['mediaFlowProxyUrl'];
|
||||
|
||||
const buildMediaFlowProxyExtractorUrl = (ctx: Context, host: string, url: URL): URL => {
|
||||
const buildMediaFlowProxyExtractorUrl = (ctx: Context, host: string, url: URL, headers: Record<string, string>): URL => {
|
||||
const mediaFlowProxyUrl = new URL('/extractor/video', ctx.config.mediaFlowProxyUrl);
|
||||
|
||||
mediaFlowProxyUrl.searchParams.append('host', host);
|
||||
mediaFlowProxyUrl.searchParams.append('api_password', `${ctx.config.mediaFlowProxyPassword}`);
|
||||
mediaFlowProxyUrl.searchParams.append('d', url.href);
|
||||
|
||||
return mediaFlowProxyUrl;
|
||||
};
|
||||
|
||||
export const buildMediaFlowProxyExtractorRedirectUrl = (ctx: Context, host: string, url: URL): URL => {
|
||||
const mediaFlowProxyUrl = buildMediaFlowProxyExtractorUrl(ctx, host, url);
|
||||
|
||||
mediaFlowProxyUrl.searchParams.append('redirect_stream', 'true');
|
||||
for (const headerKey in headers) {
|
||||
mediaFlowProxyUrl.searchParams.set('h_' + headerKey.toLowerCase(), headers[headerKey] as string);
|
||||
}
|
||||
|
||||
return mediaFlowProxyUrl;
|
||||
};
|
||||
|
||||
export const buildMediaFlowProxyExtractorStreamUrl = async (ctx: Context, fetcher: Fetcher, host: string, url: URL, headers: Record<string, string> = {}): Promise<URL> => {
|
||||
const mediaFlowProxyUrl = buildMediaFlowProxyExtractorUrl(ctx, host, url);
|
||||
for (const headerKey in headers) {
|
||||
mediaFlowProxyUrl.searchParams.set('h_' + headerKey.toLowerCase(), headers[headerKey] as string);
|
||||
}
|
||||
const mediaFlowProxyUrl = buildMediaFlowProxyExtractorUrl(ctx, host, url, headers);
|
||||
|
||||
const extractResult: ExtractResult = await fetcher.json(ctx, mediaFlowProxyUrl);
|
||||
|
||||
|
|
@ -44,9 +37,6 @@ export const buildMediaFlowProxyExtractorStreamUrl = async (ctx: Context, fetche
|
|||
for (const requestHeadersKey in extractResult.request_headers) {
|
||||
streamUrl.searchParams.append(`h_${requestHeadersKey}`, extractResult.request_headers[requestHeadersKey] as string);
|
||||
}
|
||||
for (const headerKey in headers) {
|
||||
streamUrl.searchParams.set('h_' + headerKey.toLowerCase(), headers[headerKey] as string);
|
||||
}
|
||||
streamUrl.searchParams.append('d', extractResult.destination_url);
|
||||
|
||||
return streamUrl;
|
||||
|
|
|
|||
Loading…
Reference in a new issue