From f62fa944bbec3150c48423e6a19e1b1e5ac7f786 Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Sat, 10 May 2025 22:59:12 +0000 Subject: [PATCH] feat: extract embed handling --- src/embed-extractor/Dropload.ts | 9 ++++--- src/embed-extractor/EmbedExtractorRegistry.ts | 10 ++++++++ src/embed-extractor/SuperVideo.ts | 13 ++++------ src/embed-extractor/index.ts | 12 ++-------- src/handler/KinoKiste.ts | 24 ++++++++----------- src/handler/MeineCloud.ts | 8 +++---- src/utils/embed.test.ts | 14 +++++------ 7 files changed, 41 insertions(+), 49 deletions(-) create mode 100644 src/embed-extractor/EmbedExtractorRegistry.ts diff --git a/src/embed-extractor/Dropload.ts b/src/embed-extractor/Dropload.ts index 8b40c11..ca9a240 100644 --- a/src/embed-extractor/Dropload.ts +++ b/src/embed-extractor/Dropload.ts @@ -10,14 +10,13 @@ export class Dropload implements EmbedExtractor { const normalizedUrl = url.replace('/e/', '').replace('/embed-', '/'); const html = await cachedFetchText(normalizedUrl); - const resolutionMatch = html.match(/(\d{3,}x\d{3,}),/); - const resolution = resolutionMatch && resolutionMatch[1] ? scanFromResolution(resolutionMatch[1]) : undefined; + const resolution = scanFromResolution((html.match(/(\d{3,}x\d{3,}),/) as string[])[1] as string); - const sizeMatch = html.match(/([\d.]+) ?([GM]B)/); - const size = sizeMatch && sizeMatch[1] && sizeMatch[2] ? `${sizeMatch[1]} ${sizeMatch[2]}` : undefined; + const sizeMatch = html.match(/([\d.]+) ?([GM]B)/) as string[]; + const size = `${sizeMatch[1]} ${sizeMatch[2]}`; return { - url: await extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]), + url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]), name: `WebStreamr ${resolution}`, title: `${this.label} | 💾 ${size} | ${iso2ToFlag(language)}`, behaviorHints: { diff --git a/src/embed-extractor/EmbedExtractorRegistry.ts b/src/embed-extractor/EmbedExtractorRegistry.ts new file mode 100644 index 0000000..afacee8 --- /dev/null +++ b/src/embed-extractor/EmbedExtractorRegistry.ts @@ -0,0 +1,10 @@ +import { EmbedExtractor } from './types'; +import { Dropload } from './Dropload'; +import { SuperVideo } from './SuperVideo'; + +type EmbedExtractorRegistryType = Record; + +export const EmbedExtractorRegistry: EmbedExtractorRegistryType = { + dropload: new Dropload(), + supervideo: new SuperVideo(), +}; diff --git a/src/embed-extractor/SuperVideo.ts b/src/embed-extractor/SuperVideo.ts index b323744..6437dc6 100644 --- a/src/embed-extractor/SuperVideo.ts +++ b/src/embed-extractor/SuperVideo.ts @@ -7,18 +7,15 @@ export class SuperVideo implements EmbedExtractor { readonly label = 'SuperVideo'; readonly extract = async (url: string, language: string) => { - const normalizedUrl = url.replace('/e/', '').replace('/embed-', '/'); + const normalizedUrl = url.replace('/e/', '/').replace('/embed-', '/'); const html = await cachedFetchText(normalizedUrl); - // TODO: resolution and size can be merged: 1280x720, 699.8 MB - const resolutionMatch = html.match(/(\d{3,}x\d{3,}),/); - const resolution = resolutionMatch && resolutionMatch[1] ? scanFromResolution(resolutionMatch[1]) : undefined; - - const sizeMatch = html.match(/([\d.]+) ?([GM]B)/); - const size = sizeMatch && sizeMatch[1] && sizeMatch[2] ? `${sizeMatch[1]} ${sizeMatch[2]}` : undefined; + const resolutionAndSizeMatch = html.match(/(\d{3,}x\d{3,}), ([\d.]+) ?([GM]B)/) as string[]; + const resolution = scanFromResolution(resolutionAndSizeMatch[1] as string); + const size = `${resolutionAndSizeMatch[2]} ${resolutionAndSizeMatch[3]}`; return { - url: await extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]), + url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]), name: `WebStreamr ${resolution}`, title: `${this.label} | 💾 ${size} | ${iso2ToFlag(language)}`, behaviorHints: { diff --git a/src/embed-extractor/index.ts b/src/embed-extractor/index.ts index 70f61ea..76c0bb1 100644 --- a/src/embed-extractor/index.ts +++ b/src/embed-extractor/index.ts @@ -1,10 +1,2 @@ -import { Dropload } from './Dropload'; -import { SuperVideo } from './SuperVideo'; -import { EmbedExtractor } from './types'; - -type EmbedExtractorRegistryType = Record; - -export const EmbedExtractorRegistry: EmbedExtractorRegistryType = { - dropload: new Dropload(), - supervideo: new SuperVideo(), -}; +export * from './EmbedExtractorRegistry'; +export * from './types'; diff --git a/src/handler/KinoKiste.ts b/src/handler/KinoKiste.ts index e6559be..a58c87a 100644 --- a/src/handler/KinoKiste.ts +++ b/src/handler/KinoKiste.ts @@ -2,7 +2,7 @@ import * as cheerio from 'cheerio'; import slugify from 'slugify'; import { Handler } from './types'; import { ImdbId, cachedFetchText, fulfillAllPromises, parseImdbId } from '../utils'; -import { EmbedExtractorRegistry } from '../embed-extractor'; +import { EmbedExtractor, EmbedExtractorRegistry } from '../embed-extractor'; export class KinoKiste implements Handler { readonly id = 'kinokiste'; @@ -30,20 +30,16 @@ export class KinoKiste implements Handler { const $ = cheerio.load(html); return fulfillAllPromises( - $(`[data-num="${imdbId.series}x${imdbId.episode}"]`).map((_i, urlWrapperElement) => { - return $(urlWrapperElement).siblings('.mirrors').children('[data-link]') - .map((_i, urlElement) => ({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - embedId: slugify($(urlElement).attr('data-m')!), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - embedUrl: $(urlElement).attr('data-link')! - .replace(/^(https:)?\/\//, 'https://'), - })) - .toArray() - .filter(({ embedId }) => embedId.match(/^(dropload|supervideo)$/)); - }) + $(`[data-num="${imdbId.series}x${imdbId.episode}"]`) + .siblings('.mirrors') + .children('[data-link]') + .map((_i, urlElement) => ({ + embedId: slugify($(urlElement).attr('data-m') as string), + embedUrl: ($(urlElement).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://'), + })) .toArray() - .map(({ embedId, embedUrl }) => EmbedExtractorRegistry[embedId]?.extract(embedUrl, 'de')) + .filter(({ embedId }) => embedId.match(/^(dropload|supervideo)$/)) + .map(({ embedId, embedUrl }) => (EmbedExtractorRegistry[embedId] as EmbedExtractor).extract(embedUrl, 'de')) .filter(stream => stream !== undefined), ); }; diff --git a/src/handler/MeineCloud.ts b/src/handler/MeineCloud.ts index f75ac03..a65eff8 100644 --- a/src/handler/MeineCloud.ts +++ b/src/handler/MeineCloud.ts @@ -2,7 +2,7 @@ import * as cheerio from 'cheerio'; import slugify from 'slugify'; import { Handler } from './types'; import { cachedFetchText, fulfillAllPromises, parseImdbId } from '../utils'; -import { EmbedExtractorRegistry } from '../embed-extractor'; +import { EmbedExtractor, EmbedExtractorRegistry } from '../embed-extractor'; export class MeineCloud implements Handler { readonly id = 'meinecloud'; @@ -26,13 +26,11 @@ export class MeineCloud implements Handler { $('[data-link!=""]') .map((_i, el) => ({ embedId: slugify($(el).text()), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - embedUrl: $(el).attr('data-link')! - .replace(/^(https:)?\/\//, 'https://'), + embedUrl: ($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://'), })) .toArray() .filter(({ embedId }) => embedId.match(/^(dropload|supervideo)$/)) - .map(({ embedId, embedUrl }) => EmbedExtractorRegistry[embedId]?.extract(embedUrl, 'de')) + .map(({ embedId, embedUrl }) => (EmbedExtractorRegistry[embedId] as EmbedExtractor).extract(embedUrl, 'de')) .filter(stream => stream !== undefined), ); }; diff --git a/src/utils/embed.test.ts b/src/utils/embed.test.ts index 15835a9..b773707 100644 --- a/src/utils/embed.test.ts +++ b/src/utils/embed.test.ts @@ -1,5 +1,4 @@ import { unpack } from 'unpacker'; -import { cachedFetchText } from './fetch'; import { extractUrlFromPacked } from './embed'; jest.mock('unpacker', () => ({ @@ -8,21 +7,22 @@ jest.mock('unpacker', () => ({ describe('extractUrlFromPacked', () => { test('throw if no embed was found', () => { - expect(extractUrlFromPacked('', [/eval/])).rejects.toThrow('No p.a.c.k.e.d string found on https://some-url.test'); + expect(() => { + extractUrlFromPacked('', [/sources:\[{file:"(.*?)"/]); + }).toThrow('No p.a.c.k.e.d string found'); }); test('throw if no link was found', () => { (unpack as jest.Mock).mockReturnValue('some-unexpected-data'); - expect(extractUrlFromPacked('eval(function(p,a,c,k,e,d){...})', [/eval/])).rejects.toThrow('Could not find a stream link on https://some-url.test'); + expect(() => { + extractUrlFromPacked('eval(function(p,a,c,k,e,d){...}))', [/sources:\[{file:"(.*?)"/]); + }).toThrow('Could not find a stream link'); }); test('finds link', async () => { (unpack as jest.Mock).mockReturnValue('{sources:[{file:"https://streaming-url.mp4"}'); - (cachedFetchText as jest.Mock).mockReturnValue( - Promise.resolve('Something to stream;1280x720, 834.6 MB'), - ); - expect(extractUrlFromPacked('eval(function(p,a,c,k,e,d){...})', [/eval/])).toBe('https://streaming-url.mp4'); + expect(extractUrlFromPacked('eval(function(p,a,c,k,e,d){...}))', [/sources:\[{file:"(.*?)"/])).toBe('https://streaming-url.mp4'); }); });