feat(extractor): implement VixSrc via MediaFlow Proxy
This commit is contained in:
parent
7ea57222d9
commit
f12e3c5bd8
18 changed files with 356 additions and 11 deletions
21
src/extractor/VixSrc.test.ts
Normal file
21
src/extractor/VixSrc.test.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import winston from 'winston';
|
||||
import { FetcherMock } from '../utils';
|
||||
import { CountryCode } from '../types';
|
||||
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||
import { VixSrc } from './VixSrc';
|
||||
import { createTestContext } from '../test';
|
||||
|
||||
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||
const extractorRegistry = new ExtractorRegistry(logger, [new VixSrc(new FetcherMock(`${__dirname}/__fixtures__/VixSrc`))]);
|
||||
|
||||
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow-proxy.test', mediaFlowProxyPassword: 'asdfg' });
|
||||
|
||||
describe('VixSrc', () => {
|
||||
test('Full Metal Jacket', async () => {
|
||||
expect(await extractorRegistry.handle(ctx, new URL('https://vixsrc.to/movie/600'), CountryCode.it, 'Full Metal Jacket (1987)')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Black Mirror', async () => {
|
||||
expect(await extractorRegistry.handle(ctx, new URL('https://vixsrc.to/tv/42009/4/2'), CountryCode.it, 'Black Mirror 4x2')).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
48
src/extractor/VixSrc.ts
Normal file
48
src/extractor/VixSrc.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import bytes from 'bytes';
|
||||
import { Extractor } from './Extractor';
|
||||
import { Fetcher, buildMediaFlowProxyExtractorStreamUrl, supportsMediaFlowProxy } from '../utils';
|
||||
import { Context, CountryCode, Format, UrlResult } from '../types';
|
||||
|
||||
export class VixSrc extends Extractor {
|
||||
public readonly id = 'vixsrc';
|
||||
|
||||
public readonly label = 'VixSrc (via MediaFlow Proxy)';
|
||||
|
||||
public override readonly ttl = 0;
|
||||
|
||||
private readonly fetcher: Fetcher;
|
||||
|
||||
public constructor(fetcher: Fetcher) {
|
||||
super();
|
||||
|
||||
this.fetcher = fetcher;
|
||||
}
|
||||
|
||||
public supports(ctx: Context, url: URL): boolean {
|
||||
return null !== url.host.match(/vixsrc/) && supportsMediaFlowProxy(ctx);
|
||||
}
|
||||
|
||||
protected async extractInternal(ctx: Context, url: URL, countryCode: CountryCode): Promise<UrlResult[]> {
|
||||
const html = await this.fetcher.text(ctx, url);
|
||||
|
||||
const filenameMatch = html.match(/"filename":"(.*?)"/);
|
||||
const sizeMatch = html.match(/"size":(\d+)/);
|
||||
const qualityMatch = html.match(/"quality":(\d+)/);
|
||||
|
||||
return [
|
||||
{
|
||||
url: await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'VixCloud', url),
|
||||
format: Format.hls,
|
||||
label: this.label,
|
||||
sourceId: `${this.id}_${countryCode}`,
|
||||
ttl: this.ttl,
|
||||
meta: {
|
||||
countryCode,
|
||||
...(filenameMatch && { title: filenameMatch[1] }),
|
||||
...(sizeMatch && { bytes: bytes.parse(`${sizeMatch[1]} kb`) as number }),
|
||||
...(qualityMatch && { height: parseInt(qualityMatch[1] as string) }),
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"destination_url":"https://vixsrc.to/playlist/219736?token=689cf5587c2fec67fd83503a2a7dfd64&expires=1755949827&h=1","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://vixsrc.to/movie/600"},"mediaflow_proxy_url":"https://mediaflow-proxy.test/proxy/hls/manifest.m3u8","query_params":{"api_password":"asdfg"}}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"destination_url":"https://vixsrc.to/playlist/307316?token=2f5004a3b15f4ccdb3446f2b132a0957&expires=1755949828&h=1","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://vixsrc.to/tv/42009/4/2"},"mediaflow_proxy_url":"https://mediaflow-proxy.test/proxy/hls/manifest.m3u8","query_params":{"api_password":"asdfg"}}
|
||||
63
src/extractor/__fixtures__/VixSrc/https:vixsrc.tomovie600
generated
Normal file
63
src/extractor/__fixtures__/VixSrc/https:vixsrc.tomovie600
generated
Normal file
File diff suppressed because one or more lines are too long
63
src/extractor/__fixtures__/VixSrc/https:vixsrc.totv4200942
generated
Normal file
63
src/extractor/__fixtures__/VixSrc/https:vixsrc.totv4200942
generated
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -14,7 +14,7 @@ exports[`Mixdrop mixdrop.my /e/ 1`] = `
|
|||
},
|
||||
"sourceId": "mixdrop_de",
|
||||
"ttl": 0,
|
||||
"url": "https://mediaflow-proxy.test/extractor/video?host=Mixdrop&redirect_stream=true&api_password=asdfg&d=https%3A%2F%2Fmixdrop.my%2Fe%2Fknq0kj8waq44l8",
|
||||
"url": "https://mediaflow-proxy.test/extractor/video?host=Mixdrop&api_password=asdfg&d=https%3A%2F%2Fmixdrop.my%2Fe%2Fknq0kj8waq44l8&redirect_stream=true",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ exports[`Uqload uqload.net / 1`] = `
|
|||
},
|
||||
"sourceId": "uqload_fr",
|
||||
"ttl": 0,
|
||||
"url": "https://mediaflow-proxy.test/extractor/video?host=Uqload&redirect_stream=true&api_password=asdfg&d=https%3A%2F%2Fuqload.net%2Fz0xbr87oz637.html",
|
||||
"url": "https://mediaflow-proxy.test/extractor/video?host=Uqload&api_password=asdfg&d=https%3A%2F%2Fuqload.net%2Fz0xbr87oz637.html&redirect_stream=true",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
@ -29,7 +29,7 @@ exports[`Uqload uqload.net /embed- 1`] = `
|
|||
},
|
||||
"sourceId": "uqload_fr",
|
||||
"ttl": 0,
|
||||
"url": "https://mediaflow-proxy.test/extractor/video?host=Uqload&redirect_stream=true&api_password=asdfg&d=https%3A%2F%2Fuqload.net%2Fz0xbr87oz637.html",
|
||||
"url": "https://mediaflow-proxy.test/extractor/video?host=Uqload&api_password=asdfg&d=https%3A%2F%2Fuqload.net%2Fz0xbr87oz637.html&redirect_stream=true",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
|
|||
37
src/extractor/__snapshots__/VixSrc.test.ts.snap
Normal file
37
src/extractor/__snapshots__/VixSrc.test.ts.snap
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`VixSrc Black Mirror 1`] = `
|
||||
[
|
||||
{
|
||||
"format": "hls",
|
||||
"label": "VixSrc (via MediaFlow Proxy)",
|
||||
"meta": {
|
||||
"bytes": 2389338112,
|
||||
"countryCode": "it",
|
||||
"height": 1080,
|
||||
"title": "Black.Mirror.S04E02.Arkangel.1080p.NF.WEB-DL.ITA.ENG.DDP5.1.H.264.mkv",
|
||||
},
|
||||
"sourceId": "vixsrc_it",
|
||||
"ttl": 0,
|
||||
"url": "https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&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%2Fvixsrc.to%2Ftv%2F42009%2F4%2F2&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F307316%3Ftoken%3D2f5004a3b15f4ccdb3446f2b132a0957%26expires%3D1755949828%26h%3D1",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`VixSrc Full Metal Jacket 1`] = `
|
||||
[
|
||||
{
|
||||
"format": "hls",
|
||||
"label": "VixSrc (via MediaFlow Proxy)",
|
||||
"meta": {
|
||||
"bytes": 8544718848,
|
||||
"countryCode": "it",
|
||||
"height": 1080,
|
||||
"title": "Full.metal.jacket.1987.1080p.x264.AC3.ITA.AAC.ENG.Subs.mkv",
|
||||
},
|
||||
"sourceId": "vixsrc_it",
|
||||
"ttl": 0,
|
||||
"url": "https://mediaflow-proxy.test/proxy/hls/manifest.m3u8?api_password=asdfg&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%2Fvixsrc.to%2Fmovie%2F600&d=https%3A%2F%2Fvixsrc.to%2Fplaylist%2F219736%3Ftoken%3D689cf5587c2fec67fd83503a2a7dfd64%26expires%3D1755949827%26h%3D1",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
@ -10,6 +10,7 @@ import { Soaper } from './Soaper';
|
|||
import { SuperVideo } from './SuperVideo';
|
||||
import { Uqload } from './Uqload';
|
||||
import { VidSrc } from './VidSrc';
|
||||
import { VixSrc } from './VixSrc';
|
||||
|
||||
export * from './Extractor';
|
||||
export * from './ExtractorRegistry';
|
||||
|
|
@ -24,5 +25,6 @@ export const createExtractors = (fetcher: Fetcher): Extractor[] => [
|
|||
new Soaper(fetcher),
|
||||
new Uqload(fetcher),
|
||||
new VidSrc(fetcher),
|
||||
new VixSrc(fetcher),
|
||||
new ExternalUrl(fetcher), // fallback extractor which must come last
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import express, { NextFunction, Request, Response } from 'express';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import winston from 'winston';
|
||||
import { CineHDPlus, Cuevana, Eurostreaming, Frembed, FrenchCloud, Source, KinoGer, MeineCloud, MostraGuarda, Soaper, StreamKiste, VerHdLink, VidSrc } from './source';
|
||||
import { CineHDPlus, Cuevana, Eurostreaming, Frembed, FrenchCloud, Source, KinoGer, MeineCloud, MostraGuarda, Soaper, StreamKiste, VerHdLink, VidSrc, VixSrc } from './source';
|
||||
import { createExtractors, ExtractorRegistry } from './extractor';
|
||||
import { ConfigureController, ManifestController, StreamController } from './controller';
|
||||
import { envGet, envIsProd, Fetcher, StreamResolver, tmdbFetch, TmdbId } from './utils';
|
||||
|
|
@ -38,6 +38,7 @@ const sources: Source[] = [
|
|||
// IT
|
||||
new Eurostreaming(fetcher),
|
||||
new MostraGuarda(fetcher),
|
||||
new VixSrc(fetcher),
|
||||
];
|
||||
|
||||
const addon = express();
|
||||
|
|
|
|||
23
src/source/VixSrc.test.ts
Normal file
23
src/source/VixSrc.test.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { VixSrc } from './VixSrc';
|
||||
import { FetcherMock, ImdbId } from '../utils';
|
||||
import { createTestContext } from '../test';
|
||||
|
||||
const ctx = createTestContext();
|
||||
|
||||
describe('VixSrc', () => {
|
||||
let handler: VixSrc;
|
||||
|
||||
beforeEach(() => {
|
||||
handler = new VixSrc(new FetcherMock(`${__dirname}/__fixtures__/VixSrc`));
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
32
src/source/VixSrc.ts
Normal file
32
src/source/VixSrc.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { ContentType } from 'stremio-addon-sdk';
|
||||
import { Source, SourceResult } from './types';
|
||||
import { Fetcher, getTmdbId, Id } from '../utils';
|
||||
import { Context, CountryCode } from '../types';
|
||||
|
||||
export class VixSrc implements Source {
|
||||
public readonly id = 'vixsrc';
|
||||
|
||||
public readonly label = 'VixSrc';
|
||||
|
||||
public readonly contentTypes: ContentType[] = ['movie', 'series'];
|
||||
|
||||
public readonly countryCodes: CountryCode[] = [CountryCode.it];
|
||||
|
||||
private readonly baseUrl = 'https://vixsrc.to';
|
||||
|
||||
private readonly fetcher: Fetcher;
|
||||
|
||||
public constructor(fetcher: Fetcher) {
|
||||
this.fetcher = fetcher;
|
||||
}
|
||||
|
||||
public async handle(ctx: Context, _type: string, id: Id): Promise<SourceResult[]> {
|
||||
const tmdbId = await getTmdbId(ctx, this.fetcher, id);
|
||||
|
||||
const url = tmdbId.season
|
||||
? new URL(`/tv/${tmdbId.id}/${tmdbId.season}/${tmdbId.episode}`, this.baseUrl)
|
||||
: new URL(`/movie/${tmdbId.id}`, this.baseUrl);
|
||||
|
||||
return [{ countryCode: CountryCode.it, url }];
|
||||
};
|
||||
}
|
||||
1
src/source/__fixtures__/VixSrc/https:api.themoviedb.org3findtt0093058external_sourceimdb_id
generated
Normal file
1
src/source/__fixtures__/VixSrc/https:api.themoviedb.org3findtt0093058external_sourceimdb_id
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"movie_results":[{"adult":false,"backdrop_path":"/3k2TRmqMjgt7tcwkYwZQdctnty3.jpg","id":600,"title":"Full Metal Jacket","original_title":"Full Metal Jacket","overview":"A pragmatic U.S. Marine observes the dehumanizing effects the U.S.-Vietnam War has on his fellow recruits from their brutal boot camp training to the bloody street fighting in Hue.","poster_path":"/kMKyx1k8hWWscYFnPbnxxN4Eqo4.jpg","media_type":"movie","original_language":"en","genre_ids":[18,10752],"popularity":8.9033,"release_date":"1987-06-26","video":false,"vote_average":8.122,"vote_count":10846}],"person_results":[],"tv_results":[],"tv_episode_results":[],"tv_season_results":[]}
|
||||
1
src/source/__fixtures__/VixSrc/https:api.themoviedb.org3findtt2085059external_sourceimdb_id
generated
Normal file
1
src/source/__fixtures__/VixSrc/https:api.themoviedb.org3findtt2085059external_sourceimdb_id
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"movie_results":[],"person_results":[],"tv_results":[{"adult":false,"backdrop_path":"/dg3OindVAGZBjlT3xYKqIAdukPL.jpg","id":42009,"name":"Black Mirror","original_name":"Black Mirror","overview":"Twisted tales run wild in this mind-bending anthology series that reveals humanity's worst traits, greatest innovations and more.","poster_path":"/seN6rRfN0I6n8iDXjlSMk1QjNcq.jpg","media_type":"tv","original_language":"en","genre_ids":[10765,18,9648],"popularity":107.277,"first_air_date":"2011-12-04","vote_average":8.283,"vote_count":5543,"origin_country":["GB"]}],"tv_episode_results":[],"tv_season_results":[]}
|
||||
19
src/source/__snapshots__/VixSrc.test.ts.snap
Normal file
19
src/source/__snapshots__/VixSrc.test.ts.snap
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`VixSrc handle imdb black mirror s4e2 1`] = `
|
||||
[
|
||||
{
|
||||
"countryCode": "it",
|
||||
"url": "https://vixsrc.to/tv/42009/4/2",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`VixSrc handle imdb full metal jacket 1`] = `
|
||||
[
|
||||
{
|
||||
"countryCode": "it",
|
||||
"url": "https://vixsrc.to/movie/600",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
@ -10,4 +10,5 @@ export * from './Soaper';
|
|||
export * from './StreamKiste';
|
||||
export * from './VerHdLink';
|
||||
export * from './VidSrc';
|
||||
export * from './VixSrc';
|
||||
export * from './types';
|
||||
|
|
|
|||
|
|
@ -1,16 +1,47 @@
|
|||
import { Context } from '../types';
|
||||
import { Fetcher } from './Fetcher';
|
||||
|
||||
interface ExtractResult {
|
||||
destination_url: string;
|
||||
request_headers: Record<string, string>;
|
||||
mediaflow_proxy_url: string;
|
||||
query_params: Record<string, string>;
|
||||
}
|
||||
|
||||
export const supportsMediaFlowProxy = (ctx: Context): boolean => !!(ctx.config['mediaFlowProxyUrl'] && ctx.config['mediaFlowProxyPassword']);
|
||||
|
||||
export const buildMediaFlowProxyExtractorRedirectUrl = (ctx: Context, host: string, url: URL): URL => {
|
||||
const buildMediaFlowProxyExtractorUrl = (ctx: Context, host: string, url: URL): URL => {
|
||||
const mediaFlowProxyUrl = new URL(`${ctx.config.mediaFlowProxyUrl}/extractor/video`);
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('host', host);
|
||||
params.append('redirect_stream', 'true');
|
||||
params.append('api_password', `${ctx.config.mediaFlowProxyPassword}`);
|
||||
params.append('d', url.href);
|
||||
mediaFlowProxyUrl.search = params.toString();
|
||||
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');
|
||||
|
||||
return mediaFlowProxyUrl;
|
||||
};
|
||||
|
||||
export const buildMediaFlowProxyExtractorStreamUrl = async (ctx: Context, fetcher: Fetcher, host: string, url: URL): Promise<URL> => {
|
||||
const mediaFlowProxyUrl = buildMediaFlowProxyExtractorUrl(ctx, host, url);
|
||||
|
||||
const extractResult: ExtractResult = JSON.parse(await fetcher.text(ctx, mediaFlowProxyUrl));
|
||||
|
||||
const streamUrl = new URL(extractResult.mediaflow_proxy_url);
|
||||
|
||||
for (const queryParamsKey in extractResult.query_params) {
|
||||
streamUrl.searchParams.append(queryParamsKey, extractResult.query_params[queryParamsKey] as string);
|
||||
}
|
||||
for (const requestHeadersKey in extractResult.request_headers) {
|
||||
streamUrl.searchParams.append(`h_${requestHeadersKey}`, extractResult.request_headers[requestHeadersKey] as string);
|
||||
}
|
||||
streamUrl.searchParams.append('d', extractResult.destination_url);
|
||||
|
||||
return streamUrl;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue