Files
john-nuvio/providers/miruro.js
T

877 lines
26 KiB
JavaScript

/**
* miruro - Built from src/miruro/
* Generated: 2026-07-20T14:16:44.855Z
*/
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
// node_modules/fflate/esm/browser.js
var u8 = Uint8Array;
var u16 = Uint16Array;
var i32 = Int32Array;
var fleb = new u8([
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
1,
1,
2,
2,
2,
2,
3,
3,
3,
3,
4,
4,
4,
4,
5,
5,
5,
5,
0,
/* unused */
0,
0,
/* impossible */
0
]);
var fdeb = new u8([
0,
0,
0,
0,
1,
1,
2,
2,
3,
3,
4,
4,
5,
5,
6,
6,
7,
7,
8,
8,
9,
9,
10,
10,
11,
11,
12,
12,
13,
13,
/* unused */
0,
0
]);
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
var freb = function(eb, start) {
var b = new u16(31);
for (var i = 0; i < 31; ++i) {
b[i] = start += 1 << eb[i - 1];
}
var r = new i32(b[30]);
for (var i = 1; i < 30; ++i) {
for (var j = b[i]; j < b[i + 1]; ++j) {
r[j] = j - b[i] << 5 | i;
}
}
return { b, r };
};
var _a = freb(fleb, 2);
var fl = _a.b;
var revfl = _a.r;
fl[28] = 258, revfl[258] = 28;
var _b = freb(fdeb, 0);
var fd = _b.b;
var revfd = _b.r;
var rev = new u16(32768);
for (i = 0; i < 32768; ++i) {
x = (i & 43690) >> 1 | (i & 21845) << 1;
x = (x & 52428) >> 2 | (x & 13107) << 2;
x = (x & 61680) >> 4 | (x & 3855) << 4;
rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
}
var x;
var i;
var hMap = function(cd, mb, r) {
var s = cd.length;
var i = 0;
var l = new u16(mb);
for (; i < s; ++i) {
if (cd[i])
++l[cd[i] - 1];
}
var le = new u16(mb);
for (i = 1; i < mb; ++i) {
le[i] = le[i - 1] + l[i - 1] << 1;
}
var co;
if (r) {
co = new u16(1 << mb);
var rvb = 15 - mb;
for (i = 0; i < s; ++i) {
if (cd[i]) {
var sv = i << 4 | cd[i];
var r_1 = mb - cd[i];
var v = le[cd[i] - 1]++ << r_1;
for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
co[rev[v] >> rvb] = sv;
}
}
}
} else {
co = new u16(s);
for (i = 0; i < s; ++i) {
if (cd[i]) {
co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i];
}
}
}
return co;
};
var flt = new u8(288);
for (i = 0; i < 144; ++i)
flt[i] = 8;
var i;
for (i = 144; i < 256; ++i)
flt[i] = 9;
var i;
for (i = 256; i < 280; ++i)
flt[i] = 7;
var i;
for (i = 280; i < 288; ++i)
flt[i] = 8;
var i;
var fdt = new u8(32);
for (i = 0; i < 32; ++i)
fdt[i] = 5;
var i;
var flrm = /* @__PURE__ */ hMap(flt, 9, 1);
var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
var max = function(a) {
var m = a[0];
for (var i = 1; i < a.length; ++i) {
if (a[i] > m)
m = a[i];
}
return m;
};
var bits = function(d, p, m) {
var o = p / 8 | 0;
return (d[o] | d[o + 1] << 8) >> (p & 7) & m;
};
var bits16 = function(d, p) {
var o = p / 8 | 0;
return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
};
var shft = function(p) {
return (p + 7) / 8 | 0;
};
var slc = function(v, s, e) {
if (s == null || s < 0)
s = 0;
if (e == null || e > v.length)
e = v.length;
return new u8(v.subarray(s, e));
};
var ec = [
"unexpected EOF",
"invalid block type",
"invalid length/literal",
"invalid distance",
"stream finished",
"no stream handler",
,
// determined by compression function
"no callback",
"invalid UTF-8 data",
"extra field too long",
"date not in range 1980-2099",
"filename too long",
"stream finishing",
"invalid zip data"
// determined by unknown compression method
];
var err = function(ind, msg, nt) {
var e = new Error(msg || ec[ind]);
e.code = ind;
if (Error.captureStackTrace)
Error.captureStackTrace(e, err);
if (!nt)
throw e;
return e;
};
var inflt = function(dat, st, buf, dict) {
var sl = dat.length, dl = dict ? dict.length : 0;
if (!sl || st.f && !st.l)
return buf || new u8(0);
var noBuf = !buf;
var resize = noBuf || st.i != 2;
var noSt = st.i;
if (noBuf)
buf = new u8(sl * 3);
var cbuf = function(l2) {
var bl = buf.length;
if (l2 > bl) {
var nbuf = new u8(Math.max(bl * 2, l2));
nbuf.set(buf);
buf = nbuf;
}
};
var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
var tbts = sl * 8;
do {
if (!lm) {
final = bits(dat, pos, 1);
var type = bits(dat, pos + 1, 3);
pos += 3;
if (!type) {
var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
if (t > sl) {
if (noSt)
err(0);
break;
}
if (resize)
cbuf(bt + l);
buf.set(dat.subarray(s, t), bt);
st.b = bt += l, st.p = pos = t * 8, st.f = final;
continue;
} else if (type == 1)
lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
else if (type == 2) {
var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
var tl = hLit + bits(dat, pos + 5, 31) + 1;
pos += 14;
var ldt = new u8(tl);
var clt = new u8(19);
for (var i = 0; i < hcLen; ++i) {
clt[clim[i]] = bits(dat, pos + i * 3, 7);
}
pos += hcLen * 3;
var clb = max(clt), clbmsk = (1 << clb) - 1;
var clm = hMap(clt, clb, 1);
for (var i = 0; i < tl; ) {
var r = clm[bits(dat, pos, clbmsk)];
pos += r & 15;
var s = r >> 4;
if (s < 16) {
ldt[i++] = s;
} else {
var c = 0, n = 0;
if (s == 16)
n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];
else if (s == 17)
n = 3 + bits(dat, pos, 7), pos += 3;
else if (s == 18)
n = 11 + bits(dat, pos, 127), pos += 7;
while (n--)
ldt[i++] = c;
}
}
var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
lbt = max(lt);
dbt = max(dt);
lm = hMap(lt, lbt, 1);
dm = hMap(dt, dbt, 1);
} else
err(1);
if (pos > tbts) {
if (noSt)
err(0);
break;
}
}
if (resize)
cbuf(bt + 131072);
var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
var lpos = pos;
for (; ; lpos = pos) {
var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
pos += c & 15;
if (pos > tbts) {
if (noSt)
err(0);
break;
}
if (!c)
err(2);
if (sym < 256)
buf[bt++] = sym;
else if (sym == 256) {
lpos = pos, lm = null;
break;
} else {
var add = sym - 254;
if (sym > 264) {
var i = sym - 257, b = fleb[i];
add = bits(dat, pos, (1 << b) - 1) + fl[i];
pos += b;
}
var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
if (!d)
err(3);
pos += d & 15;
var dt = fd[dsym];
if (dsym > 3) {
var b = fdeb[dsym];
dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
}
if (pos > tbts) {
if (noSt)
err(0);
break;
}
if (resize)
cbuf(bt + 131072);
var end = bt + add;
if (bt < dt) {
var shift = dl - dt, dend = Math.min(dt, end);
if (shift + bt < 0)
err(3);
for (; bt < dend; ++bt)
buf[bt] = dict[shift + bt];
}
for (; bt < end; ++bt)
buf[bt] = buf[bt - dt];
}
}
st.l = lm, st.p = lpos, st.b = bt, st.f = final;
if (lm)
final = 1, st.m = lbt, st.d = dm, st.n = dbt;
} while (!final);
return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
};
var et = /* @__PURE__ */ new u8(0);
var gzs = function(d) {
if (d[0] != 31 || d[1] != 139 || d[2] != 8)
err(6, "invalid gzip data");
var flg = d[3];
var st = 10;
if (flg & 4)
st += (d[10] | d[11] << 8) + 2;
for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++])
;
return st + (flg & 2);
};
var gzl = function(d) {
var l = d.length;
return (d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16 | d[l - 1] << 24) >>> 0;
};
function gunzipSync(data, opts) {
var st = gzs(data);
if (st + 8 > data.length)
err(6, "invalid gzip data");
return inflt(data.subarray(st, -8), { i: 2 }, opts && opts.out || new u8(gzl(data)), opts && opts.dictionary);
}
var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
var tds = 0;
try {
td.decode(et, { stream: true });
tds = 1;
} catch (e) {
}
var dutf8 = function(d) {
for (var r = "", i = 0; ; ) {
var c = d[i++];
var eb = (c > 127) + (c > 223) + (c > 239);
if (i + eb > d.length)
return { s: r, r: slc(d, i - 1) };
if (!eb)
r += String.fromCharCode(c);
else if (eb == 3) {
c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | d[i++] & 63) - 65536, r += String.fromCharCode(55296 | c >> 10, 56320 | c & 1023);
} else if (eb & 1)
r += String.fromCharCode((c & 31) << 6 | d[i++] & 63);
else
r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | d[i++] & 63);
}
};
function strFromU8(dat, latin1) {
if (latin1) {
var r = "";
for (var i = 0; i < dat.length; i += 16384)
r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));
return r;
} else if (td) {
return td.decode(dat);
} else {
var _a2 = dutf8(dat), s = _a2.s, r = _a2.r;
if (r.length)
err(8);
return s;
}
}
// src/miruro/http.js
var BASE_URL = "https://www.miruro.to";
var HEADERS = {
"Accept": "*/*",
"Referer": `${BASE_URL}/`,
"User-Agent": "Mozilla/5.0 (Linux; Android 15) AppleWebKit/537.36 Chrome/140.0.0.0 Mobile Safari/537.36",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin"
};
var PIPE_OBFUSCATION_KEY = hexToBytes("71951034f8fbcf53d89db52ceb3dc22c");
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
function hexToBytes(value) {
const bytes = new Uint8Array(value.length / 2);
for (let index = 0; index < bytes.length; index++) {
bytes[index] = parseInt(value.slice(index * 2, index * 2 + 2), 16);
}
return bytes;
}
function encodeBase64Url(value) {
let result = "";
for (let index = 0; index < value.length; index += 3) {
const first = value.charCodeAt(index);
const hasSecond = index + 1 < value.length;
const hasThird = index + 2 < value.length;
const second = hasSecond ? value.charCodeAt(index + 1) : 0;
const third = hasThird ? value.charCodeAt(index + 2) : 0;
const chunk = first << 16 | second << 8 | third;
result += BASE64_ALPHABET[chunk >> 18 & 63];
result += BASE64_ALPHABET[chunk >> 12 & 63];
result += hasSecond ? BASE64_ALPHABET[chunk >> 6 & 63] : "=";
result += hasThird ? BASE64_ALPHABET[chunk & 63] : "=";
}
return result.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}
function decodeBase64Url(value) {
const normalized = value.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "");
const outputLength = Math.floor(normalized.length * 6 / 8);
const output = new Uint8Array(outputLength);
let buffer = 0;
let bits2 = 0;
let outputIndex = 0;
for (let index = 0; index < normalized.length; index++) {
const code = BASE64_ALPHABET.indexOf(normalized[index]);
if (code < 0) {
continue;
}
buffer = buffer << 6 | code;
bits2 += 6;
if (bits2 >= 8) {
bits2 -= 8;
output[outputIndex++] = buffer >> bits2 & 255;
}
}
return outputIndex === output.length ? output : output.slice(0, outputIndex);
}
function decodeSecurePayload(raw, obfuscationVersion) {
if (!obfuscationVersion) {
return JSON.parse(raw);
}
const bytes = decodeBase64Url(raw);
if (obfuscationVersion === "2") {
for (let index = 0; index < bytes.length; index++) {
bytes[index] ^= PIPE_OBFUSCATION_KEY[index % PIPE_OBFUSCATION_KEY.length];
}
}
return JSON.parse(strFromU8(gunzipSync(bytes)));
}
function mergeHeaders(headers) {
return __spreadValues(__spreadValues({}, HEADERS), headers || {});
}
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();
}
async function fetchJson(url, options = {}) {
const raw = await fetchText(url, options);
return JSON.parse(raw);
}
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 = 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
var ANIMAP_URL = "https://animap.id/api/map";
var ANILIST_URL = "https://graphql.anilist.co";
var HLS_QUALITY_TIMEOUT_MS = 4e3;
var PROVIDERS = [
{ id: "bonk", label: "Bonk", subCategory: "ssub" },
{ id: "kiwi", label: "Kiwi", subCategory: "sub" },
{ id: "hop", label: "Hop", subCategory: "ssub" },
{ id: "ally", label: "Ally", subCategory: "sub" },
{ id: "pewe", label: "Pewe", subCategory: "sub" },
{ id: "bee", label: "Bee", subCategory: "ssub" },
{ id: "moo", label: "Moo", subCategory: "sub" }
];
function asNumber(value) {
const number = Number(value);
return Number.isFinite(number) ? number : null;
}
function parseIdentifier(value) {
const id = String(value || "").trim();
if (/^tt\d+$/i.test(id)) {
return { service: "imdb", value: id.toLowerCase() };
}
if (/^\d+$/.test(id)) {
return { service: "tmdb", value: id };
}
return null;
}
function recordMatchesIdentifier(record, identifier, mediaType) {
const expectedType = mediaType === "movie" ? "movie" : "tv";
if (identifier.service === "imdb") {
const imdbMatches = (record.imdb_id || []).some((id) => String(id).toLowerCase() === identifier.value);
const typeMatches = (record.tmdb_id || []).length === 0 || (record.tmdb_id || []).some((mapping) => mapping.type === expectedType);
return imdbMatches && typeMatches;
}
return (record.tmdb_id || []).some((mapping) => String(mapping.id) === identifier.value && mapping.type === expectedType);
}
function getAniListIds(records, identifier, mediaType) {
const ids = [];
for (const record of Array.isArray(records) ? records : []) {
if (!recordMatchesIdentifier(record, identifier, mediaType)) {
continue;
}
for (const id of record.anilist_id || []) {
const numericId = asNumber(id);
if (numericId && !ids.includes(numericId)) {
ids.push(numericId);
}
}
}
return ids;
}
function getLocalEpisode(mappings, mediaType, season, episode) {
if (!mappings) {
return null;
}
if (mediaType === "movie") {
return 1;
}
const requestedSeason = asNumber(season) || 1;
const requestedEpisode = asNumber(episode);
if (!requestedEpisode || requestedEpisode < 1) {
return null;
}
const mappedSeason = mappings.tmdbSeason || mappings.defaultTvdbSeason;
if (mappedSeason === "absolute") {
return requestedSeason === 1 ? requestedEpisode : null;
}
if (mappedSeason != null && String(mappedSeason) !== String(requestedSeason)) {
return null;
}
const offset = asNumber(mappings.tmdbOffset) || asNumber(mappings.episodeOffset) || 0;
const episodeCount = asNumber(mappings.episodes);
if (requestedEpisode <= offset || episodeCount && requestedEpisode > offset + episodeCount) {
return null;
}
return requestedEpisode - offset;
}
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 {
edges {
relationType
node {
id
type
format
}
}
}
}
}
`,
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;
}
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;
}
currentId = sequelId;
}
return currentId;
}
function getSequelEpisode(payload, mediaType, season, episode) {
return getLocalEpisode(payload.mappings, mediaType, season, episode) || getLocalEpisode(payload.mappings, mediaType, 1, episode);
}
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 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] Season ${season} mapping fallback failed: ${error.message}`);
}
}
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;
if (!Array.isArray(episodes)) {
return null;
}
return episodes.find((item) => Number(item.number) === Number(episodeNumber)) || null;
}
function getOrigin(url) {
const match = typeof url === "string" ? url.match(/^(https?:\/\/[^/]+)/i) : null;
return match ? match[1] : BASE_URL;
}
function getDeclaredQuality(stream) {
const value = `${stream.quality || ""} ${stream.resolution || ""} ${stream.server || ""}`;
const resolution = value.match(/(2160|1440|1080|720|480|360)p?/i);
if (resolution) {
return `${resolution[1]}p`;
}
return /4k/i.test(value) ? "4K" : null;
}
function getHlsQuality(manifest) {
const heights = [];
const pattern = /\bRESOLUTION\s*=\s*\d+\s*x\s*(\d+)\b/gi;
let match;
while ((match = pattern.exec(manifest)) !== null) {
const height = Number(match[1]);
if (height > 0) {
heights.push(height);
}
}
return heights.length > 0 ? `${Math.max(...heights)}p` : null;
}
function isHlsStream(stream) {
const type = String(stream && stream.type || "").toLowerCase();
return type === "hls" || type === "m3u8" || type === "application/x-mpegurl";
}
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";
}
function isPlayableStream(stream) {
const type = String(stream && stream.type || "").toLowerCase();
return !!(stream && stream.url) && (type === "hls" || type === "m3u8" || type === "mp4" || type === "application/x-mpegurl");
}
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
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 };