refactor: move bytes, height and countryCode into meta object

and pass that to the extractors instead. this is a preparation to
support more meta like e.g. title
This commit is contained in:
WebStreamr 2025-05-25 19:25:39 +02:00
parent faa6eb6ed7
commit 979dddd20c
No known key found for this signature in database
25 changed files with 187 additions and 145 deletions

View file

@ -1,7 +1,7 @@
import randomstring from 'randomstring';
import { Extractor } from './types';
import { Fetcher } from '../utils';
import { Context } from '../types';
import { Context, Meta } from '../types';
import { NotFoundError } from '../error';
export class DoodStream implements Extractor {
@ -19,7 +19,7 @@ export class DoodStream implements Extractor {
readonly supports = (url: URL): boolean => null !== url.host.match(/dood|do[0-9]go/);
readonly extract = async (ctx: Context, url: URL, countryCode: string) => {
readonly extract = async (ctx: Context, url: URL, meta: Meta) => {
const videoId = url.pathname.split('/').slice(-1)[0] as string;
const normalizedUrl = new URL(`http://dood.to/e/${videoId}`);
@ -37,10 +37,8 @@ export class DoodStream implements Extractor {
return {
url: new URL(`${baseUrl}${randomstring.generate(10)}?token=${token}&expiry=${Date.now()}`),
label: this.label,
sourceId: `${this.id}_${countryCode.toLowerCase()}`,
height: 0,
bytes: 0,
countryCode,
sourceId: `${this.id}_${meta.countryCode.toLowerCase()}`,
meta,
requestHeaders: {
Referer: 'http://dood.to/',
},

View file

@ -1,7 +1,7 @@
import bytes from 'bytes';
import { Extractor } from './types';
import { extractUrlFromPacked, Fetcher } from '../utils';
import { Context } from '../types';
import { Context, Meta } from '../types';
import { NotFoundError } from '../error';
export class Dropload implements Extractor {
@ -19,7 +19,7 @@ export class Dropload implements Extractor {
readonly supports = (url: URL): boolean => null !== url.host.match(/dropload/);
readonly extract = async (ctx: Context, url: URL, countryCode: string) => {
readonly extract = async (ctx: Context, url: URL, meta: Meta) => {
url.pathname = url.pathname.replace('/e/', '').replace('/embed-', '/');
const html = await this.fetcher.text(ctx, url);
@ -34,10 +34,12 @@ export class Dropload implements Extractor {
return {
url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]),
label: this.label,
sourceId: `${this.id}_${countryCode.toLowerCase()}`,
height: parseInt(heightMatch[1] as string) as number,
bytes: bytes.parse(sizeMatch[1] as string) as number,
countryCode,
sourceId: `${this.id}_${meta.countryCode.toLowerCase()}`,
meta: {
...meta,
bytes: bytes.parse(sizeMatch[1] as string) as number,
height: parseInt(heightMatch[1] as string) as number,
},
};
};
}

View file

@ -1,6 +1,6 @@
import { Extractor } from './types';
import { Fetcher } from '../utils';
import { Context } from '../types';
import { Context, Meta } from '../types';
export class ExternalUrl implements Extractor {
readonly id = 'external';
@ -17,7 +17,7 @@ export class ExternalUrl implements Extractor {
readonly supports = (url: URL): boolean => null !== url.host.match(/.*/);
readonly extract = async (ctx: Context, url: URL, countryCode: string) => {
readonly extract = async (ctx: Context, url: URL, meta: Meta) => {
// We only want to make sure that the URL is accessible
await this.fetcher.head(ctx, url);
@ -25,10 +25,8 @@ export class ExternalUrl implements Extractor {
url: url,
isExternal: true,
label: `${url.host}`,
sourceId: `${this.id}_${countryCode.toLowerCase()}`,
height: 0,
bytes: 0,
countryCode,
sourceId: `${this.id}_${meta.countryCode.toLowerCase()}`,
meta,
};
};
}

View file

@ -13,27 +13,27 @@ describe('ExtractorRegistry', () => {
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } };
test('returns undefined when no extractor can be found', async () => {
const urlResult = await extractorRegistry.handle(ctx, new URL('https://some-url.test'), 'en');
const urlResult = await extractorRegistry.handle(ctx, new URL('https://some-url.test'), { countryCode: 'en' });
expect(urlResult).toBeUndefined();
});
test('returns from memory cache if possible', async () => {
const urlResult1 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), 'de');
const urlResult2 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), 'de');
const urlResult1 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), { countryCode: 'de' });
const urlResult2 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), { countryCode: 'de' });
expect(urlResult2).toBe(urlResult1);
});
test('returns from memory cache if possible', async () => {
const urlResult1 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), 'de');
const urlResult2 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), 'de');
const urlResult1 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), { countryCode: 'de' });
const urlResult2 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), { countryCode: 'de' });
expect(urlResult2).toBe(urlResult1);
});
test('ignores not found errors', async () => {
const urlResult = await extractorRegistry.handle(ctx, new URL('https://dropload.io/asdfghijklmn.html'), 'de');
const urlResult = await extractorRegistry.handle(ctx, new URL('https://dropload.io/asdfghijklmn.html'), { countryCode: 'de' });
expect(urlResult).toBeUndefined();
});

