feat: extract embed handling
This commit is contained in:
parent
841f2a0811
commit
f62fa944bb
7 changed files with 41 additions and 49 deletions
|
|
@ -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: {
|
||||
|
|
|
|||
10
src/embed-extractor/EmbedExtractorRegistry.ts
Normal file
10
src/embed-extractor/EmbedExtractorRegistry.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { EmbedExtractor } from './types';
|
||||
import { Dropload } from './Dropload';
|
||||
import { SuperVideo } from './SuperVideo';
|
||||
|
||||
type EmbedExtractorRegistryType = Record<string, EmbedExtractor>;
|
||||
|
||||
export const EmbedExtractorRegistry: EmbedExtractorRegistryType = {
|
||||
dropload: new Dropload(),
|
||||
supervideo: new SuperVideo(),
|
||||
};
|
||||
|
|
@ -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: <span class="downloadbox__size">1280x720, 699.8 MB</span>
|
||||
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: {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,2 @@
|
|||
import { Dropload } from './Dropload';
|
||||
import { SuperVideo } from './SuperVideo';
|
||||
import { EmbedExtractor } from './types';
|
||||
|
||||
type EmbedExtractorRegistryType = Record<string, EmbedExtractor>;
|
||||
|
||||
export const EmbedExtractorRegistry: EmbedExtractorRegistryType = {
|
||||
dropload: new Dropload(),
|
||||
supervideo: new SuperVideo(),
|
||||
};
|
||||
export * from './EmbedExtractorRegistry';
|
||||
export * from './types';
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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('<html><body>Something to stream;1280x720, 834.6 MB<script>(eval(function(p,a,c,k,e,d){...}))</script></body></html>'),
|
||||
);
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue