diff --git a/src/extractor/StreamUp.test.ts b/src/extractor/StreamUp.test.ts new file mode 100644 index 0000000..a4a8b26 --- /dev/null +++ b/src/extractor/StreamUp.test.ts @@ -0,0 +1,16 @@ +import winston from 'winston'; +import { createTestContext } from '../test'; +import { FetcherMock } from '../utils'; +import { ExtractorRegistry } from './ExtractorRegistry'; +import { StreamUp } from './StreamUp'; + +const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] }); +const extractorRegistry = new ExtractorRegistry(logger, [new StreamUp(new FetcherMock(`${__dirname}/__fixtures__/StreamUp`))]); + +const ctx = createTestContext(); + +describe('StreamUp', () => { + test('handle one battle after another', async () => { + expect(await extractorRegistry.handle(ctx, new URL('https://strmup.to/6950ae79eaa4c'))).toMatchSnapshot(); + }); +}); diff --git a/src/extractor/StreamUp.ts b/src/extractor/StreamUp.ts new file mode 100644 index 0000000..9be0e28 --- /dev/null +++ b/src/extractor/StreamUp.ts @@ -0,0 +1,49 @@ +import * as cheerio from 'cheerio'; +import { Context, Format, Meta, UrlResult } from '../types'; +import { guessHeightFromPlaylist } from '../utils'; +import { Extractor } from './Extractor'; + +interface StreamUpApiData { + streaming_url: string; +} + +/** @see https://github.com/Gujal00/ResolveURL/blob/master/script.module.resolveurl/lib/resolveurl/plugins/streamup.py */ +export class StreamUp extends Extractor { + public readonly id = 'streamup'; + + public readonly label = 'StreamUP'; + + public override readonly ttl: number = 10800000; // 3h + + public supports(_ctx: Context, url: URL): boolean { + return null !== url.host.match(/streamup|strmup/) || [ + 'vfaststream.com', + ].includes(url.host); + } + + protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise { + const headers = { Referer: `${url.origin}/`, Origin: url.origin }; + + const html = await this.fetcher.text(ctx, url, { headers }); + + const data = await this.fetcher.json(ctx, new URL(`/ajax/stream?filecode=${url.pathname.split('/').at(-1)}`, url), { headers }) as StreamUpApiData; + const playlistUrl = new URL(data.streaming_url); + + const $ = cheerio.load(html); + const title = $('title').text().trim(); + + return [ + { + url: playlistUrl, + format: Format.hls, + label: this.label, + ttl: this.ttl, + meta: { + ...meta, + height: await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, url), + title, + }, + }, + ]; + } +} diff --git a/src/extractor/__fixtures__/StreamUp/https:s3-hls2-cdn59.strmupcdn.comhlsSJbD5Zm1pj06jZ2LDbHa13YBmryxasmaster.m3u8token35f702a94c0e324d607d8761795a3788-1768016464-104.28.197.7-5ebad85b212e5e349b388501e051c992a2b68e70fdcaa6cc18270adee1f7c003 b/src/extractor/__fixtures__/StreamUp/https:s3-hls2-cdn59.strmupcdn.comhlsSJbD5Zm1pj06jZ2LDbHa13YBmryxasmaster.m3u8token35f702a94c0e324d607d8761795a3788-1768016464-104.28.197.7-5ebad85b212e5e349b388501e051c992a2b68e70fdcaa6cc18270adee1f7c003 new file mode 100644 index 0000000..508fb09 --- /dev/null +++ b/src/extractor/__fixtures__/StreamUp/https:s3-hls2-cdn59.strmupcdn.comhlsSJbD5Zm1pj06jZ2LDbHa13YBmryxasmaster.m3u8token35f702a94c0e324d607d8761795a3788-1768016464-104.28.197.7-5ebad85b212e5e349b388501e051c992a2b68e70fdcaa6cc18270adee1f7c003 @@ -0,0 +1,4 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-STREAM-INF:BANDWIDTH=1500000,AVERAGE-BANDWIDTH=1350000,CODECS="avc1.4d401f,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=30 +index_1920x1080.m3u8?token=35f702a94c0e324d607d8761795a3788-1768016464-104.28.197.7-5ebad85b212e5e349b388501e051c992a2b68e70fdcaa6cc18270adee1f7c003 diff --git a/src/extractor/__fixtures__/StreamUp/https:strmup.to6950ae79eaa4c b/src/extractor/__fixtures__/StreamUp/https:strmup.to6950ae79eaa4c new file mode 100644 index 0000000..20f2127 --- /dev/null +++ b/src/extractor/__fixtures__/StreamUp/https:strmup.to6950ae79eaa4c @@ -0,0 +1,55 @@ + + + + + + + Wake.Up.Dead.Man.A.Knives.Out.Mystery.2025 + + + + + + + + + +
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/extractor/__fixtures__/StreamUp/https:strmup.toajaxstreamfilecode6950ae79eaa4c b/src/extractor/__fixtures__/StreamUp/https:strmup.toajaxstreamfilecode6950ae79eaa4c new file mode 100644 index 0000000..7e9b3ee --- /dev/null +++ b/src/extractor/__fixtures__/StreamUp/https:strmup.toajaxstreamfilecode6950ae79eaa4c @@ -0,0 +1 @@ +{"title":"","thumbnail":"https:\/\/add4.cdnup.cc\/thumbnail\/xWcziV3Qim\/6950ae79eaa4c.jpg","streaming_url":"https:\/\/s3-hls2-cdn59.strmupcdn.com\/hls\/SJbD5Zm1pj06jZ2LDbHa13YBmryxas\/master.m3u8?token=35f702a94c0e324d607d8761795a3788-1768016464-104.28.197.7-5ebad85b212e5e349b388501e051c992a2b68e70fdcaa6cc18270adee1f7c003","vast_ads":null,"filecode":"6950ae79eaa4c","subtitles":[{"id":521875,"status":1,"file_id":2229055,"type":0,"file_path":"https:\/\/add4.cdnup.cc\/subtitles\/QUKzfdu1n4\/PPTRdlsmJY94PKK_subtitle_0.vtt","language":"German \u2013 German (Forced)"},{"id":521876,"status":1,"file_id":2229055,"type":0,"file_path":"https:\/\/add4.cdnup.cc\/subtitles\/gxReoqgF82\/ABVuNSZeq61xYhM_subtitle_1.vtt","language":"German \u2013 German"},{"id":521877,"status":1,"file_id":2229055,"type":0,"file_path":"https:\/\/add4.cdnup.cc\/subtitles\/YSzO4RQUAu\/DNRW2kgCksC8YYq_subtitle_2.vtt","language":"German \u2013 German"},{"id":521878,"status":1,"file_id":2229055,"type":0,"file_path":"https:\/\/add4.cdnup.cc\/subtitles\/l7MtB81Fsb\/DSFucpIAjME1ZdV_subtitle_3.vtt","language":"German \u2013 German (SDH)"}],"default_sub_lang":"German"} \ No newline at end of file diff --git a/src/extractor/__snapshots__/StreamUp.test.ts.snap b/src/extractor/__snapshots__/StreamUp.test.ts.snap new file mode 100644 index 0000000..bece149 --- /dev/null +++ b/src/extractor/__snapshots__/StreamUp.test.ts.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`StreamUp handle one battle after another 1`] = ` +[ + { + "format": "hls", + "label": "StreamUP", + "meta": { + "countryCodes": [], + "extractorId": "streamup", + "height": 1080, + "title": "Wake.Up.Dead.Man.A.Knives.Out.Mystery.2025", + }, + "ttl": 10800000, + "url": "https://s3-hls2-cdn59.strmupcdn.com/hls/SJbD5Zm1pj06jZ2LDbHa13YBmryxas/master.m3u8?token=35f702a94c0e324d607d8761795a3788-1768016464-104.28.197.7-5ebad85b212e5e349b388501e051c992a2b68e70fdcaa6cc18270adee1f7c003", + }, +] +`; diff --git a/src/extractor/index.ts b/src/extractor/index.ts index 19536ba..5dbcf4f 100644 --- a/src/extractor/index.ts +++ b/src/extractor/index.ts @@ -15,6 +15,7 @@ import { RgShows } from './RgShows'; import { SaveFiles } from './SaveFiles'; import { StreamEmbed } from './StreamEmbed'; import { Streamtape } from './Streamtape'; +import { StreamUp } from './StreamUp'; import { SuperVideo } from './SuperVideo'; import { Uqload } from './Uqload'; import { Vidora } from './Vidora'; @@ -43,6 +44,7 @@ export const createExtractors = (fetcher: Fetcher): Extractor[] => { new SaveFiles(fetcher), new StreamEmbed(fetcher), new Streamtape(fetcher), + new StreamUp(fetcher), new SuperVideo(fetcher), new Uqload(fetcher), new Vidora(fetcher),