Create StreamUp.ts

This commit is contained in:
GSTAR 2026-01-06 21:22:16 +01:00 committed by webstreamr
parent 28a8a1554d
commit e1b85dafe7

113
src/extractor/StreamUp.ts Normal file
View file

@ -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<UrlResult[]> {
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,
},
},
];
}
}