mirror of
https://gitlab.com/vyla-entertainment/stream-api.git
synced 2026-11-09 20:27:19 -05:00
updated
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# [DMCA](https://docs.vyla.cc/misc/dmca)
|
||||
# [DMCA](https://vyla.cc/misc/project/DMCA)
|
||||
|
||||
If your source is listed here, I sincerely apologize. I understand that its inclusion may be costing you a lot of money, and that was never my intention. Please contact me as soon as possible, and I'll remove it ASAP.
|
||||
|
||||
@@ -10,7 +10,7 @@ Original work and core authorship:
|
||||
|
||||
[@alwaysmesmerizingyou](https://gitlab.com/alwaysmesmerizingyou)
|
||||
|
||||
[@GavinGoGaming](https://gitlab.com/GavinGoGaming)
|
||||
[@GavinGoGaming](https://github.com/GavinGoGaming)
|
||||
|
||||
Full credits and acknowledgements:
|
||||
https://docs.vyla.cc/misc/credit
|
||||
https://vyla.cc/misc/project/credit
|
||||
@@ -5,7 +5,7 @@
|
||||
"description": "Stream API — multi-provider stream scraper",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@vyla-entertainment/sdk": "^1.12.0",
|
||||
"@vyla-entertainment/sdk": "latest",
|
||||
"dotenv": "^17.4.2",
|
||||
"latest": "^0.2.0"
|
||||
}
|
||||
|
||||
112
server.js
112
server.js
@@ -587,50 +587,104 @@ function applyCdnHeaders(cleanUrl, extraHeaders, sourceKey) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getSdkStreams(sourceKey, id, s, e, clientIP) {
|
||||
if (typeof sdk.getStreams === 'function') {
|
||||
try {
|
||||
const res = await sdk.getStreams(sourceKey, id, s, e, clientIP);
|
||||
if (res && (Array.isArray(res) ? res.length > 0 : Object.keys(res).length > 0)) {
|
||||
return res;
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
return sdk.getStream(sourceKey, id, s, e, clientIP);
|
||||
}
|
||||
|
||||
function fetchSource(cfg, cacheKey, id, s, e, clientIP, absoluteBase) {
|
||||
function fixSource(url) {
|
||||
if (url.includes('/api?url=')) {
|
||||
if (typeof url === 'string' && url.includes('/api?url=')) {
|
||||
return `${absoluteBase}/api?url=${encodeURIComponent(url)}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
function applyFix(result) {
|
||||
if (result && Array.isArray(result.allUrls)) {
|
||||
result.allUrls = result.allUrls.map(entry => ({
|
||||
...entry,
|
||||
url: fixSource(entry.url)
|
||||
}));
|
||||
}
|
||||
if (!result) return result;
|
||||
const fixEntry = item => {
|
||||
if (typeof item === 'string') return fixSource(item);
|
||||
if (item && typeof item === 'object') {
|
||||
const target = item.url || item.file || item.streamUrl || item.link;
|
||||
if (target) return { ...item, url: fixSource(target) };
|
||||
}
|
||||
return item;
|
||||
};
|
||||
|
||||
if (Array.isArray(result)) return result.map(fixEntry);
|
||||
if (Array.isArray(result.streams)) result.streams = result.streams.map(fixEntry);
|
||||
if (Array.isArray(result.allUrls)) result.allUrls = result.allUrls.map(fixEntry);
|
||||
if (Array.isArray(result.sources)) result.sources = result.sources.map(fixEntry);
|
||||
if (Array.isArray(result.urls)) result.urls = result.urls.map(fixEntry);
|
||||
if (typeof result.url === 'string') result.url = fixSource(result.url);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (cfg.skipCache) {
|
||||
return withTimeout(
|
||||
jitter(cfg.jitter).then(() => withRetry(() => sdk.getStream(cfg.key, id, s, e, clientIP), cfg.retries, 300)),
|
||||
jitter(cfg.jitter).then(() => withRetry(() => getSdkStreams(cfg.key, id, s, e, clientIP), cfg.retries, 300)),
|
||||
cfg.timeout
|
||||
).then(applyFix);
|
||||
}
|
||||
|
||||
return withTimeout(
|
||||
jitter(cfg.jitter).then(() =>
|
||||
getSharedCached(`${cfg.key}-${cacheKey}`, () => withRetry(() => sdk.getStream(cfg.key, id, s, e, clientIP), cfg.retries, 300))
|
||||
getSharedCached(`${cfg.key}-${cacheKey}`, () => withRetry(() => getSdkStreams(cfg.key, id, s, e, clientIP), cfg.retries, 300))
|
||||
),
|
||||
cfg.timeout
|
||||
).then(applyFix);
|
||||
}
|
||||
|
||||
function normalizeCandidates(rawResult) {
|
||||
let candidates = [];
|
||||
if (rawResult?.allUrls?.length) {
|
||||
candidates = rawResult.allUrls.map(u => typeof u === 'object' ? u : { url: u });
|
||||
} else if (rawResult?.streams?.length) {
|
||||
candidates = rawResult.streams.map(u => typeof u === 'object' ? u : { url: u });
|
||||
} else if (Array.isArray(rawResult)) {
|
||||
candidates = rawResult.map(u => typeof u === 'object' ? u : { url: u });
|
||||
} else if (rawResult) {
|
||||
candidates = [{ url: typeof rawResult === 'object' ? rawResult.url : rawResult, headers: rawResult?.headers, skipProxy: rawResult?.skipProxy, skipHlsCheck: rawResult?.skipHlsCheck }];
|
||||
if (!rawResult) return [];
|
||||
const pool = [];
|
||||
|
||||
if (Array.isArray(rawResult)) {
|
||||
pool.push(...rawResult);
|
||||
} else if (typeof rawResult === 'object') {
|
||||
if (Array.isArray(rawResult.streams)) pool.push(...rawResult.streams);
|
||||
if (Array.isArray(rawResult.allUrls)) pool.push(...rawResult.allUrls);
|
||||
if (Array.isArray(rawResult.sources)) pool.push(...rawResult.sources);
|
||||
if (Array.isArray(rawResult.urls)) pool.push(...rawResult.urls);
|
||||
if (rawResult.url) {
|
||||
pool.push({
|
||||
url: rawResult.url,
|
||||
headers: rawResult.headers,
|
||||
quality: rawResult.quality,
|
||||
type: rawResult.type,
|
||||
name: rawResult.name,
|
||||
label: rawResult.label,
|
||||
language: rawResult.language
|
||||
});
|
||||
}
|
||||
} else if (typeof rawResult === 'string') {
|
||||
pool.push({ url: rawResult });
|
||||
}
|
||||
|
||||
const seen = new Set();
|
||||
const candidates = [];
|
||||
|
||||
for (const item of pool) {
|
||||
if (!item) continue;
|
||||
const candidate = typeof item === 'object'
|
||||
? { ...item, url: item.url || item.file || item.streamUrl || item.link }
|
||||
: { url: item };
|
||||
|
||||
if (typeof candidate.url !== 'string') continue;
|
||||
const trimmed = candidate.url.trim();
|
||||
if (!trimmed || seen.has(trimmed)) continue;
|
||||
seen.add(trimmed);
|
||||
candidate.url = trimmed;
|
||||
candidates.push(candidate);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
@@ -772,8 +826,15 @@ async function verifyServed(finalUrl, headers, absoluteBase, trace = []) {
|
||||
|
||||
async function verifyCandidate(candidate, sourceKey, absoluteBase, trace = []) {
|
||||
if (!candidate?.url) return null;
|
||||
const wrapped = wrapUrl(candidate, sourceKey, absoluteBase, sdk);
|
||||
if (!wrapped) { trace.push('wrapUrl returned null'); return null; }
|
||||
let wrapped = wrapUrl(candidate, sourceKey, absoluteBase, sdk);
|
||||
if (!wrapped || (!wrapped.includes('/api?url=') && !wrapped.includes('/api/?url='))) {
|
||||
const cfg = sdk.getSources().find(c => c.key === sourceKey);
|
||||
const extraParam = cfg?.proxyParam ? `&${cfg.proxyParam}=1` : '&vn=1';
|
||||
const headersParam = candidate.headers && Object.keys(candidate.headers).length
|
||||
? `&proxyHeaders=${encodeURIComponent(JSON.stringify(candidate.headers))}`
|
||||
: '';
|
||||
wrapped = `${absoluteBase}/api?url=${encodeURIComponent(candidate.url)}${extraParam}${headersParam}`;
|
||||
}
|
||||
|
||||
const viaProxy = wrapped !== candidate.url || toLoopback(wrapped, absoluteBase) !== wrapped;
|
||||
trace.push(`wrapped=${wrapped} viaProxy=${viaProxy}`);
|
||||
@@ -783,9 +844,9 @@ async function verifyCandidate(candidate, sourceKey, absoluteBase, trace = []) {
|
||||
);
|
||||
if (!ok) { trace.push('verify failed or timed out'); return null; }
|
||||
|
||||
const out = { url: wrapped, raw_url: candidate.url, proxied: viaProxy };
|
||||
const out = { url: wrapped, raw_url: candidate.url, proxied: true };
|
||||
if (!viaProxy && candidate.headers && Object.keys(candidate.headers).length) out.headers = candidate.headers;
|
||||
for (const k of ['quality', 'type', 'name', 'language']) {
|
||||
for (const k of ['quality', 'type', 'name', 'label', 'language', 'resolution']) {
|
||||
if (candidate[k] !== undefined) out[k] = candidate[k];
|
||||
}
|
||||
return out;
|
||||
@@ -846,7 +907,7 @@ async function handleTestSource(sdk, sourceKey, id, s, e, clientIP, host) {
|
||||
|
||||
try {
|
||||
rawResult = await fetchSource(cfg, `${id}-${s ?? ''}-${e ?? ''}`, id, s, e, clientIP, absoluteBase);
|
||||
if (!rawResult) rawResult = await withTimeout(sdk.getStream(sourceKey, id, s, e), 20_000);
|
||||
if (!rawResult) rawResult = await withTimeout(getSdkStreams(sourceKey, id, s, e, clientIP), 20_000);
|
||||
} catch (err) { fetchError = err.message; }
|
||||
|
||||
const seen = new Set();
|
||||
@@ -932,13 +993,14 @@ async function streamSources(sources, id, s, e, clientIP, absoluteBase, res) {
|
||||
sent.add(stream.url);
|
||||
n++;
|
||||
const { raw_url, ...publicStream } = stream;
|
||||
const streamLabel = publicStream.label || publicStream.name || (publicStream.quality ? `${label} (${publicStream.quality})` : (n > 1 ? `${label} ${n}` : label));
|
||||
safeWrite(`data: ${JSON.stringify({
|
||||
type: 'source',
|
||||
source: {
|
||||
source: cfg.key,
|
||||
label: n > 1 ? `${label} ${n}` : label,
|
||||
index: n - 1,
|
||||
...publicStream,
|
||||
label: streamLabel,
|
||||
},
|
||||
})}\n\n`);
|
||||
}
|
||||
@@ -975,7 +1037,7 @@ async function handleRequest(req, res) {
|
||||
if (pathname === '/' || pathname === '') {
|
||||
return {
|
||||
status: 200,
|
||||
body: `${LOGO_TEXT}\n\ndeveloped_by: @vyla-entertainment\ngitlab: https://gitlab.com/vyla-entertainment\ndocs: https://docs.vyla.cc\ndmca: https://docs.vyla.cc/misc/dmca`,
|
||||
body: `${LOGO_TEXT}\n\ndeveloped_by: @vyla-entertainment\ngitlab: https://gitlab.com/vyla-entertainment\ndocs: https://vyla.cc/stream-api/overview\ndmca: https://vyla.cc/misc/project/dmca`,
|
||||
headers: { 'Content-Type': 'text/plain; charset=utf-8', ...CORS_HEADERS },
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user