From e1b85dafe74ef2855ec2210e87ece341ddf7f65b Mon Sep 17 00:00:00 2001 From: GSTAR <44748406+GLlgGL@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:22:16 +0100 Subject: [PATCH 1/9] Create StreamUp.ts --- src/extractor/StreamUp.ts | 113 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/extractor/StreamUp.ts diff --git a/src/extractor/StreamUp.ts b/src/extractor/StreamUp.ts new file mode 100644 index 0000000..dccfddb --- /dev/null +++ b/src/extractor/StreamUp.ts @@ -0,0 +1,113 @@ +import crypto from 'crypto'; +import { Context, Format, Meta, UrlResult } from '../types'; +import { Extractor } from './Extractor'; +import { NotFoundError } from '../error'; + +/** + * Port of: + * 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 = 6 * 60 * 60 * 1000; // 6 hours + + public supports(_ctx: Context, url: URL): boolean { + return [ + 'streamup.ws', + 'streamup.cc', + 'strmup.to', + 'strmup.cc', + 'vfaststream.com', + ].includes(url.host); + } + + public override normalize(url: URL): URL { + return new URL(`/${url.pathname.split('/').at(-1)}`, url.origin); + } + + protected async extractInternal( + ctx: Context, + url: URL, + meta: Meta, + ): Promise { + const referer = `${url.origin}/`; + + const headers = { + Referer: referer, + 'User-Agent': 'Mozilla/5.0', + }; + + const html = await this.fetcher.text(ctx, url, { headers }); + + const sessionMatch = html.match(/'([a-f0-9]{32})'/); + const encryptedMatch = html.match(/'([A-Za-z0-9+/=]{200,})'/); + + let streamUrl: string | undefined; + + if (sessionMatch && encryptedMatch) { + /* encrypted flow */ + const sessionId = sessionMatch[1] as string; + const encryptedB64 = encryptedMatch[1] as string; + + const keyUrl = new URL(`/ajax/stream?session=${sessionId}`, url.origin); + const keyB64 = await this.fetcher.text(ctx, keyUrl, { headers }); + + const key = Buffer.from(keyB64, 'base64'); + const encrypted = Buffer.from(encryptedB64, 'base64'); + + const iv = encrypted.subarray(0, 16); + const ciphertext = encrypted.subarray(16); + + const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); + const decrypted = Buffer.concat([ + decipher.update(ciphertext), + decipher.final(), + ]).toString('utf-8'); + + const data = JSON.parse(decrypted) as { + streaming_url?: string; + }; + + streamUrl = data.streaming_url; + } else { + /* fallback flow */ + const filecode = url.pathname.split('/').at(-1); + const apiUrl = new URL(`/ajax/stream?filecode=${filecode}`, url.origin); + + const jsonText = await this.fetcher.text(ctx, apiUrl, { headers }); + const data = JSON.parse(jsonText) as { + streaming_url?: string; + }; + + streamUrl = data.streaming_url; + } + + if (!streamUrl) { + throw new NotFoundError(); + } + + /* cleanup identical to Python resolver */ + streamUrl = streamUrl + .replace(/\\r\\n/g, '') + .replace(/\r|\n/g, '') + .replace(/\\\//g, '/'); + + return [ + { + url: new URL(streamUrl), + format: Format.hls, + label: this.label, + sourceId: `${this.id}_${meta.countryCodes?.join('_')}`, + ttl: this.ttl, + requestHeaders: { + Referer: referer, + Origin: url.origin, + }, + meta: { + ...meta, + }, + }, + ]; + } +} -- 2.45.2 From e3c717064cca89b9d0d929efd9a1b0ca1dd7c043 Mon Sep 17 00:00:00 2001 From: GSTAR <44748406+GLlgGL@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:26:14 +0100 Subject: [PATCH 2/9] Update StreamUp.ts --- src/extractor/StreamUp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extractor/StreamUp.ts b/src/extractor/StreamUp.ts index dccfddb..a326b68 100644 --- a/src/extractor/StreamUp.ts +++ b/src/extractor/StreamUp.ts @@ -1,7 +1,7 @@ import crypto from 'crypto'; +import { NotFoundError } from '../error'; import { Context, Format, Meta, UrlResult } from '../types'; import { Extractor } from './Extractor'; -import { NotFoundError } from '../error'; /** * Port of: -- 2.45.2 From 7ab34e3e6346145d97f19dd8d6e93efb83051af8 Mon Sep 17 00:00:00 2001 From: GSTAR <44748406+GLlgGL@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:28:36 +0100 Subject: [PATCH 3/9] Update StreamUp.ts --- src/extractor/StreamUp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extractor/StreamUp.ts b/src/extractor/StreamUp.ts index a326b68..a3a35aa 100644 --- a/src/extractor/StreamUp.ts +++ b/src/extractor/StreamUp.ts @@ -34,7 +34,7 @@ export class StreamUp extends Extractor { const referer = `${url.origin}/`; const headers = { - Referer: referer, + 'Referer': referer, 'User-Agent': 'Mozilla/5.0', }; -- 2.45.2 From 5182486a78fc0f438cd41bf8ac82ca7e2ed0c973 Mon Sep 17 00:00:00 2001 From: GSTAR <44748406+GLlgGL@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:42:50 +0100 Subject: [PATCH 4/9] Update StreamUp.ts --- src/extractor/StreamUp.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/extractor/StreamUp.ts b/src/extractor/StreamUp.ts index a3a35aa..9090c21 100644 --- a/src/extractor/StreamUp.ts +++ b/src/extractor/StreamUp.ts @@ -43,8 +43,6 @@ export class StreamUp extends Extractor { const sessionMatch = html.match(/'([a-f0-9]{32})'/); const encryptedMatch = html.match(/'([A-Za-z0-9+/=]{200,})'/); - let streamUrl: string | undefined; - if (sessionMatch && encryptedMatch) { /* encrypted flow */ const sessionId = sessionMatch[1] as string; -- 2.45.2 From 8b3e2765c6708bc2f8ec289453717e0da8578756 Mon Sep 17 00:00:00 2001 From: GSTAR <44748406+GLlgGL@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:46:30 +0100 Subject: [PATCH 5/9] Update StreamUp.ts --- src/extractor/StreamUp.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/extractor/StreamUp.ts b/src/extractor/StreamUp.ts index 9090c21..a3a35aa 100644 --- a/src/extractor/StreamUp.ts +++ b/src/extractor/StreamUp.ts @@ -43,6 +43,8 @@ export class StreamUp extends Extractor { const sessionMatch = html.match(/'([a-f0-9]{32})'/); const encryptedMatch = html.match(/'([A-Za-z0-9+/=]{200,})'/); + let streamUrl: string | undefined; + if (sessionMatch && encryptedMatch) { /* encrypted flow */ const sessionId = sessionMatch[1] as string; -- 2.45.2 From 63b0f34525c5fe6514b39de0f41cdc4d94c4fa98 Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Fri, 9 Jan 2026 19:48:31 +0000 Subject: [PATCH 6/9] keep only essentials, add height and title parsing and tests --- src/extractor/StreamUp.test.ts | 16 +++ src/extractor/StreamUp.ts | 105 ++++-------------- ...01e051c992a2b68e70fdcaa6cc18270adee1f7c003 | 4 + .../StreamUp/https:strmup.to6950ae79eaa4c | 55 +++++++++ ...s:strmup.toajaxstreamfilecode6950ae79eaa4c | 1 + .../__snapshots__/StreamUp.test.ts.snap | 23 ++++ src/extractor/index.ts | 2 + 7 files changed, 124 insertions(+), 82 deletions(-) create mode 100644 src/extractor/StreamUp.test.ts create mode 100644 src/extractor/__fixtures__/StreamUp/https:s3-hls2-cdn59.strmupcdn.comhlsSJbD5Zm1pj06jZ2LDbHa13YBmryxasmaster.m3u8token35f702a94c0e324d607d8761795a3788-1768016464-104.28.197.7-5ebad85b212e5e349b388501e051c992a2b68e70fdcaa6cc18270adee1f7c003 create mode 100644 src/extractor/__fixtures__/StreamUp/https:strmup.to6950ae79eaa4c create mode 100644 src/extractor/__fixtures__/StreamUp/https:strmup.toajaxstreamfilecode6950ae79eaa4c create mode 100644 src/extractor/__snapshots__/StreamUp.test.ts.snap 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 index a3a35aa..9b10cba 100644 --- a/src/extractor/StreamUp.ts +++ b/src/extractor/StreamUp.ts @@ -1,111 +1,52 @@ -import crypto from 'crypto'; -import { NotFoundError } from '../error'; +import * as cheerio from 'cheerio'; import { Context, Format, Meta, UrlResult } from '../types'; +import { guessHeightFromPlaylist } from '../utils'; import { Extractor } from './Extractor'; -/** - * Port of: - * https://github.com/Gujal00/ResolveURL/blob/master/script.module.resolveurl/lib/resolveurl/plugins/streamup.py - */ +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 = 6 * 60 * 60 * 1000; // 6 hours + + public readonly label = 'StreamUP'; + + public override readonly ttl: number = 10800000; // 3h public supports(_ctx: Context, url: URL): boolean { - return [ - 'streamup.ws', - 'streamup.cc', - 'strmup.to', - 'strmup.cc', + return null !== url.host.match(/streamup|strmup/) || [ 'vfaststream.com', ].includes(url.host); } - public override normalize(url: URL): URL { - return new URL(`/${url.pathname.split('/').at(-1)}`, url.origin); - } - - protected async extractInternal( - ctx: Context, - url: URL, - meta: Meta, - ): Promise { - const referer = `${url.origin}/`; - + protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise { const headers = { - 'Referer': referer, + 'Referer': `${url.origin}/`, + 'Origin': url.origin, 'User-Agent': 'Mozilla/5.0', }; const html = await this.fetcher.text(ctx, url, { headers }); - const sessionMatch = html.match(/'([a-f0-9]{32})'/); - const encryptedMatch = html.match(/'([A-Za-z0-9+/=]{200,})'/); + 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); - let streamUrl: string | undefined; - - if (sessionMatch && encryptedMatch) { - /* encrypted flow */ - const sessionId = sessionMatch[1] as string; - const encryptedB64 = encryptedMatch[1] as string; - - const keyUrl = new URL(`/ajax/stream?session=${sessionId}`, url.origin); - const keyB64 = await this.fetcher.text(ctx, keyUrl, { headers }); - - const key = Buffer.from(keyB64, 'base64'); - const encrypted = Buffer.from(encryptedB64, 'base64'); - - const iv = encrypted.subarray(0, 16); - const ciphertext = encrypted.subarray(16); - - const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); - const decrypted = Buffer.concat([ - decipher.update(ciphertext), - decipher.final(), - ]).toString('utf-8'); - - const data = JSON.parse(decrypted) as { - streaming_url?: string; - }; - - streamUrl = data.streaming_url; - } else { - /* fallback flow */ - const filecode = url.pathname.split('/').at(-1); - const apiUrl = new URL(`/ajax/stream?filecode=${filecode}`, url.origin); - - const jsonText = await this.fetcher.text(ctx, apiUrl, { headers }); - const data = JSON.parse(jsonText) as { - streaming_url?: string; - }; - - streamUrl = data.streaming_url; - } - - if (!streamUrl) { - throw new NotFoundError(); - } - - /* cleanup identical to Python resolver */ - streamUrl = streamUrl - .replace(/\\r\\n/g, '') - .replace(/\r|\n/g, '') - .replace(/\\\//g, '/'); + const $ = cheerio.load(html); + const title = $('title').text().trim(); return [ { - url: new URL(streamUrl), + url: playlistUrl, format: Format.hls, label: this.label, - sourceId: `${this.id}_${meta.countryCodes?.join('_')}`, ttl: this.ttl, - requestHeaders: { - Referer: referer, - Origin: url.origin, - }, + requestHeaders: headers, meta: { ...meta, + height: await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, url, { headers }), + 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..01262ec --- /dev/null +++ b/src/extractor/__snapshots__/StreamUp.test.ts.snap @@ -0,0 +1,23 @@ +// 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", + }, + "requestHeaders": { + "Origin": "https://strmup.to", + "Referer": "https://strmup.to/", + "User-Agent": "Mozilla/5.0", + }, + "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), -- 2.45.2 From 63ffcca3b982a1f3d6c2071e653998788e50f2f9 Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Fri, 9 Jan 2026 20:02:46 +0000 Subject: [PATCH 7/9] remove unneded playback headers --- src/extractor/StreamUp.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/extractor/StreamUp.ts b/src/extractor/StreamUp.ts index 9b10cba..b321f3b 100644 --- a/src/extractor/StreamUp.ts +++ b/src/extractor/StreamUp.ts @@ -42,10 +42,9 @@ export class StreamUp extends Extractor { format: Format.hls, label: this.label, ttl: this.ttl, - requestHeaders: headers, meta: { ...meta, - height: await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, url, { headers }), + height: await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, url), title, }, }, -- 2.45.2 From a6a7780b9081ca07aa62edb4c697ca815bdc0e14 Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Fri, 9 Jan 2026 20:04:14 +0000 Subject: [PATCH 8/9] use default user agent --- src/extractor/StreamUp.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/extractor/StreamUp.ts b/src/extractor/StreamUp.ts index b321f3b..9be0e28 100644 --- a/src/extractor/StreamUp.ts +++ b/src/extractor/StreamUp.ts @@ -22,11 +22,7 @@ export class StreamUp extends Extractor { } protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise { - const headers = { - 'Referer': `${url.origin}/`, - 'Origin': url.origin, - 'User-Agent': 'Mozilla/5.0', - }; + const headers = { Referer: `${url.origin}/`, Origin: url.origin }; const html = await this.fetcher.text(ctx, url, { headers }); -- 2.45.2 From dc1545ecb17c061f1063547b3b6e9174992404af Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Fri, 9 Jan 2026 20:06:08 +0000 Subject: [PATCH 9/9] update snapshot --- src/extractor/__snapshots__/StreamUp.test.ts.snap | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/extractor/__snapshots__/StreamUp.test.ts.snap b/src/extractor/__snapshots__/StreamUp.test.ts.snap index 01262ec..bece149 100644 --- a/src/extractor/__snapshots__/StreamUp.test.ts.snap +++ b/src/extractor/__snapshots__/StreamUp.test.ts.snap @@ -11,11 +11,6 @@ exports[`StreamUp handle one battle after another 1`] = ` "height": 1080, "title": "Wake.Up.Dead.Man.A.Knives.Out.Mystery.2025", }, - "requestHeaders": { - "Origin": "https://strmup.to", - "Referer": "https://strmup.to/", - "User-Agent": "Mozilla/5.0", - }, "ttl": 10800000, "url": "https://s3-hls2-cdn59.strmupcdn.com/hls/SJbD5Zm1pj06jZ2LDbHa13YBmryxas/master.m3u8?token=35f702a94c0e324d607d8761795a3788-1768016464-104.28.197.7-5ebad85b212e5e349b388501e051c992a2b68e70fdcaa6cc18270adee1f7c003", }, -- 2.45.2