fix: failing on nuviotv due to es2016
This commit is contained in:
1 parent
27a87191c3
commit
c7eb7b83a9
6 files changed
+390
-469
No files matched your search
+12
-36
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* _template - Built from src/_template/
|
||||
* Generated: 2026-07-19T17:21:01.885Z
|
||||
* Generated: 2026-07-20T14:16:44.846Z
|
||||
*/
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
@@ -24,46 +24,22 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __async = (__this, __arguments, generator) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var fulfilled = (value) => {
|
||||
try {
|
||||
step(generator.next(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
var rejected = (value) => {
|
||||
try {
|
||||
step(generator.throw(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
||||
step((generator = generator.apply(__this, __arguments)).next());
|
||||
});
|
||||
};
|
||||
|
||||
// src/_template/extractor.js
|
||||
var import_cheerio_without_node_native = __toESM(require("cheerio-without-node-native"));
|
||||
function extractStreams(tmdbId, mediaType, season, episode) {
|
||||
return __async(this, null, function* () {
|
||||
return [];
|
||||
});
|
||||
async function extractStreams(tmdbId, mediaType, season, episode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// src/_template/index.js
|
||||
function getStreams(tmdbId, mediaType, season, episode) {
|
||||
return __async(this, null, function* () {
|
||||
try {
|
||||
console.log(`[Template] Request: ${mediaType} ${tmdbId}`);
|
||||
const streams = yield extractStreams(tmdbId, mediaType, season, episode);
|
||||
return streams;
|
||||
} catch (error) {
|
||||
console.error(`[Template] Error: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
async function getStreams(tmdbId, mediaType, season, episode) {
|
||||
try {
|
||||
console.log(`[Template] Request: ${mediaType} ${tmdbId}`);
|
||||
const streams = await extractStreams(tmdbId, mediaType, season, episode);
|
||||
return streams;
|
||||
} catch (error) {
|
||||
console.error(`[Template] Error: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
module.exports = { getStreams };
|
||||
+157
-176
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* flixer - Built from src/flixer/
|
||||
* Generated: 2026-07-19T17:21:01.894Z
|
||||
* Generated: 2026-07-20T14:16:44.852Z
|
||||
*/
|
||||
var __defProp = Object.defineProperty;
|
||||
var __defProps = Object.defineProperties;
|
||||
@@ -21,26 +21,6 @@ var __spreadValues = (a, b) => {
|
||||
return a;
|
||||
};
|
||||
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
||||
var __async = (__this, __arguments, generator) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var fulfilled = (value) => {
|
||||
try {
|
||||
step(generator.next(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
var rejected = (value) => {
|
||||
try {
|
||||
step(generator.throw(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
||||
step((generator = generator.apply(__this, __arguments)).next());
|
||||
});
|
||||
};
|
||||
|
||||
// src/flixer/http.js
|
||||
var SITE_URL = "https://flixer.gd";
|
||||
@@ -244,55 +224,49 @@ function createNonce() {
|
||||
}
|
||||
var serverTimeOffset = 0;
|
||||
var serverTimeFetchedAt = 0;
|
||||
function getServerTimestamp() {
|
||||
return __async(this, null, function* () {
|
||||
const now = Date.now();
|
||||
if (serverTimeFetchedAt && now - serverTimeFetchedAt < 3e5) {
|
||||
return Math.floor((now + serverTimeOffset) / 1e3);
|
||||
}
|
||||
try {
|
||||
const startedAt = Date.now();
|
||||
const response = yield fetch(`${API_URL}/api/time?t=${startedAt}`, {
|
||||
headers: __spreadProps(__spreadValues({}, HEADERS), { "Cache-Control": "no-cache" })
|
||||
});
|
||||
if (!response.ok)
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
const payload = yield response.json();
|
||||
if (!payload || typeof payload.timestamp !== "number")
|
||||
throw new Error("invalid response");
|
||||
const finishedAt = Date.now();
|
||||
serverTimeOffset = payload.timestamp * 1e3 + (finishedAt - startedAt) / 2 - finishedAt;
|
||||
serverTimeFetchedAt = finishedAt;
|
||||
return Math.floor((finishedAt + serverTimeOffset) / 1e3);
|
||||
} catch (_) {
|
||||
return Math.floor(Date.now() / 1e3);
|
||||
}
|
||||
});
|
||||
}
|
||||
function createSecureHeaders(apiKey, path) {
|
||||
return __async(this, null, function* () {
|
||||
const timestamp = yield getServerTimestamp();
|
||||
const nonce = createNonce();
|
||||
const message = `${apiKey}:${timestamp}:${nonce}:${path}`;
|
||||
return {
|
||||
"X-Api-Key": apiKey,
|
||||
"X-Request-Timestamp": String(timestamp),
|
||||
"X-Request-Nonce": nonce,
|
||||
"X-Request-Signature": encodeBase64(hmacSha256(apiKey, message)),
|
||||
"X-Client-Fingerprint": "zerlgt",
|
||||
"X-Fingerprint-Lite": "e9136c41504646444"
|
||||
};
|
||||
});
|
||||
}
|
||||
function fetchText(_0) {
|
||||
return __async(this, arguments, function* (url, options = {}) {
|
||||
const response = yield fetch(url, __spreadProps(__spreadValues({}, options), {
|
||||
headers: __spreadValues(__spreadValues({}, HEADERS), options.headers || {})
|
||||
}));
|
||||
async function getServerTimestamp() {
|
||||
const now = Date.now();
|
||||
if (serverTimeFetchedAt && now - serverTimeFetchedAt < 3e5) {
|
||||
return Math.floor((now + serverTimeOffset) / 1e3);
|
||||
}
|
||||
try {
|
||||
const startedAt = Date.now();
|
||||
const response = await fetch(`${API_URL}/api/time?t=${startedAt}`, {
|
||||
headers: __spreadProps(__spreadValues({}, HEADERS), { "Cache-Control": "no-cache" })
|
||||
});
|
||||
if (!response.ok)
|
||||
throw new Error(`HTTP ${response.status} for ${url}`);
|
||||
return yield response.text();
|
||||
});
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
const payload = await response.json();
|
||||
if (!payload || typeof payload.timestamp !== "number")
|
||||
throw new Error("invalid response");
|
||||
const finishedAt = Date.now();
|
||||
serverTimeOffset = payload.timestamp * 1e3 + (finishedAt - startedAt) / 2 - finishedAt;
|
||||
serverTimeFetchedAt = finishedAt;
|
||||
return Math.floor((finishedAt + serverTimeOffset) / 1e3);
|
||||
} catch (_) {
|
||||
return Math.floor(Date.now() / 1e3);
|
||||
}
|
||||
}
|
||||
async function createSecureHeaders(apiKey, path) {
|
||||
const timestamp = await getServerTimestamp();
|
||||
const nonce = createNonce();
|
||||
const message = `${apiKey}:${timestamp}:${nonce}:${path}`;
|
||||
return {
|
||||
"X-Api-Key": apiKey,
|
||||
"X-Request-Timestamp": String(timestamp),
|
||||
"X-Request-Nonce": nonce,
|
||||
"X-Request-Signature": encodeBase64(hmacSha256(apiKey, message)),
|
||||
"X-Client-Fingerprint": "zerlgt",
|
||||
"X-Fingerprint-Lite": "e9136c41504646444"
|
||||
};
|
||||
}
|
||||
async function fetchText(url, options = {}) {
|
||||
const response = await fetch(url, __spreadProps(__spreadValues({}, options), {
|
||||
headers: __spreadValues(__spreadValues({}, HEADERS), options.headers || {})
|
||||
}));
|
||||
if (!response.ok)
|
||||
throw new Error(`HTTP ${response.status} for ${url}`);
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
// src/flixer/decoder.js
|
||||
@@ -591,54 +565,67 @@ var NATO_ORDER = [
|
||||
"zulu"
|
||||
];
|
||||
var SERVER_LABELS = {
|
||||
alpha: "Ares",
|
||||
bravo: "Balder",
|
||||
charlie: "Circe",
|
||||
delta: "Dionysus",
|
||||
echo: "Eros",
|
||||
foxtrot: "Freya",
|
||||
golf: "Gaia",
|
||||
hotel: "Hades",
|
||||
india: "Isis",
|
||||
juliet: "Juno",
|
||||
kilo: "Kronos",
|
||||
lima: "Loki"
|
||||
lima: "Loki",
|
||||
mike: "Medusa",
|
||||
november: "Nyx",
|
||||
oscar: "Odin",
|
||||
papa: "Persephone",
|
||||
quebec: "Quirinus",
|
||||
romeo: "Ra",
|
||||
sierra: "Selene",
|
||||
tango: "Thor",
|
||||
uniform: "Uranus",
|
||||
victor: "Vulcan",
|
||||
whiskey: "Woden",
|
||||
xray: "Xolotl",
|
||||
yankee: "Ymir",
|
||||
zulu: "Zeus"
|
||||
};
|
||||
var TMDB_ID_CACHE = {};
|
||||
function resolveTmdbId(id, mediaType) {
|
||||
return __async(this, null, function* () {
|
||||
if (/^\d+$/.test(id))
|
||||
return id;
|
||||
if (!/^tt\d+$/i.test(id))
|
||||
async function resolveTmdbId(id, mediaType) {
|
||||
if (/^\d+$/.test(id))
|
||||
return id;
|
||||
if (!/^tt\d+$/i.test(id))
|
||||
return null;
|
||||
const cacheKey = `${mediaType}:${id.toLowerCase()}`;
|
||||
if (TMDB_ID_CACHE[cacheKey])
|
||||
return TMDB_ID_CACHE[cacheKey];
|
||||
const catalogType = mediaType === "movie" ? "movie" : "series";
|
||||
try {
|
||||
const raw = await fetchText(`https://v3-cinemeta.strem.io/meta/${catalogType}/${encodeURIComponent(id)}.json`, {
|
||||
headers: { "Accept": "application/json" }
|
||||
});
|
||||
const payload = JSON.parse(raw);
|
||||
const resolved = payload && payload.meta && payload.meta.moviedb_id;
|
||||
if (!resolved || !/^\d+$/.test(String(resolved)))
|
||||
return null;
|
||||
const cacheKey = `${mediaType}:${id.toLowerCase()}`;
|
||||
if (TMDB_ID_CACHE[cacheKey])
|
||||
return TMDB_ID_CACHE[cacheKey];
|
||||
const catalogType = mediaType === "movie" ? "movie" : "series";
|
||||
try {
|
||||
const raw = yield fetchText(`https://v3-cinemeta.strem.io/meta/${catalogType}/${encodeURIComponent(id)}.json`, {
|
||||
headers: { "Accept": "application/json" }
|
||||
});
|
||||
const payload = JSON.parse(raw);
|
||||
const resolved = payload && payload.meta && payload.meta.moviedb_id;
|
||||
if (!resolved || !/^\d+$/.test(String(resolved)))
|
||||
return null;
|
||||
TMDB_ID_CACHE[cacheKey] = String(resolved);
|
||||
console.log(`[Flixer] Resolved ${id} to TMDB ${resolved}`);
|
||||
return TMDB_ID_CACHE[cacheKey];
|
||||
} catch (error) {
|
||||
console.warn(`[Flixer] Could not resolve IMDb ID ${id}: ${error && error.message ? error.message : String(error)}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
TMDB_ID_CACHE[cacheKey] = String(resolved);
|
||||
console.log(`[Flixer] Resolved ${id} to TMDB ${resolved}`);
|
||||
return TMDB_ID_CACHE[cacheKey];
|
||||
} catch (error) {
|
||||
console.warn(`[Flixer] Could not resolve IMDb ID ${id}: ${error && error.message ? error.message : String(error)}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function buildApiPath(tmdbId, mediaType, season, episode) {
|
||||
if (mediaType === "movie")
|
||||
return `/api/tmdb/movie/${encodeURIComponent(tmdbId)}/images`;
|
||||
return `/api/tmdb/tv/${encodeURIComponent(tmdbId)}/season/${season}/episode/${episode}/images`;
|
||||
}
|
||||
function decryptPayload(encrypted, apiKey) {
|
||||
return __async(this, null, function* () {
|
||||
const clearText = yield process_img_data(encrypted, apiKey);
|
||||
return JSON.parse(clearText);
|
||||
});
|
||||
async function decryptPayload(encrypted, apiKey) {
|
||||
const clearText = await process_img_data(encrypted, apiKey);
|
||||
return JSON.parse(clearText);
|
||||
}
|
||||
function getServers(payload) {
|
||||
const values = [];
|
||||
@@ -674,90 +661,84 @@ function getQuality(url) {
|
||||
const match = String(url).match(/(?:^|[^0-9])(2160|1440|1080|720|480|360)p?(?:[^0-9]|$)/i);
|
||||
return match ? `${match[1]}p` : "Auto";
|
||||
}
|
||||
function fetchServerPayload(apiUrl, apiPath, apiKey, server) {
|
||||
return __async(this, null, function* () {
|
||||
const secureHeaders = yield createSecureHeaders(apiKey, apiPath);
|
||||
return yield fetchText(apiUrl, {
|
||||
headers: __spreadProps(__spreadValues({}, secureHeaders), {
|
||||
"X-Only-Sources": "1",
|
||||
"X-Server": server
|
||||
})
|
||||
});
|
||||
async function fetchServerPayload(apiUrl, apiPath, apiKey, server) {
|
||||
const secureHeaders = await createSecureHeaders(apiKey, apiPath);
|
||||
return await fetchText(apiUrl, {
|
||||
headers: __spreadProps(__spreadValues({}, secureHeaders), {
|
||||
"X-Only-Sources": "1",
|
||||
"X-Server": server
|
||||
})
|
||||
});
|
||||
}
|
||||
function extractStreams(tmdbId, mediaType, season, episode) {
|
||||
return __async(this, null, function* () {
|
||||
const id = String(tmdbId || "").trim();
|
||||
if (mediaType !== "movie" && mediaType !== "tv")
|
||||
return [];
|
||||
if (mediaType === "tv" && (!Number.isInteger(Number(season)) || !Number.isInteger(Number(episode)) || Number(season) < 1 || Number(episode) < 1))
|
||||
return [];
|
||||
const resolvedId = yield resolveTmdbId(id, mediaType);
|
||||
if (!resolvedId)
|
||||
return [];
|
||||
initDecoder();
|
||||
const apiKey = get_img_key();
|
||||
const apiPath = buildApiPath(resolvedId, mediaType, Number(season), Number(episode));
|
||||
const apiUrl = `${API_URL}${apiPath}`;
|
||||
const secureHeaders = yield createSecureHeaders(apiKey, apiPath);
|
||||
const serverPayload = yield fetchText(apiUrl, {
|
||||
headers: __spreadProps(__spreadValues({}, secureHeaders), { bW90aGFmYWth: "1" })
|
||||
});
|
||||
const servers = getServers(yield decryptPayload(serverPayload, apiKey));
|
||||
const encryptedResponses = yield Promise.all(servers.map((server) => __async(this, null, function* () {
|
||||
try {
|
||||
return { server, encrypted: yield fetchServerPayload(apiUrl, apiPath, apiKey, server) };
|
||||
} catch (error) {
|
||||
console.warn(`[Flixer] ${server} request failed: ${error && error.message ? error.message : String(error)}`);
|
||||
return { server, encrypted: "" };
|
||||
}
|
||||
})));
|
||||
const streams = [];
|
||||
const seenUrls = {};
|
||||
for (const response of encryptedResponses) {
|
||||
if (!response.encrypted)
|
||||
async function extractStreams(tmdbId, mediaType, season, episode) {
|
||||
const id = String(tmdbId || "").trim();
|
||||
if (mediaType !== "movie" && mediaType !== "tv")
|
||||
return [];
|
||||
if (mediaType === "tv" && (!Number.isInteger(Number(season)) || !Number.isInteger(Number(episode)) || Number(season) < 1 || Number(episode) < 1))
|
||||
return [];
|
||||
const resolvedId = await resolveTmdbId(id, mediaType);
|
||||
if (!resolvedId)
|
||||
return [];
|
||||
initDecoder();
|
||||
const apiKey = get_img_key();
|
||||
const apiPath = buildApiPath(resolvedId, mediaType, Number(season), Number(episode));
|
||||
const apiUrl = `${API_URL}${apiPath}`;
|
||||
const secureHeaders = await createSecureHeaders(apiKey, apiPath);
|
||||
const serverPayload = await fetchText(apiUrl, {
|
||||
headers: __spreadProps(__spreadValues({}, secureHeaders), { bW90aGFmYWth: "1" })
|
||||
});
|
||||
const servers = getServers(await decryptPayload(serverPayload, apiKey));
|
||||
const encryptedResponses = await Promise.all(servers.map(async (server) => {
|
||||
try {
|
||||
return { server, encrypted: await fetchServerPayload(apiUrl, apiPath, apiKey, server) };
|
||||
} catch (error) {
|
||||
console.warn(`[Flixer] ${server} request failed: ${error && error.message ? error.message : String(error)}`);
|
||||
return { server, encrypted: "" };
|
||||
}
|
||||
}));
|
||||
const streams = [];
|
||||
const seenUrls = {};
|
||||
for (const response of encryptedResponses) {
|
||||
if (!response.encrypted)
|
||||
continue;
|
||||
try {
|
||||
const url = findUrl(await decryptPayload(response.encrypted, apiKey), response.server);
|
||||
if (!url || seenUrls[url])
|
||||
continue;
|
||||
try {
|
||||
const url = findUrl(yield decryptPayload(response.encrypted, apiKey), response.server);
|
||||
if (!url || seenUrls[url])
|
||||
continue;
|
||||
seenUrls[url] = true;
|
||||
const label = SERVER_LABELS[response.server] || response.server.charAt(0).toUpperCase() + response.server.slice(1);
|
||||
const quality = getQuality(url);
|
||||
streams.push({
|
||||
name: `Flixer \xB7 ${label}`,
|
||||
title: `${label} \xB7 ${quality}`,
|
||||
url,
|
||||
quality,
|
||||
headers: {
|
||||
"Referer": `${SITE_URL}/`,
|
||||
"Origin": SITE_URL,
|
||||
"User-Agent": HEADERS["User-Agent"]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error && error.message ? error.message : String(error);
|
||||
if (!/^E5[678]$/.test(message)) {
|
||||
console.warn(`[Flixer] ${response.server} decode failed: ${message}`);
|
||||
seenUrls[url] = true;
|
||||
const label = SERVER_LABELS[response.server] || response.server.charAt(0).toUpperCase() + response.server.slice(1);
|
||||
const quality = getQuality(url);
|
||||
streams.push({
|
||||
name: `Flixer \xB7 ${label}`,
|
||||
title: `${label} \xB7 ${quality}`,
|
||||
url,
|
||||
quality,
|
||||
headers: {
|
||||
"Referer": `${SITE_URL}/`,
|
||||
"Origin": SITE_URL,
|
||||
"User-Agent": HEADERS["User-Agent"]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error && error.message ? error.message : String(error);
|
||||
if (!/^E5[678]$/.test(message)) {
|
||||
console.warn(`[Flixer] ${response.server} decode failed: ${message}`);
|
||||
}
|
||||
}
|
||||
return streams;
|
||||
});
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
|
||||
// src/flixer/index.js
|
||||
function getStreams(tmdbId, mediaType, season, episode) {
|
||||
return __async(this, null, function* () {
|
||||
try {
|
||||
console.log(`[Flixer] Request: ${mediaType} ${tmdbId}`);
|
||||
const streams = yield extractStreams(tmdbId, mediaType, season, episode);
|
||||
console.log(`[Flixer] Found ${streams.length} stream(s)`);
|
||||
return streams;
|
||||
} catch (error) {
|
||||
console.error(`[Flixer] Error: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
async function getStreams(tmdbId, mediaType, season, episode) {
|
||||
try {
|
||||
console.log(`[Flixer] Request: ${mediaType} ${tmdbId}`);
|
||||
const streams = await extractStreams(tmdbId, mediaType, season, episode);
|
||||
console.log(`[Flixer] Found ${streams.length} stream(s)`);
|
||||
return streams;
|
||||
} catch (error) {
|
||||
console.error(`[Flixer] Error: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
module.exports = { getStreams };
|
||||
+208
-250
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* miruro - Built from src/miruro/
|
||||
* Generated: 2026-07-19T17:21:26.205Z
|
||||
* Generated: 2026-07-20T14:16:44.855Z
|
||||
*/
|
||||
var __defProp = Object.defineProperty;
|
||||
var __defProps = Object.defineProperties;
|
||||
@@ -21,26 +21,6 @@ var __spreadValues = (a, b) => {
|
||||
return a;
|
||||
};
|
||||
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
||||
var __async = (__this, __arguments, generator) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var fulfilled = (value) => {
|
||||
try {
|
||||
step(generator.next(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
var rejected = (value) => {
|
||||
try {
|
||||
step(generator.throw(value));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
||||
step((generator = generator.apply(__this, __arguments)).next());
|
||||
});
|
||||
};
|
||||
|
||||
// node_modules/fflate/esm/browser.js
|
||||
var u8 = Uint8Array;
|
||||
@@ -528,46 +508,40 @@ function decodeSecurePayload(raw, obfuscationVersion) {
|
||||
function mergeHeaders(headers) {
|
||||
return __spreadValues(__spreadValues({}, HEADERS), headers || {});
|
||||
}
|
||||
function fetchText(_0) {
|
||||
return __async(this, arguments, function* (url, options = {}) {
|
||||
const response = yield fetch(url, __spreadProps(__spreadValues({}, options), {
|
||||
headers: mergeHeaders(options.headers)
|
||||
}));
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status} for ${url}`);
|
||||
}
|
||||
return yield response.text();
|
||||
});
|
||||
async function fetchText(url, options = {}) {
|
||||
const response = await fetch(url, __spreadProps(__spreadValues({}, options), {
|
||||
headers: mergeHeaders(options.headers)
|
||||
}));
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status} for ${url}`);
|
||||
}
|
||||
return await response.text();
|
||||
}
|
||||
function fetchJson(_0) {
|
||||
return __async(this, arguments, function* (url, options = {}) {
|
||||
const raw = yield fetchText(url, options);
|
||||
return JSON.parse(raw);
|
||||
});
|
||||
async function fetchJson(url, options = {}) {
|
||||
const raw = await fetchText(url, options);
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
function secureGet(_0) {
|
||||
return __async(this, arguments, function* (path, query = {}) {
|
||||
const envelope = encodeBase64Url(JSON.stringify({
|
||||
path,
|
||||
method: "GET",
|
||||
query,
|
||||
body: null
|
||||
}));
|
||||
const url = `${BASE_URL}/api/secure/pipe?e=${envelope}`;
|
||||
let response;
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
response = yield fetch(url, { headers: HEADERS });
|
||||
if (response.ok || response.status !== 403 && response.status !== 429 && response.status < 500) {
|
||||
break;
|
||||
}
|
||||
async function secureGet(path, query = {}) {
|
||||
const envelope = encodeBase64Url(JSON.stringify({
|
||||
path,
|
||||
method: "GET",
|
||||
query,
|
||||
body: null
|
||||
}));
|
||||
const url = `${BASE_URL}/api/secure/pipe?e=${envelope}`;
|
||||
let response;
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
response = await fetch(url, { headers: HEADERS });
|
||||
if (response.ok || response.status !== 403 && response.status !== 429 && response.status < 500) {
|
||||
break;
|
||||
}
|
||||
if (!response || !response.ok) {
|
||||
throw new Error(`Miruro API returned HTTP ${response ? response.status : "unknown"}`);
|
||||
}
|
||||
const raw = yield response.text();
|
||||
const version = response.headers && response.headers.get ? response.headers.get("x-obfuscated") : null;
|
||||
return decodeSecurePayload(raw.trim(), version);
|
||||
});
|
||||
}
|
||||
if (!response || !response.ok) {
|
||||
throw new Error(`Miruro API returned HTTP ${response ? response.status : "unknown"}`);
|
||||
}
|
||||
const raw = await response.text();
|
||||
const version = response.headers && response.headers.get ? response.headers.get("x-obfuscated") : null;
|
||||
return decodeSecurePayload(raw.trim(), version);
|
||||
}
|
||||
|
||||
// src/miruro/extractor.js
|
||||
@@ -647,16 +621,15 @@ function getLocalEpisode(mappings, mediaType, season, episode) {
|
||||
}
|
||||
return requestedEpisode - offset;
|
||||
}
|
||||
function getAniListSequel(anilistId) {
|
||||
return __async(this, null, function* () {
|
||||
const response = yield fetchJson(ANILIST_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
async function getAniListSequel(anilistId) {
|
||||
const response = await fetchJson(ANILIST_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
query ($id: Int!) {
|
||||
Media(id: $id, type: ANIME) {
|
||||
relations {
|
||||
@@ -672,85 +645,80 @@ function getAniListSequel(anilistId) {
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: anilistId }
|
||||
})
|
||||
});
|
||||
const edges = response && response.data && response.data.Media && response.data.Media.relations ? response.data.Media.relations.edges : [];
|
||||
const sequel = (edges || []).find((edge) => edge.relationType === "SEQUEL" && edge.node && edge.node.type === "ANIME" && ["TV", "TV_SHORT", "ONA"].includes(edge.node.format));
|
||||
return sequel ? asNumber(sequel.node.id) : null;
|
||||
variables: { id: anilistId }
|
||||
})
|
||||
});
|
||||
const edges = response && response.data && response.data.Media && response.data.Media.relations ? response.data.Media.relations.edges : [];
|
||||
const sequel = (edges || []).find((edge) => edge.relationType === "SEQUEL" && edge.node && edge.node.type === "ANIME" && ["TV", "TV_SHORT", "ONA"].includes(edge.node.format));
|
||||
return sequel ? asNumber(sequel.node.id) : null;
|
||||
}
|
||||
function getSeasonAniListId(anilistId, season) {
|
||||
return __async(this, null, function* () {
|
||||
const requestedSeason = asNumber(season) || 1;
|
||||
let currentId = anilistId;
|
||||
for (let currentSeason = 1; currentSeason < requestedSeason; currentSeason++) {
|
||||
const sequelId = yield getAniListSequel(currentId);
|
||||
if (!sequelId || sequelId === currentId) {
|
||||
return null;
|
||||
}
|
||||
currentId = sequelId;
|
||||
async function getSeasonAniListId(anilistId, season) {
|
||||
const requestedSeason = asNumber(season) || 1;
|
||||
let currentId = anilistId;
|
||||
for (let currentSeason = 1; currentSeason < requestedSeason; currentSeason++) {
|
||||
const sequelId = await getAniListSequel(currentId);
|
||||
if (!sequelId || sequelId === currentId) {
|
||||
return null;
|
||||
}
|
||||
return currentId;
|
||||
});
|
||||
currentId = sequelId;
|
||||
}
|
||||
return currentId;
|
||||
}
|
||||
function getSequelEpisode(payload, mediaType, season, episode) {
|
||||
return getLocalEpisode(payload.mappings, mediaType, season, episode) || getLocalEpisode(payload.mappings, mediaType, 1, episode);
|
||||
}
|
||||
function resolveEpisodePayload(mediaId, mediaType, season, episode) {
|
||||
return __async(this, null, function* () {
|
||||
const identifier = parseIdentifier(mediaId);
|
||||
if (!identifier) {
|
||||
return null;
|
||||
}
|
||||
let mappingRecords;
|
||||
try {
|
||||
mappingRecords = yield fetchJson(
|
||||
`${ANIMAP_URL}/${identifier.service}/${encodeURIComponent(identifier.value)}`,
|
||||
{ headers: { "Accept": "application/json" } }
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(`[Miruro] No anime mapping for ${mediaId}: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
const aniListIds = getAniListIds(mappingRecords, identifier, mediaType);
|
||||
if (aniListIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (aniListIds.length === 1) {
|
||||
const anilistId = aniListIds[0];
|
||||
if (mediaType === "tv" && (asNumber(season) || 1) > 1) {
|
||||
try {
|
||||
const seasonAniListId = yield getSeasonAniListId(anilistId, season);
|
||||
if (seasonAniListId && seasonAniListId !== anilistId) {
|
||||
const payload2 = yield secureGet("episodes", { anilistId: seasonAniListId });
|
||||
const localEpisode2 = getSequelEpisode(payload2, mediaType, season, episode);
|
||||
if (localEpisode2) {
|
||||
return { anilistId: seasonAniListId, localEpisode: localEpisode2, payload: payload2 };
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[Miruro] Season ${season} mapping fallback failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
const payload = yield secureGet("episodes", { anilistId });
|
||||
const localEpisode = getLocalEpisode(payload.mappings, mediaType, season, episode);
|
||||
return localEpisode ? { anilistId, localEpisode, payload } : null;
|
||||
}
|
||||
for (const anilistId of aniListIds) {
|
||||
async function resolveEpisodePayload(mediaId, mediaType, season, episode) {
|
||||
const identifier = parseIdentifier(mediaId);
|
||||
if (!identifier) {
|
||||
return null;
|
||||
}
|
||||
let mappingRecords;
|
||||
try {
|
||||
mappingRecords = await fetchJson(
|
||||
`${ANIMAP_URL}/${identifier.service}/${encodeURIComponent(identifier.value)}`,
|
||||
{ headers: { "Accept": "application/json" } }
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(`[Miruro] No anime mapping for ${mediaId}: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
const aniListIds = getAniListIds(mappingRecords, identifier, mediaType);
|
||||
if (aniListIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (aniListIds.length === 1) {
|
||||
const anilistId = aniListIds[0];
|
||||
if (mediaType === "tv" && (asNumber(season) || 1) > 1) {
|
||||
try {
|
||||
const info = yield secureGet(`info/${anilistId}`);
|
||||
const localEpisode = getLocalEpisode(info.mappings, mediaType, season, episode);
|
||||
if (localEpisode) {
|
||||
const payload = yield secureGet("episodes", { anilistId });
|
||||
return { anilistId, localEpisode, payload };
|
||||
const seasonAniListId = await getSeasonAniListId(anilistId, season);
|
||||
if (seasonAniListId && seasonAniListId !== anilistId) {
|
||||
const payload2 = await secureGet("episodes", { anilistId: seasonAniListId });
|
||||
const localEpisode2 = getSequelEpisode(payload2, mediaType, season, episode);
|
||||
if (localEpisode2) {
|
||||
return { anilistId: seasonAniListId, localEpisode: localEpisode2, payload: payload2 };
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[Miruro] Skipping AniList ${anilistId}: ${error.message}`);
|
||||
console.warn(`[Miruro] Season ${season} mapping fallback failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const payload = await secureGet("episodes", { anilistId });
|
||||
const localEpisode = getLocalEpisode(payload.mappings, mediaType, season, episode);
|
||||
return localEpisode ? { anilistId, localEpisode, payload } : null;
|
||||
}
|
||||
for (const anilistId of aniListIds) {
|
||||
try {
|
||||
const info = await secureGet(`info/${anilistId}`);
|
||||
const localEpisode = getLocalEpisode(info.mappings, mediaType, season, episode);
|
||||
if (localEpisode) {
|
||||
const payload = await secureGet("episodes", { anilistId });
|
||||
return { anilistId, localEpisode, payload };
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[Miruro] Skipping AniList ${anilistId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function findEpisode(providerPayload, variant, episodeNumber) {
|
||||
const episodes = providerPayload && providerPayload.episodes ? providerPayload.episodes[variant] : null;
|
||||
@@ -787,132 +755,122 @@ function isHlsStream(stream) {
|
||||
const type = String(stream && stream.type || "").toLowerCase();
|
||||
return type === "hls" || type === "m3u8" || type === "application/x-mpegurl";
|
||||
}
|
||||
function resolveQuality(stream, headers) {
|
||||
return __async(this, null, function* () {
|
||||
const declaredQuality = getDeclaredQuality(stream);
|
||||
if (declaredQuality) {
|
||||
return declaredQuality;
|
||||
}
|
||||
if (isHlsStream(stream)) {
|
||||
let timeoutId;
|
||||
try {
|
||||
const manifest = yield Promise.race([
|
||||
fetchText(stream.url, { headers }),
|
||||
new Promise((resolve) => {
|
||||
timeoutId = setTimeout(() => resolve(null), HLS_QUALITY_TIMEOUT_MS);
|
||||
})
|
||||
]);
|
||||
const hlsQuality = typeof manifest === "string" ? getHlsQuality(manifest) : null;
|
||||
if (hlsQuality) {
|
||||
return hlsQuality;
|
||||
}
|
||||
} catch (_) {
|
||||
} finally {
|
||||
if (timeoutId != null) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
async function resolveQuality(stream, headers) {
|
||||
const declaredQuality = getDeclaredQuality(stream);
|
||||
if (declaredQuality) {
|
||||
return declaredQuality;
|
||||
}
|
||||
if (isHlsStream(stream)) {
|
||||
let timeoutId;
|
||||
try {
|
||||
const manifest = await Promise.race([
|
||||
fetchText(stream.url, { headers }),
|
||||
new Promise((resolve) => {
|
||||
timeoutId = setTimeout(() => resolve(null), HLS_QUALITY_TIMEOUT_MS);
|
||||
})
|
||||
]);
|
||||
const hlsQuality = typeof manifest === "string" ? getHlsQuality(manifest) : null;
|
||||
if (hlsQuality) {
|
||||
return hlsQuality;
|
||||
}
|
||||
} catch (_) {
|
||||
} finally {
|
||||
if (timeoutId != null) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
return "HD";
|
||||
});
|
||||
}
|
||||
return "HD";
|
||||
}
|
||||
function isPlayableStream(stream) {
|
||||
const type = String(stream && stream.type || "").toLowerCase();
|
||||
return !!(stream && stream.url) && (type === "hls" || type === "m3u8" || type === "mp4" || type === "application/x-mpegurl");
|
||||
}
|
||||
function normalizeStreams(sourcePayload, provider, variant) {
|
||||
return __async(this, null, function* () {
|
||||
const subtitleCount = Array.isArray(sourcePayload.subtitles) ? sourcePayload.subtitles.length : 0;
|
||||
const language = variant === "dub" ? "DUB" : "SUB";
|
||||
return Promise.all((sourcePayload.streams || []).filter(isPlayableStream).map((stream) => __async(this, null, function* () {
|
||||
const streamHeaders = stream.headers && typeof stream.headers === "object" ? stream.headers : {};
|
||||
const referer = stream.referer || streamHeaders.Referer || streamHeaders.referer || BASE_URL;
|
||||
const subtitleLabel = subtitleCount > 0 && variant !== "dub" ? " \xB7 CC" : "";
|
||||
const headers = __spreadProps(__spreadValues({}, streamHeaders), {
|
||||
"Referer": referer,
|
||||
"Origin": getOrigin(referer),
|
||||
"User-Agent": HEADERS["User-Agent"]
|
||||
});
|
||||
const quality = yield resolveQuality(stream, headers);
|
||||
const server = !stream.server || /^(?:HD|auto)$/i.test(stream.server) ? quality : stream.server;
|
||||
return {
|
||||
// Nuvio's source picker prominently displays `name`, so include the
|
||||
// Miruro server and audio variant here instead of only in `title`.
|
||||
name: `Miruro \xB7 ${provider.label} \xB7 ${language}`,
|
||||
title: `[${language}] ${provider.label} \xB7 ${server}${subtitleLabel}`,
|
||||
url: stream.url,
|
||||
quality,
|
||||
headers
|
||||
};
|
||||
})));
|
||||
});
|
||||
}
|
||||
function getProviderStreams(provider, providerPayload, anilistId, episodeNumber) {
|
||||
return __async(this, null, function* () {
|
||||
const requests = [];
|
||||
const subEpisode = findEpisode(providerPayload, "sub", episodeNumber);
|
||||
const dubEpisode = findEpisode(providerPayload, "dub", episodeNumber);
|
||||
if (subEpisode) {
|
||||
requests.push({ episode: subEpisode, variant: "sub", category: provider.subCategory });
|
||||
}
|
||||
if (dubEpisode) {
|
||||
requests.push({ episode: dubEpisode, variant: "dub", category: "dub" });
|
||||
}
|
||||
const results = yield Promise.all(requests.map((request) => __async(this, null, function* () {
|
||||
try {
|
||||
const sources = yield secureGet("sources", {
|
||||
episodeId: request.episode.id,
|
||||
provider: provider.id,
|
||||
category: request.category,
|
||||
anilistId
|
||||
});
|
||||
return normalizeStreams(sources, provider, request.variant);
|
||||
} catch (error) {
|
||||
console.warn(`[Miruro] ${provider.label} ${request.variant} failed: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
})));
|
||||
return results.flat();
|
||||
});
|
||||
}
|
||||
function extractStreams(tmdbId, mediaType, season, episode) {
|
||||
return __async(this, null, function* () {
|
||||
if (!tmdbId || mediaType !== "movie" && mediaType !== "tv") {
|
||||
return [];
|
||||
}
|
||||
const resolved = yield resolveEpisodePayload(tmdbId, mediaType, season, episode);
|
||||
if (!resolved || !resolved.payload || !resolved.payload.providers) {
|
||||
return [];
|
||||
}
|
||||
const providerResults = yield Promise.all(PROVIDERS.map((provider) => {
|
||||
const payload = resolved.payload.providers[provider.id];
|
||||
return payload ? getProviderStreams(provider, payload, resolved.anilistId, resolved.localEpisode) : [];
|
||||
}));
|
||||
const streams = providerResults.flat();
|
||||
const seen = /* @__PURE__ */ new Set();
|
||||
return streams.filter((stream) => {
|
||||
if (seen.has(stream.url)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(stream.url);
|
||||
return true;
|
||||
async function normalizeStreams(sourcePayload, provider, variant) {
|
||||
const subtitleCount = Array.isArray(sourcePayload.subtitles) ? sourcePayload.subtitles.length : 0;
|
||||
const language = variant === "dub" ? "DUB" : "SUB";
|
||||
return Promise.all((sourcePayload.streams || []).filter(isPlayableStream).map(async (stream) => {
|
||||
const streamHeaders = stream.headers && typeof stream.headers === "object" ? stream.headers : {};
|
||||
const referer = stream.referer || streamHeaders.Referer || streamHeaders.referer || BASE_URL;
|
||||
const subtitleLabel = subtitleCount > 0 && variant !== "dub" ? " \xB7 CC" : "";
|
||||
const headers = __spreadProps(__spreadValues({}, streamHeaders), {
|
||||
"Referer": referer,
|
||||
"Origin": getOrigin(referer),
|
||||
"User-Agent": HEADERS["User-Agent"]
|
||||
});
|
||||
const quality = await resolveQuality(stream, headers);
|
||||
const server = !stream.server || /^(?:HD|auto)$/i.test(stream.server) ? quality : stream.server;
|
||||
return {
|
||||
// Nuvio's source picker prominently displays `name`, so include the
|
||||
// Miruro server and audio variant here instead of only in `title`.
|
||||
name: `Miruro \xB7 ${provider.label} \xB7 ${language}`,
|
||||
title: `[${language}] ${provider.label} \xB7 ${server}${subtitleLabel}`,
|
||||
url: stream.url,
|
||||
quality,
|
||||
headers
|
||||
};
|
||||
}));
|
||||
}
|
||||
async function getProviderStreams(provider, providerPayload, anilistId, episodeNumber) {
|
||||
const requests = [];
|
||||
const subEpisode = findEpisode(providerPayload, "sub", episodeNumber);
|
||||
const dubEpisode = findEpisode(providerPayload, "dub", episodeNumber);
|
||||
if (subEpisode) {
|
||||
requests.push({ episode: subEpisode, variant: "sub", category: provider.subCategory });
|
||||
}
|
||||
if (dubEpisode) {
|
||||
requests.push({ episode: dubEpisode, variant: "dub", category: "dub" });
|
||||
}
|
||||
const results = await Promise.all(requests.map(async (request) => {
|
||||
try {
|
||||
const sources = await secureGet("sources", {
|
||||
episodeId: request.episode.id,
|
||||
provider: provider.id,
|
||||
category: request.category,
|
||||
anilistId
|
||||
});
|
||||
return normalizeStreams(sources, provider, request.variant);
|
||||
} catch (error) {
|
||||
console.warn(`[Miruro] ${provider.label} ${request.variant} failed: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}));
|
||||
return results.flat();
|
||||
}
|
||||
async function extractStreams(tmdbId, mediaType, season, episode) {
|
||||
if (!tmdbId || mediaType !== "movie" && mediaType !== "tv") {
|
||||
return [];
|
||||
}
|
||||
const resolved = await resolveEpisodePayload(tmdbId, mediaType, season, episode);
|
||||
if (!resolved || !resolved.payload || !resolved.payload.providers) {
|
||||
return [];
|
||||
}
|
||||
const providerResults = await Promise.all(PROVIDERS.map((provider) => {
|
||||
const payload = resolved.payload.providers[provider.id];
|
||||
return payload ? getProviderStreams(provider, payload, resolved.anilistId, resolved.localEpisode) : [];
|
||||
}));
|
||||
const streams = providerResults.flat();
|
||||
const seen = /* @__PURE__ */ new Set();
|
||||
return streams.filter((stream) => {
|
||||
if (seen.has(stream.url)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(stream.url);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// src/miruro/index.js
|
||||
function getStreams(tmdbId, mediaType, season, episode) {
|
||||
return __async(this, null, function* () {
|
||||
try {
|
||||
const episodeLabel = mediaType === "tv" ? ` S${season}E${episode}` : "";
|
||||
console.log(`[Miruro] Request: ${mediaType} ${tmdbId}${episodeLabel}`);
|
||||
const streams = yield extractStreams(tmdbId, mediaType, season, episode);
|
||||
console.log(`[Miruro] Found ${streams.length} stream(s)`);
|
||||
return streams;
|
||||
} catch (error) {
|
||||
console.error(`[Miruro] Error: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
async function getStreams(tmdbId, mediaType, season, episode) {
|
||||
try {
|
||||
const episodeLabel = mediaType === "tv" ? ` S${season}E${episode}` : "";
|
||||
console.log(`[Miruro] Request: ${mediaType} ${tmdbId}${episodeLabel}`);
|
||||
const streams = await extractStreams(tmdbId, mediaType, season, episode);
|
||||
console.log(`[Miruro] Found ${streams.length} stream(s)`);
|
||||
return streams;
|
||||
} catch (error) {
|
||||
console.error(`[Miruro] Error: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
module.exports = { getStreams };
|
||||
Reference in new issue
Block a user