From 3d28b226e5e6c33bef638d3c751be01a03aaf4c2 Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:57:21 +0000 Subject: [PATCH] feat(extractor): add StreamEmbed support --- src/extractor/StreamEmbed.test.ts | 17 + src/extractor/StreamEmbed.ts | 42 ++ .../https:watch.gxplayer.xyzwatchvMEKI92PU | 626 ++++++++++++++++++ .../__snapshots__/StreamEmbed.test.ts.snap | 20 + src/extractor/index.ts | 2 + 5 files changed, 707 insertions(+) create mode 100644 src/extractor/StreamEmbed.test.ts create mode 100644 src/extractor/StreamEmbed.ts create mode 100644 src/extractor/__fixtures__/StreamEmbed/https:watch.gxplayer.xyzwatchvMEKI92PU create mode 100644 src/extractor/__snapshots__/StreamEmbed.test.ts.snap diff --git a/src/extractor/StreamEmbed.test.ts b/src/extractor/StreamEmbed.test.ts new file mode 100644 index 0000000..d856a9c --- /dev/null +++ b/src/extractor/StreamEmbed.test.ts @@ -0,0 +1,17 @@ +import winston from 'winston'; +import { createTestContext } from '../test'; +import { CountryCode } from '../types'; +import { FetcherMock } from '../utils'; +import { ExtractorRegistry } from './ExtractorRegistry'; +import { StreamEmbed } from './StreamEmbed'; + +const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] }); +const extractorRegistry = new ExtractorRegistry(logger, [new StreamEmbed(new FetcherMock(`${__dirname}/__fixtures__/StreamEmbed`))]); + +const ctx = createTestContext(); + +describe('StreamEmbed', () => { + test('watch.gxplayer.xyz', async () => { + expect(await extractorRegistry.handle(ctx, new URL('https://watch.gxplayer.xyz/watch?v=MEKI92PU'), CountryCode.de)).toMatchSnapshot(); + }); +}); diff --git a/src/extractor/StreamEmbed.ts b/src/extractor/StreamEmbed.ts new file mode 100644 index 0000000..90a7dcd --- /dev/null +++ b/src/extractor/StreamEmbed.ts @@ -0,0 +1,42 @@ +import { Context, CountryCode, Format, UrlResult } from '../types'; +import { Fetcher } from '../utils'; +import { Extractor } from './Extractor'; + +export class StreamEmbed extends Extractor { + public readonly id = 'streamembed'; + + public readonly label = 'StreamEmbed'; + + private readonly fetcher: Fetcher; + + public constructor(fetcher: Fetcher) { + super(); + + this.fetcher = fetcher; + } + + public supports(_ctx: Context, url: URL): boolean { + return null !== url.host.match(/bullstream|mp4player|watch\.gxplayer/); + } + + protected async extractInternal(ctx: Context, url: URL, countryCode: CountryCode): Promise { + const html = await this.fetcher.text(ctx, url); + + const video = JSON.parse((html.match(/video ?= ?(.*);/) as string[])[1] as string); + + return [ + { + url: new URL(`/m3u8/${video.uid}/${video.md5}/master.txt?s=1&id=${video.id}&cache=${video.status}`, url.origin), + format: Format.hls, + label: this.label, + sourceId: `${this.id}_${countryCode}`, + ttl: this.ttl, + meta: { + countryCodes: [countryCode], + height: parseInt(JSON.parse(video.quality)[0]), + title: decodeURIComponent(video.title), + }, + }, + ]; + }; +} diff --git a/src/extractor/__fixtures__/StreamEmbed/https:watch.gxplayer.xyzwatchvMEKI92PU b/src/extractor/__fixtures__/StreamEmbed/https:watch.gxplayer.xyzwatchvMEKI92PU new file mode 100644 index 0000000..6e57f71 --- /dev/null +++ b/src/extractor/__fixtures__/StreamEmbed/https:watch.gxplayer.xyzwatchvMEKI92PU @@ -0,0 +1,626 @@ + + + + + + +Baymax%20-%20Riesiges%20Robowabohu.mp4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/extractor/__snapshots__/StreamEmbed.test.ts.snap b/src/extractor/__snapshots__/StreamEmbed.test.ts.snap new file mode 100644 index 0000000..7c71ec3 --- /dev/null +++ b/src/extractor/__snapshots__/StreamEmbed.test.ts.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`StreamEmbed watch.gxplayer.xyz 1`] = ` +[ + { + "format": "hls", + "label": "StreamEmbed", + "meta": { + "countryCodes": [ + "de", + ], + "height": 720, + "title": "Baymax - Riesiges Robowabohu.mp4", + }, + "sourceId": "streamembed_de", + "ttl": 900000, + "url": "https://watch.gxplayer.xyz/m3u8/3/30b4a92517ace5825f5944c8a794ad3e/master.txt?s=1&id=11782&cache=1", + }, +] +`; diff --git a/src/extractor/index.ts b/src/extractor/index.ts index 6f19bdc..9a5128d 100644 --- a/src/extractor/index.ts +++ b/src/extractor/index.ts @@ -6,6 +6,7 @@ import { Extractor } from './Extractor'; import { KinoGer } from './KinoGer'; import { Mixdrop } from './Mixdrop'; import { Soaper } from './Soaper'; +import { StreamEmbed } from './StreamEmbed'; import { Streamtape } from './Streamtape'; import { SuperVideo } from './SuperVideo'; import { Uqload } from './Uqload'; @@ -21,6 +22,7 @@ export const createExtractors = (fetcher: Fetcher): Extractor[] => [ new KinoGer(fetcher), new Mixdrop(fetcher), new Soaper(fetcher), + new StreamEmbed(fetcher), new Streamtape(fetcher), new SuperVideo(fetcher), new Uqload(fetcher),