View file

@ -1,7 +1,7 @@
import TTLCache from '@isaacs/ttlcache';
import winston from 'winston';
import { Extractor } from './types';
import { Context, UrlResult } from '../types';
import { Context, Meta, UrlResult } from '../types';
import { Fetcher } from '../utils';
import { DoodStream } from './DoodStream';
import { Dropload } from './Dropload';
@ -25,7 +25,7 @@ export class ExtractorRegistry {
this.urlResultCache = new TTLCache({ max: 1024 });
}
readonly handle = async (ctx: Context, url: URL, countryCode: string): Promise<UrlResult | undefined> => {
readonly handle = async (ctx: Context, url: URL, meta: Meta): Promise<UrlResult | undefined> => {
let urlResult = this.urlResultCache.get(url.href);
if (this.urlResultCache.has(url.href)) {
return urlResult;
@ -35,7 +35,7 @@ export class ExtractorRegistry {
this.logger.info(`Extract stream URL using ${extractor.id} extractor from ${url}`, ctx);
try {
urlResult = await extractor.extract(ctx, url, countryCode);
urlResult = await extractor.extract(ctx, url, meta);
} catch (error) {
if (error instanceof NotFoundError) {
this.urlResultCache.set(url.href, urlResult, { ttl: extractor.ttl });

View file

@ -1,7 +1,7 @@
import bytes from 'bytes';
import { Extractor } from './types';
import { extractUrlFromPacked, Fetcher } from '../utils';
import { Context } from '../types';
import { Context, Meta } from '../types';
export class SuperVideo implements Extractor {
readonly id = 'supervideo';
@ -18,7 +18,7 @@ export class SuperVideo implements Extractor {
readonly supports = (url: URL): boolean => null !== url.host.match(/supervideo/);
readonly extract = async (ctx: Context, url: URL, countryCode: string) => {
readonly extract = async (ctx: Context, url: URL, meta: Meta) => {
url.pathname = url.pathname.replace('/e/', '').replace('/embed-', '/');
const html = await this.fetcher.text(ctx, url);
@ -27,10 +27,12 @@ export class SuperVideo implements Extractor {
return {
url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]),
label: this.label,
sourceId: `${this.id}_${countryCode.toLowerCase()}`,
height: parseInt(heightAndSizeMatch[1] as string) as number,
bytes: bytes.parse(heightAndSizeMatch[2] as string) as number,
countryCode,
sourceId: `${this.id}_${meta.countryCode.toLowerCase()}`,
meta: {
...meta,
bytes: bytes.parse(heightAndSizeMatch[2] as string) as number,
height: parseInt(heightAndSizeMatch[1] as string) as number,
},
};
};
}

View file

@ -1,4 +1,4 @@
import { Context, UrlResult } from '../types';
import { Context, Meta, UrlResult } from '../types';
export interface Extractor {
readonly id: string;
@ -9,5 +9,5 @@ export interface Extractor {
readonly supports: (url: URL) => boolean;
readonly extract: (ctx: Context, url: URL, countryCode: string) => Promise<UrlResult>;
readonly extract: (ctx: Context, url: URL, meta: Meta) => Promise<UrlResult>;
}

View file

@ -49,7 +49,7 @@ export class CineHDPlus implements Handler {
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/cinehdplus/))
.map(url => this.extractorRegistry.handle(ctx, url, countryCode)),
.map(url => this.extractorRegistry.handle(ctx, url, { countryCode })),
);
};

View file

@ -47,7 +47,7 @@ export class Eurostreaming implements Handler {
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/eurostreaming/))
.map(url => this.extractorRegistry.handle(ctx, url, 'it')),
.map(url => this.extractorRegistry.handle(ctx, url, { countryCode: 'it' })),
);
};

View file

@ -46,7 +46,7 @@ export class Frembed implements Handler {
}
}
return Promise.all(urls.map(url => this.extractorRegistry.handle(ctx, url, 'fr')));
return Promise.all(urls.map(url => this.extractorRegistry.handle(ctx, url, { countryCode: 'fr' })));
};
private readonly apiCall = async (ctx: Context, url: URL): Promise<string | undefined> => {

View file

@ -35,7 +35,7 @@ export class FrenchCloud implements Handler {
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/frenchcloud/))
.map(url => this.extractorRegistry.handle(ctx, url, 'fr')),
.map(url => this.extractorRegistry.handle(ctx, url, { countryCode: 'fr' })),
);
};
}

View file

@ -44,7 +44,7 @@ export class KinoKiste implements Handler {
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/kinokiste/))
.map(url => this.extractorRegistry.handle(ctx, url, 'de')),
.map(url => this.extractorRegistry.handle(ctx, url, { countryCode: 'de' })),
);
};

View file

@ -35,7 +35,7 @@ export class MeineCloud implements Handler {
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/meinecloud/))
.map(url => this.extractorRegistry.handle(ctx, url, 'de')),
.map(url => this.extractorRegistry.handle(ctx, url, { countryCode: 'de' })),
);
};
}

View file

@ -35,7 +35,7 @@ export class MostraGuarda implements Handler {
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/mostraguarda/))
.map(url => this.extractorRegistry.handle(ctx, url, 'it')),
.map(url => this.extractorRegistry.handle(ctx, url, { countryCode: 'it' })),
);
};
}

View file

@ -46,7 +46,7 @@ export class VerHdLink implements Handler {
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/verhdlink/))
.map(url => this.extractorRegistry.handle(ctx, url, countryCode));
.map(url => this.extractorRegistry.handle(ctx, url, { countryCode }));
}),
);
};

View file

@ -3,18 +3,22 @@
exports[`CineHDPlus handle imdb babylon 5 s2e3 (es) 1`] = `
[
{
"bytes": 219571814,
"countryCode": "es",
"height": 344,
"label": "SuperVideo",
"meta": {
"bytes": 219571814,
"countryCode": "es",
"height": 344,
},
"sourceId": "supervideo_es",
"url": "https://hfs294.serversicuro.cc/hls/,dnzpealv5xg4a3gyvavh52jryms77x3wwakzvgy2ly3yfusnblczjnvvweua,.urlset/master.m3u8",
},
{
"bytes": 219571814,
"countryCode": "es",
"height": 344,
"label": "Dropload",
"meta": {
"bytes": 219571814,
"countryCode": "es",
"height": 344,
},
"sourceId": "dropload_es",
"url": "https://srv29.dropload.io/hls2/01/00295/957hny8mzrvh_h/master.m3u8?t=VpcZJYlXpp0FJb0Nh0yJR0nyI8-SKB2tGFwpcTx9v2w&s=1747213958&e=14400&f=1476559&i=0.0&sp=0&ii=130.61.236.204",
},
@ -24,18 +28,22 @@ exports[`CineHDPlus handle imdb babylon 5 s2e3 (es) 1`] = `
exports[`CineHDPlus handle imdb black mirror s2e3 (mx) 1`] = `
[
{
"bytes": 146800640,
"countryCode": "mx",
"height": 360,
"label": "SuperVideo",
"meta": {
"bytes": 146800640,
"countryCode": "mx",
"height": 360,
},
"sourceId": "supervideo_mx",
"url": "https://hfs299.serversicuro.cc/hls/,dnzpff262hg4a3gyvcex5ojxt23vrieljzut7xhjydwidlbp4g7nqswkpfwq,.urlset/master.m3u8",
},
{
"bytes": 146800640,
"countryCode": "mx",
"height": 360,
"label": "Dropload",
"meta": {
"bytes": 146800640,
"countryCode": "mx",
"height": 360,
},
"sourceId": "dropload_mx",
"url": "https://srv27.dropload.io/hls2/01/00075/t058ulwwkwn6_h/master.m3u8?t=GkktV2WgIMrvrgNLyjn6Bxeo2fArEAz6y9nSi_6dVAs&s=1747213564&e=14400&f=376684&i=0.0&sp=0&ii=130.61.236.204",
},

View file

@ -3,26 +3,32 @@
exports[`Eurostreaming handle imdb black mirror s2e4 1`] = `
[
{
"bytes": 875875532,
"countryCode": "it",
"height": 1080,
"label": "SuperVideo",
"meta": {
"bytes": 875875532,
"countryCode": "it",
"height": 1080,
},
"sourceId": "supervideo_it",
"url": "https://hfs309.serversicuro.cc/hls/dnzpdeoe5xg4a3gyvaqx5ojpswtxjd5a22rklgah7,khdhascykjrixs7huqq,obtfascykj3vna7rk5a,.urlset/master.m3u8",
},
{
"bytes": 875875532,
"countryCode": "it",
"height": 1080,
"label": "SuperVideo",
"meta": {
"bytes": 875875532,
"countryCode": "it",
"height": 1080,
},
"sourceId": "supervideo_it",
"url": "https://hfs309.serversicuro.cc/hls/dnzpdeoe5xg4a3gyvaqx5ojpswtxjd5a22rklgah7,khdhascykjrixs7huqq,obtfascykj3vna7rk5a,.urlset/master.m3u8",
},
{
"bytes": 875875532,
"countryCode": "it",
"height": 1080,
"label": "Dropload",
"meta": {
"bytes": 875875532,
"countryCode": "it",
"height": 1080,
},
"sourceId": "dropload_it",
"url": "https://srv24.dropload.io/hls2/01/00289/978xyyxi3ldq_h/master.m3u8?t=unq9qxjYC-zgcmoXiSQ6rkMu7qHrSpN_pWOc95IPYwk&s=1747217151&e=14400&f=1448116&i=0.0&sp=0&ii=130.61.236.204",
},

View file

@ -3,10 +3,10 @@
exports[`Frembed handle imdb black mirror s4e2 1`] = `
[
{
"bytes": 0,
"countryCode": "fr",
"height": 0,
"label": "DoodStream",
"meta": {
"countryCode": "fr",
},
"requestHeaders": {
"Referer": "http://dood.to/",
},
@ -14,20 +14,20 @@ exports[`Frembed handle imdb black mirror s4e2 1`] = `
"url": "https://ee317r.cloudatacdn.com/u5kj63yvxddlsdgge7qremqqka27irwiupc4k7o5cikbh7fdq4j4mwzraf7q/1y4sn4ndoi~mocked-random-string?token=fh3rdlhjvao7chgxndsi3ra7&expiry=639837296000",
},
{
"bytes": 0,
"countryCode": "fr",
"height": 0,
"isExternal": true,
"label": "netu.frembed.art",
"meta": {
"countryCode": "fr",
},
"sourceId": "external_fr",
"url": "https://netu.frembed.art/e/0DFgfkcXOsDP",
},
{
"bytes": 0,
"countryCode": "fr",
"height": 0,
"isExternal": true,
"label": "johnalwayssame.com",
"meta": {
"countryCode": "fr",
},
"sourceId": "external_fr",
"url": "https://johnalwayssame.com/e/cqy9oue7sv0g",
},

View file

@ -3,35 +3,39 @@
exports[`FrenchCloud handle imdb the devil's bath 1`] = `
[
{
"bytes": 966682214,
"countryCode": "fr",
"height": 720,
"label": "SuperVideo",
"meta": {
"bytes": 966682214,
"countryCode": "fr",
"height": 720,
},
"sourceId": "supervideo_fr",
"url": "https://hfs292.serversicuro.cc/hls/,dnzpfwdj5dg4a3gyvbwx5lbvtu65tuwysrizwl7puv6u7dkpym3afmw5hnga,.urlset/master.m3u8",
},
{
"bytes": 966682214,
"countryCode": "fr",
"height": 720,
"label": "Dropload",
"meta": {
"bytes": 966682214,
"countryCode": "fr",
"height": 720,
},
"sourceId": "dropload_fr",
"url": "https://srv28.dropload.io/hls2/01/00215/w4yit5wiia9y_h/master.m3u8?t=ujTsd4AYOdgaA6EhuMJDrC-xYzMVEbqrrd3qsoZ8iAY&s=1747054860&e=14400&f=1076096&i=0.0&sp=0&ii=130.61.236.204",
},
{
"bytes": 0,
"countryCode": "fr",
"height": 0,
"isExternal": true,
"label": "mixdrop.ag",
"meta": {
"countryCode": "fr",
},
"sourceId": "external_fr",
"url": "https://mixdrop.ag/e/l7v73zqrfdj19z",
},
{
"bytes": 0,
"countryCode": "fr",
"height": 0,
"label": "DoodStream",
"meta": {
"countryCode": "fr",
},
"requestHeaders": {
"Referer": "http://dood.to/",
},

View file

@ -3,18 +3,22 @@
exports[`KinoKiste handle imdb black mirror s2e4 1`] = `
[
{
"bytes": 733793484,
"countryCode": "de",
"height": 720,
"label": "SuperVideo",
"meta": {
"bytes": 733793484,
"countryCode": "de",
"height": 720,
},
"sourceId": "supervideo_de",
"url": "https://hfs279.serversicuro.cc/hls/,dnzpervt3xg4a3gyvdix52ttsqpj5jgxs7keazdmhvru2uyin5g7f67a5u4a,.urlset/master.m3u8",
},
{
"bytes": 733793484,
"countryCode": "de",
"height": 720,
"label": "Dropload",
"meta": {
"bytes": 733793484,
"countryCode": "de",
"height": 720,
},
"sourceId": "dropload_de",
"url": "https://srv34.dropload.io/hls2/01/00007/jvjwrkpijr0f_h/master.m3u8?t=9B5k5sYgDD2kS_549tQ4WYh1o74hO1QR9zzyXrVRqv4&s=1746903685&e=14400&f=35986&srv=srv27&i=0.0&sp=0&ii=130.61.236.204&p1=srv27&p2=srv27",
},

View file

@ -3,35 +3,39 @@
exports[`MeineCloud handle imdb the devil's bath 1`] = `
[
{
"bytes": 1073741824,
"countryCode": "de",
"height": 720,
"label": "SuperVideo",
"meta": {
"bytes": 1073741824,
"countryCode": "de",
"height": 720,
},
"sourceId": "supervideo_de",
"url": "https://hfs290.serversicuro.cc/hls/,dnzpfi3d27g4a3gyvbmh5klwtl65qh654hyjtgupd6p56thhyqu6ra3olbfa,.urlset/master.m3u8",
},
{
"bytes": 1395864371,
"countryCode": "de",
"height": 1080,
"label": "Dropload",
"meta": {
"bytes": 1395864371,
"countryCode": "de",
"height": 1080,
},
"sourceId": "dropload_de",
"url": "https://srv27.dropload.io/hls2/01/00197/lyo2h1snpe5c_h/master.m3u8?t=qTZsRGIMnUjXsQ4o2LRLlsT4tGG1AdJrRBRDGttNLPM&s=1746903744&e=14400&f=987607&i=0.0&sp=0&ii=130.61.236.204",
},
{
"bytes": 0,
"countryCode": "de",
"height": 0,
"isExternal": true,
"label": "mixdrop.ag",
"meta": {
"countryCode": "de",
},
"sourceId": "external_de",
"url": "https://mixdrop.ag/e/3nzwveprim63or6",
},
{
"bytes": 0,
"countryCode": "de",
"height": 0,
"label": "DoodStream",
"meta": {
"countryCode": "de",
},
"requestHeaders": {
"Referer": "http://dood.to/",
},

View file

@ -3,35 +3,39 @@
exports[`MostraGuarda handle imdb the devil's bath 1`] = `
[
{
"bytes": 1181116006,
"countryCode": "it",
"height": 720,
"label": "SuperVideo",
"meta": {
"bytes": 1181116006,
"countryCode": "it",
"height": 720,
},
"sourceId": "supervideo_it",
"url": "https://hfs279.serversicuro.cc/hls/,dnzpejj427g4a3gyvbth53rxqewqgqqzp3wr6zwutysvud7zroar3cb75d3q,.urlset/master.m3u8",
},
{
"bytes": 1181116006,
"countryCode": "it",
"height": 720,
"label": "Dropload",
"meta": {
"bytes": 1181116006,
"countryCode": "it",
"height": 720,
},
"sourceId": "dropload_it",
"url": "https://srv34.dropload.io/hls2/01/00200/xsr90y2ltdyx_h/master.m3u8?t=avFOZ-10hDxo6__iaoSUrq7o4yY3ldLw6XDlXxpXF8E&s=1747059001&e=14400&f=1004116&srv=srv24&i=0.0&sp=0&ii=130.61.236.204&p1=srv24&p2=srv24",
},
{
"bytes": 0,
"countryCode": "it",
"height": 0,
"isExternal": true,
"label": "mixdrop.ag",
"meta": {
"countryCode": "it",
},
"sourceId": "external_it",
"url": "https://mixdrop.ag/e/vk196d6xfzwwo1",
},
{
"bytes": 0,
"countryCode": "it",
"height": 0,
"label": "DoodStream",
"meta": {
"countryCode": "it",
},
"requestHeaders": {
"Referer": "http://dood.to/",
},

View file

@ -3,52 +3,60 @@
exports[`VerHdLink handle titanic 1`] = `
[
{
"bytes": 1503238553,
"countryCode": "mx",
"height": 556,
"label": "SuperVideo",
"meta": {
"bytes": 1503238553,
"countryCode": "mx",
"height": 556,
},
"sourceId": "supervideo_mx",
"url": "https://hfs292.serversicuro.cc/hls/,dnzpejlw37g4a3gyvdzh5p3xttjqd3ccheao6qexhjbv4ecdpod7hxlh4a7a,.urlset/master.m3u8",
},
{
"bytes": 1503238553,
"countryCode": "mx",
"height": 556,
"label": "Dropload",
"meta": {
"bytes": 1503238553,
"countryCode": "mx",
"height": 556,
},
"sourceId": "dropload_mx",
"url": "https://srv22.dropload.io/hls2/01/00046/jjjx9j5joiz8_h/master.m3u8?t=_sOFVDjKUqBAK_gJtH7YVQnCm6nj7kr4df3PNsKeKAA&s=1747060128&e=14400&f=230703&i=0.0&sp=0&ii=130.61.236.204",
},
{
"bytes": 0,
"countryCode": "mx",
"height": 0,
"isExternal": true,
"label": "mixdrop.ag",
"meta": {
"countryCode": "mx",
},
"sourceId": "external_mx",
"url": "https://mixdrop.ag/e/vn0wx308fq984q",
},
{
"bytes": 1610612736,
"countryCode": "es",
"height": 544,
"label": "SuperVideo",
"meta": {
"bytes": 1610612736,
"countryCode": "es",
"height": 544,
},
"sourceId": "supervideo_es",
"url": "https://hfs295.serversicuro.cc/hls/,dnzpfv3w37g4a3gyvdzh5kjeyi4vtoyw4x4it5gxl4zzwiv4t3fxkvagrhea,.urlset/master.m3u8",
},
{
"bytes": 1610612736,
"countryCode": "es",
"height": 544,
"label": "Dropload",
"meta": {
"bytes": 1610612736,
"countryCode": "es",
"height": 544,
},
"sourceId": "dropload_es",
"url": "https://srv23.dropload.io/hls2/01/00046/ick4ti66vt6s_h/master.m3u8?t=OUbNiE0qRNUy69JP4AIj1bGcV5kaljyNrAUlCYWGVOs&s=1747060128&e=14400&f=230700&i=0.0&sp=0&ii=130.61.236.204",
},
{
"bytes": 0,
"countryCode": "es",
"height": 0,
"isExternal": true,
"label": "mixdrop.ag",
"meta": {
"countryCode": "es",
},
"sourceId": "external_es",
"url": "https://mixdrop.ag/e/xokzmv61hrwe8o",
},

View file

@ -10,13 +10,17 @@ export type ManifestWithConfig = Manifest & { config: ManifestConfig[] };
export type Config = Record<string, string>;
export interface Meta {
bytes?: number;
countryCode: string;
height?: number;
}
export interface UrlResult {
url: URL;
isExternal?: boolean;
label: string;
sourceId: string;
height: number;
bytes: number;
countryCode?: string;
meta: Meta;
requestHeaders?: Record<string, string>;
}

View file

@ -52,12 +52,12 @@ export class StreamResolver {
await Promise.all(handlerPromises);
urlResults.sort((a, b) => {
const heightComparison = b.height - a.height;
const heightComparison = (b.meta.height ?? 0) - (a.meta.height ?? 0);
if (heightComparison !== 0) {
return heightComparison;
}
const bytesComparison = b.bytes - a.bytes;
const bytesComparison = (b.meta.bytes ?? 0) - (a.meta.bytes ?? 0);
if (bytesComparison !== 0) {
return bytesComparison;
}
@ -70,19 +70,19 @@ export class StreamResolver {
streams.push(
...urlResults.map((urlResult) => {
let name = 'WebStreamr';
if (urlResult.height) {
name += ` ${urlResult.height}p`;
if (urlResult.meta.height) {
name += ` ${urlResult.meta.height}p`;
}
if (urlResult.isExternal) {
name += ` external`;
}
let title = urlResult.label;
if (urlResult.bytes) {
title += ` | 💾 ${bytes.format(urlResult.bytes, { unitSeparator: ' ' })}`;
if (urlResult.meta.bytes) {
title += ` | 💾 ${bytes.format(urlResult.meta.bytes, { unitSeparator: ' ' })}`;
}
if (urlResult.countryCode) {
title += ` | ${flag(urlResult.countryCode)}`;
if (urlResult.meta.countryCode) {
title += ` | ${flag(urlResult.meta.countryCode)}`;
}
return {
@ -95,7 +95,7 @@ export class StreamResolver {
notWebReady: true,
proxyHeaders: { request: urlResult.requestHeaders },
}),
...(urlResult.bytes && { videoSize: urlResult.bytes }),
...(urlResult.meta.bytes && { videoSize: urlResult.meta.bytes }),
},
};
}),