feat(miruro): attempt to retrieve quality from stream playlist
This commit is contained in:
1 parent
6e86adb0e6
commit
27a87191c3
7 files changed
+920
-34
No files matched your search
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "Nuvio Providers",
|
"name": "Nuvio Providers",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"scrapers": [
|
"scrapers": [
|
||||||
{
|
{
|
||||||
"id": "miruro",
|
"id": "miruro",
|
||||||
"name": "Miruro",
|
"name": "Miruro",
|
||||||
"description": "Anime streams from Miruro with subbed and dubbed sources.",
|
"description": "Anime streams from Miruro with subbed and dubbed sources.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"author": "Jānis L",
|
"author": "Jānis L",
|
||||||
"supportedTypes": [
|
"supportedTypes": [
|
||||||
"movie",
|
"movie",
|
||||||
|
|||||||
Generated
+9
@@ -8,6 +8,9 @@
|
|||||||
"name": "nuvio-providers",
|
"name": "nuvio-providers",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"fflate": "^0.8.3"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cheerio": "^1.1.2",
|
"cheerio": "^1.1.2",
|
||||||
"cheerio-without-node-native": "^0.20.2",
|
"cheerio-without-node-native": "^0.20.2",
|
||||||
@@ -1124,6 +1127,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/fflate": {
|
||||||
|
"version": "0.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
|
||||||
|
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
|
|||||||
+4
-1
@@ -19,5 +19,8 @@
|
|||||||
"streaming",
|
"streaming",
|
||||||
"providers"
|
"providers"
|
||||||
],
|
],
|
||||||
"license": "GPL-3.0"
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"fflate": "^0.8.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* _template - Built from src/_template/
|
* _template - Built from src/_template/
|
||||||
* Generated: 2026-07-19T17:05:24.210Z
|
* Generated: 2026-07-19T17:21:01.885Z
|
||||||
*/
|
*/
|
||||||
var __create = Object.create;
|
var __create = Object.create;
|
||||||
var __defProp = Object.defineProperty;
|
var __defProp = Object.defineProperty;
|
||||||
|
|||||||
+760
-2
@@ -1,5 +1,763 @@
|
|||||||
/**
|
/**
|
||||||
* flixer - Built from src/flixer/
|
* flixer - Built from src/flixer/
|
||||||
* Generated: 2026-07-19T17:05:50.362Z
|
* Generated: 2026-07-19T17:21:01.894Z
|
||||||
*/
|
*/
|
||||||
var se=Object.defineProperty,ie=Object.defineProperties;var ce=Object.getOwnPropertyDescriptors;var G=Object.getOwnPropertySymbols;var ae=Object.prototype.hasOwnProperty,le=Object.prototype.propertyIsEnumerable;var K=(e,r,t)=>r in e?se(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,B=(e,r)=>{for(var t in r||(r={}))ae.call(r,t)&&K(e,t,r[t]);if(G)for(var t of G(r))le.call(r,t)&&K(e,t,r[t]);return e},S=(e,r)=>ie(e,ce(r));var m=(e,r,t)=>new Promise((n,o)=>{var c=a=>{try{i(t.next(a))}catch(h){o(h)}},s=a=>{try{i(t.throw(a))}catch(h){o(h)}},i=a=>a.done?n(a.value):Promise.resolve(a.value).then(c,s);i((t=t.apply(e,r)).next())});var $="https://flixer.gd",k="https://plsdontscrapemelove.flixer.gd",R={Accept:"text/plain",Origin:$,Referer:`${$}/`,"User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fe=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function v(e){let r=[];for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n>=55296&&n<=56319&&t+1<e.length){let o=e.charCodeAt(t+1);o>=56320&&o<=57343&&(n=65536+(n-55296<<10)+o-56320,t++)}n<128?r.push(n):n<2048?r.push(192|n>>6,128|n&63):n<65536?r.push(224|n>>12,128|n>>6&63,128|n&63):r.push(240|n>>18,128|n>>12&63,128|n>>6&63,128|n&63)}return new Uint8Array(r)}function p(e,r){return e>>>r|e<<32-r}function E(e){let r=e.length*8,t=Math.ceil((e.length+9)/64)*64,n=new Uint8Array(t);n.set(e),n[e.length]=128;let o=Math.floor(r/4294967296),c=r>>>0,s=t-8;n[s]=o>>>24&255,n[s+1]=o>>>16&255,n[s+2]=o>>>8&255,n[s+3]=o&255,n[s+4]=c>>>24&255,n[s+5]=c>>>16&255,n[s+6]=c>>>8&255,n[s+7]=c&255;let i=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],a=new Uint32Array(64);for(let l=0;l<n.length;l+=64){for(let x=0;x<16;x++){let y=l+x*4;a[x]=(n[y]<<24|n[y+1]<<16|n[y+2]<<8|n[y+3])>>>0}for(let x=16;x<64;x++){let y=p(a[x-15],7)^p(a[x-15],18)^a[x-15]>>>3,L=p(a[x-2],17)^p(a[x-2],19)^a[x-2]>>>10;a[x]=a[x-16]+y+a[x-7]+L>>>0}let[A,w,C,d,f,u,g,U]=i;for(let x=0;x<64;x++){let y=p(f,6)^p(f,11)^p(f,25),L=f&u^~f&g,j=U+y+L+fe[x]+a[x]>>>0,ne=p(A,2)^p(A,13)^p(A,22),re=A&w^A&C^w&C,oe=ne+re>>>0;U=g,g=u,u=f,f=d+j>>>0,d=C,C=w,w=A,A=j+oe>>>0}i[0]=i[0]+A>>>0,i[1]=i[1]+w>>>0,i[2]=i[2]+C>>>0,i[3]=i[3]+d>>>0,i[4]=i[4]+f>>>0,i[5]=i[5]+u>>>0,i[6]=i[6]+g>>>0,i[7]=i[7]+U>>>0}let h=new Uint8Array(32);for(let l=0;l<i.length;l++)h[l*4]=i[l]>>>24&255,h[l*4+1]=i[l]>>>16&255,h[l*4+2]=i[l]>>>8&255,h[l*4+3]=i[l]&255;return h}function P(e,r){let t=e instanceof Uint8Array?e:v(String(e));t.length>64&&(t=E(t));let n=r instanceof Uint8Array?r:v(String(r)),o=new Uint8Array(64+n.length),c=new Uint8Array(96);for(let s=0;s<64;s++){let i=s<t.length?t[s]:0;o[s]=i^54,c[s]=i^92}return o.set(n,64),c.set(E(o),64),E(c)}function q(e){let r="";for(let t=0;t<e.length;t+=3){let n=t+1<e.length,o=t+2<e.length,c=e[t]<<16|(n?e[t+1]:0)<<8|(o?e[t+2]:0);r+=b[c>>>18&63],r+=b[c>>>12&63],r+=n?b[c>>>6&63]:"=",r+=o?b[c&63]:"="}return r}function ue(){let e=new Uint8Array(16);for(let r=0;r<e.length;r++)e[r]=Math.floor(Math.random()*256);return q(e).replace(/[/+=]/g,"").slice(0,22)}var N=0,I=0;function xe(){return m(this,null,function*(){let e=Date.now();if(I&&e-I<3e5)return Math.floor((e+N)/1e3);try{let r=Date.now(),t=yield fetch(`${k}/api/time?t=${r}`,{headers:S(B({},R),{"Cache-Control":"no-cache"})});if(!t.ok)throw new Error(`HTTP ${t.status}`);let n=yield t.json();if(!n||typeof n.timestamp!="number")throw new Error("invalid response");let o=Date.now();return N=n.timestamp*1e3+(o-r)/2-o,I=o,Math.floor((o+N)/1e3)}catch(r){return Math.floor(Date.now()/1e3)}})}function H(e,r){return m(this,null,function*(){let t=yield xe(),n=ue(),o=`${e}:${t}:${n}:${r}`;return{"X-Api-Key":e,"X-Request-Timestamp":String(t),"X-Request-Nonce":n,"X-Request-Signature":q(P(e,o)),"X-Client-Fingerprint":"zerlgt","X-Fingerprint-Lite":"e9136c41504646444"}})}function _(t){return m(this,arguments,function*(e,r={}){let n=yield fetch(e,S(B({},r),{headers:B(B({},R),r.headers||{})}));if(!n.ok)throw new Error(`HTTP ${n.status} for ${e}`);return yield n.text()})}var he="iVBORw0KGgoAAAANSUhEUgAAAMgAAAAyCAYAAAAZUZThAAAQAE";function Ae(...e){let r=0;for(let o of e)r+=o.length;let t=new Uint8Array(r),n=0;for(let o of e)t.set(o,n),n+=o.length;return t}function z(e){let r="";for(let t of e)r+=t.Line truncated
|
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));
|
||||||
|
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";
|
||||||
|
var API_URL = "https://plsdontscrapemelove.flixer.gd";
|
||||||
|
var HEADERS = {
|
||||||
|
"Accept": "text/plain",
|
||||||
|
"Origin": SITE_URL,
|
||||||
|
"Referer": `${SITE_URL}/`,
|
||||||
|
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
|
||||||
|
};
|
||||||
|
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
var SHA256_CONSTANTS = [
|
||||||
|
1116352408,
|
||||||
|
1899447441,
|
||||||
|
3049323471,
|
||||||
|
3921009573,
|
||||||
|
961987163,
|
||||||
|
1508970993,
|
||||||
|
2453635748,
|
||||||
|
2870763221,
|
||||||
|
3624381080,
|
||||||
|
310598401,
|
||||||
|
607225278,
|
||||||
|
1426881987,
|
||||||
|
1925078388,
|
||||||
|
2162078206,
|
||||||
|
2614888103,
|
||||||
|
3248222580,
|
||||||
|
3835390401,
|
||||||
|
4022224774,
|
||||||
|
264347078,
|
||||||
|
604807628,
|
||||||
|
770255983,
|
||||||
|
1249150122,
|
||||||
|
1555081692,
|
||||||
|
1996064986,
|
||||||
|
2554220882,
|
||||||
|
2821834349,
|
||||||
|
2952996808,
|
||||||
|
3210313671,
|
||||||
|
3336571891,
|
||||||
|
3584528711,
|
||||||
|
113926993,
|
||||||
|
338241895,
|
||||||
|
666307205,
|
||||||
|
773529912,
|
||||||
|
1294757372,
|
||||||
|
1396182291,
|
||||||
|
1695183700,
|
||||||
|
1986661051,
|
||||||
|
2177026350,
|
||||||
|
2456956037,
|
||||||
|
2730485921,
|
||||||
|
2820302411,
|
||||||
|
3259730800,
|
||||||
|
3345764771,
|
||||||
|
3516065817,
|
||||||
|
3600352804,
|
||||||
|
4094571909,
|
||||||
|
275423344,
|
||||||
|
430227734,
|
||||||
|
506948616,
|
||||||
|
659060556,
|
||||||
|
883997877,
|
||||||
|
958139571,
|
||||||
|
1322822218,
|
||||||
|
1537002063,
|
||||||
|
1747873779,
|
||||||
|
1955562222,
|
||||||
|
2024104815,
|
||||||
|
2227730452,
|
||||||
|
2361852424,
|
||||||
|
2428436474,
|
||||||
|
2756734187,
|
||||||
|
3204031479,
|
||||||
|
3329325298
|
||||||
|
];
|
||||||
|
function encodeUtf8(value) {
|
||||||
|
const bytes = [];
|
||||||
|
for (let index = 0; index < value.length; index++) {
|
||||||
|
let codePoint = value.charCodeAt(index);
|
||||||
|
if (codePoint >= 55296 && codePoint <= 56319 && index + 1 < value.length) {
|
||||||
|
const trailing = value.charCodeAt(index + 1);
|
||||||
|
if (trailing >= 56320 && trailing <= 57343) {
|
||||||
|
codePoint = 65536 + (codePoint - 55296 << 10) + trailing - 56320;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (codePoint < 128)
|
||||||
|
bytes.push(codePoint);
|
||||||
|
else if (codePoint < 2048)
|
||||||
|
bytes.push(192 | codePoint >> 6, 128 | codePoint & 63);
|
||||||
|
else if (codePoint < 65536)
|
||||||
|
bytes.push(224 | codePoint >> 12, 128 | codePoint >> 6 & 63, 128 | codePoint & 63);
|
||||||
|
else
|
||||||
|
bytes.push(240 | codePoint >> 18, 128 | codePoint >> 12 & 63, 128 | codePoint >> 6 & 63, 128 | codePoint & 63);
|
||||||
|
}
|
||||||
|
return new Uint8Array(bytes);
|
||||||
|
}
|
||||||
|
function rotateRight(value, amount) {
|
||||||
|
return value >>> amount | value << 32 - amount;
|
||||||
|
}
|
||||||
|
function sha256(input) {
|
||||||
|
const bitLength = input.length * 8;
|
||||||
|
const paddedLength = Math.ceil((input.length + 9) / 64) * 64;
|
||||||
|
const bytes = new Uint8Array(paddedLength);
|
||||||
|
bytes.set(input);
|
||||||
|
bytes[input.length] = 128;
|
||||||
|
const high = Math.floor(bitLength / 4294967296);
|
||||||
|
const low = bitLength >>> 0;
|
||||||
|
const end = paddedLength - 8;
|
||||||
|
bytes[end] = high >>> 24 & 255;
|
||||||
|
bytes[end + 1] = high >>> 16 & 255;
|
||||||
|
bytes[end + 2] = high >>> 8 & 255;
|
||||||
|
bytes[end + 3] = high & 255;
|
||||||
|
bytes[end + 4] = low >>> 24 & 255;
|
||||||
|
bytes[end + 5] = low >>> 16 & 255;
|
||||||
|
bytes[end + 6] = low >>> 8 & 255;
|
||||||
|
bytes[end + 7] = low & 255;
|
||||||
|
const hash = [1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225];
|
||||||
|
const words = new Uint32Array(64);
|
||||||
|
for (let offset = 0; offset < bytes.length; offset += 64) {
|
||||||
|
for (let index = 0; index < 16; index++) {
|
||||||
|
const position = offset + index * 4;
|
||||||
|
words[index] = (bytes[position] << 24 | bytes[position + 1] << 16 | bytes[position + 2] << 8 | bytes[position + 3]) >>> 0;
|
||||||
|
}
|
||||||
|
for (let index = 16; index < 64; index++) {
|
||||||
|
const first = rotateRight(words[index - 15], 7) ^ rotateRight(words[index - 15], 18) ^ words[index - 15] >>> 3;
|
||||||
|
const second = rotateRight(words[index - 2], 17) ^ rotateRight(words[index - 2], 19) ^ words[index - 2] >>> 10;
|
||||||
|
words[index] = words[index - 16] + first + words[index - 7] + second >>> 0;
|
||||||
|
}
|
||||||
|
let [a, b, c, d, e, f, g, h] = hash;
|
||||||
|
for (let index = 0; index < 64; index++) {
|
||||||
|
const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
||||||
|
const choice = e & f ^ ~e & g;
|
||||||
|
const temporary1 = h + sum1 + choice + SHA256_CONSTANTS[index] + words[index] >>> 0;
|
||||||
|
const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
||||||
|
const majority = a & b ^ a & c ^ b & c;
|
||||||
|
const temporary2 = sum0 + majority >>> 0;
|
||||||
|
h = g;
|
||||||
|
g = f;
|
||||||
|
f = e;
|
||||||
|
e = d + temporary1 >>> 0;
|
||||||
|
d = c;
|
||||||
|
c = b;
|
||||||
|
b = a;
|
||||||
|
a = temporary1 + temporary2 >>> 0;
|
||||||
|
}
|
||||||
|
hash[0] = hash[0] + a >>> 0;
|
||||||
|
hash[1] = hash[1] + b >>> 0;
|
||||||
|
hash[2] = hash[2] + c >>> 0;
|
||||||
|
hash[3] = hash[3] + d >>> 0;
|
||||||
|
hash[4] = hash[4] + e >>> 0;
|
||||||
|
hash[5] = hash[5] + f >>> 0;
|
||||||
|
hash[6] = hash[6] + g >>> 0;
|
||||||
|
hash[7] = hash[7] + h >>> 0;
|
||||||
|
}
|
||||||
|
const output = new Uint8Array(32);
|
||||||
|
for (let index = 0; index < hash.length; index++) {
|
||||||
|
output[index * 4] = hash[index] >>> 24 & 255;
|
||||||
|
output[index * 4 + 1] = hash[index] >>> 16 & 255;
|
||||||
|
output[index * 4 + 2] = hash[index] >>> 8 & 255;
|
||||||
|
output[index * 4 + 3] = hash[index] & 255;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
function hmacSha256(key, message) {
|
||||||
|
let keyBytes = key instanceof Uint8Array ? key : encodeUtf8(String(key));
|
||||||
|
if (keyBytes.length > 64)
|
||||||
|
keyBytes = sha256(keyBytes);
|
||||||
|
const messageBytes = message instanceof Uint8Array ? message : encodeUtf8(String(message));
|
||||||
|
const inner = new Uint8Array(64 + messageBytes.length);
|
||||||
|
const outer = new Uint8Array(96);
|
||||||
|
for (let index = 0; index < 64; index++) {
|
||||||
|
const value = index < keyBytes.length ? keyBytes[index] : 0;
|
||||||
|
inner[index] = value ^ 54;
|
||||||
|
outer[index] = value ^ 92;
|
||||||
|
}
|
||||||
|
inner.set(messageBytes, 64);
|
||||||
|
outer.set(sha256(inner), 64);
|
||||||
|
return sha256(outer);
|
||||||
|
}
|
||||||
|
function encodeBase64(bytes) {
|
||||||
|
let output = "";
|
||||||
|
for (let index = 0; index < bytes.length; index += 3) {
|
||||||
|
const secondExists = index + 1 < bytes.length;
|
||||||
|
const thirdExists = index + 2 < bytes.length;
|
||||||
|
const value = bytes[index] << 16 | (secondExists ? bytes[index + 1] : 0) << 8 | (thirdExists ? bytes[index + 2] : 0);
|
||||||
|
output += BASE64_ALPHABET[value >>> 18 & 63];
|
||||||
|
output += BASE64_ALPHABET[value >>> 12 & 63];
|
||||||
|
output += secondExists ? BASE64_ALPHABET[value >>> 6 & 63] : "=";
|
||||||
|
output += thirdExists ? BASE64_ALPHABET[value & 63] : "=";
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
function createNonce() {
|
||||||
|
const bytes = new Uint8Array(16);
|
||||||
|
for (let index = 0; index < bytes.length; index++)
|
||||||
|
bytes[index] = Math.floor(Math.random() * 256);
|
||||||
|
return encodeBase64(bytes).replace(/[/+=]/g, "").slice(0, 22);
|
||||||
|
}
|
||||||
|
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 || {})
|
||||||
|
}));
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error(`HTTP ${response.status} for ${url}`);
|
||||||
|
return yield response.text();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/flixer/decoder.js
|
||||||
|
var CANVAS_FINGERPRINT = "iVBORw0KGgoAAAANSUhEUgAAAMgAAAAyCAYAAAAZUZThAAAQAE";
|
||||||
|
function concatBytes(...values) {
|
||||||
|
let length = 0;
|
||||||
|
for (const value of values)
|
||||||
|
length += value.length;
|
||||||
|
const result = new Uint8Array(length);
|
||||||
|
let offset = 0;
|
||||||
|
for (const value of values) {
|
||||||
|
result.set(value, offset);
|
||||||
|
offset += value.length;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function toHex(bytes) {
|
||||||
|
let result = "";
|
||||||
|
for (const value of bytes)
|
||||||
|
result += value.toString(16).padStart(2, "0");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function decodeBase64(value) {
|
||||||
|
const binary = atob(value);
|
||||||
|
const bytes = new Uint8Array(binary.length);
|
||||||
|
for (let index = 0; index < binary.length; index++)
|
||||||
|
bytes[index] = binary.charCodeAt(index);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
function decodeUtf8(bytes) {
|
||||||
|
let result = "";
|
||||||
|
for (let index = 0; index < bytes.length; ) {
|
||||||
|
const first = bytes[index++];
|
||||||
|
let codePoint;
|
||||||
|
if (first < 128)
|
||||||
|
codePoint = first;
|
||||||
|
else if ((first & 224) === 192)
|
||||||
|
codePoint = (first & 31) << 6 | bytes[index++] & 63;
|
||||||
|
else if ((first & 240) === 224) {
|
||||||
|
codePoint = (first & 15) << 12 | (bytes[index++] & 63) << 6 | bytes[index++] & 63;
|
||||||
|
} else {
|
||||||
|
codePoint = (first & 7) << 18 | (bytes[index++] & 63) << 12 | (bytes[index++] & 63) << 6 | bytes[index++] & 63;
|
||||||
|
}
|
||||||
|
if (codePoint <= 65535)
|
||||||
|
result += String.fromCharCode(codePoint);
|
||||||
|
else {
|
||||||
|
codePoint -= 65536;
|
||||||
|
result += String.fromCharCode(55296 | codePoint >>> 10, 56320 | codePoint & 1023);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function multiplyByte(first, second) {
|
||||||
|
let result = 0;
|
||||||
|
for (let index = 0; index < 8; index++) {
|
||||||
|
if (second & 1)
|
||||||
|
result ^= first;
|
||||||
|
first = (first << 1 ^ (first & 128 ? 283 : 0)) & 255;
|
||||||
|
second >>>= 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function inverseByte(value) {
|
||||||
|
if (!value)
|
||||||
|
return 0;
|
||||||
|
let result = 1;
|
||||||
|
let base = value;
|
||||||
|
let power = 254;
|
||||||
|
while (power) {
|
||||||
|
if (power & 1)
|
||||||
|
result = multiplyByte(result, base);
|
||||||
|
base = multiplyByte(base, base);
|
||||||
|
power >>>= 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function rotateByte(value, amount) {
|
||||||
|
return (value << amount | value >>> 8 - amount) & 255;
|
||||||
|
}
|
||||||
|
var AES_SBOX = (() => {
|
||||||
|
const result = new Uint8Array(256);
|
||||||
|
for (let index = 0; index < 256; index++) {
|
||||||
|
const value = inverseByte(index);
|
||||||
|
result[index] = value ^ rotateByte(value, 1) ^ rotateByte(value, 2) ^ rotateByte(value, 3) ^ rotateByte(value, 4) ^ 99;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
function expandAes256Key(key) {
|
||||||
|
if (key.length !== 32)
|
||||||
|
throw new Error("Invalid AES-256 key");
|
||||||
|
const words = new Uint32Array(60);
|
||||||
|
for (let index = 0; index < 8; index++) {
|
||||||
|
const offset = index * 4;
|
||||||
|
words[index] = (key[offset] << 24 | key[offset + 1] << 16 | key[offset + 2] << 8 | key[offset + 3]) >>> 0;
|
||||||
|
}
|
||||||
|
let roundConstant = 1;
|
||||||
|
for (let index = 8; index < words.length; index++) {
|
||||||
|
let temporary = words[index - 1];
|
||||||
|
if (index % 8 === 0) {
|
||||||
|
temporary = (temporary << 8 | temporary >>> 24) >>> 0;
|
||||||
|
temporary = (AES_SBOX[temporary >>> 24] << 24 | AES_SBOX[temporary >>> 16 & 255] << 16 | AES_SBOX[temporary >>> 8 & 255] << 8 | AES_SBOX[temporary & 255]) >>> 0;
|
||||||
|
temporary ^= roundConstant << 24;
|
||||||
|
roundConstant = (roundConstant << 1 ^ (roundConstant & 128 ? 283 : 0)) & 255;
|
||||||
|
} else if (index % 8 === 4) {
|
||||||
|
temporary = (AES_SBOX[temporary >>> 24] << 24 | AES_SBOX[temporary >>> 16 & 255] << 16 | AES_SBOX[temporary >>> 8 & 255] << 8 | AES_SBOX[temporary & 255]) >>> 0;
|
||||||
|
}
|
||||||
|
words[index] = (words[index - 8] ^ temporary) >>> 0;
|
||||||
|
}
|
||||||
|
return words;
|
||||||
|
}
|
||||||
|
function addRoundKey(state, words, round) {
|
||||||
|
for (let column = 0; column < 4; column++) {
|
||||||
|
const word = words[round * 4 + column];
|
||||||
|
state[column * 4] ^= word >>> 24;
|
||||||
|
state[column * 4 + 1] ^= word >>> 16 & 255;
|
||||||
|
state[column * 4 + 2] ^= word >>> 8 & 255;
|
||||||
|
state[column * 4 + 3] ^= word & 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function encryptAesBlock(input, words) {
|
||||||
|
const state = new Uint8Array(input);
|
||||||
|
const shifted = new Uint8Array(16);
|
||||||
|
addRoundKey(state, words, 0);
|
||||||
|
for (let round = 1; round <= 14; round++) {
|
||||||
|
for (let index = 0; index < 16; index++)
|
||||||
|
state[index] = AES_SBOX[state[index]];
|
||||||
|
for (let row = 0; row < 4; row++) {
|
||||||
|
for (let column = 0; column < 4; column++) {
|
||||||
|
shifted[row + column * 4] = state[row + (column + row & 3) * 4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.set(shifted);
|
||||||
|
if (round !== 14) {
|
||||||
|
for (let column = 0; column < 4; column++) {
|
||||||
|
const offset = column * 4;
|
||||||
|
const first = state[offset];
|
||||||
|
const second = state[offset + 1];
|
||||||
|
const third = state[offset + 2];
|
||||||
|
const fourth = state[offset + 3];
|
||||||
|
const combined = first ^ second ^ third ^ fourth;
|
||||||
|
state[offset] ^= combined ^ multiplyByte(first ^ second, 2);
|
||||||
|
state[offset + 1] ^= combined ^ multiplyByte(second ^ third, 2);
|
||||||
|
state[offset + 2] ^= combined ^ multiplyByte(third ^ fourth, 2);
|
||||||
|
state[offset + 3] ^= combined ^ multiplyByte(fourth ^ first, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addRoundKey(state, words, round);
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
function bytesToWords(bytes) {
|
||||||
|
const result = new Uint32Array(4);
|
||||||
|
for (let index = 0; index < 4; index++) {
|
||||||
|
const offset = index * 4;
|
||||||
|
result[index] = (bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function wordsToBytes(words) {
|
||||||
|
const result = new Uint8Array(16);
|
||||||
|
for (let index = 0; index < 4; index++) {
|
||||||
|
result[index * 4] = words[index] >>> 24;
|
||||||
|
result[index * 4 + 1] = words[index] >>> 16 & 255;
|
||||||
|
result[index * 4 + 2] = words[index] >>> 8 & 255;
|
||||||
|
result[index * 4 + 3] = words[index] & 255;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function multiplyGhash(first, second) {
|
||||||
|
const result = new Uint32Array(4);
|
||||||
|
const value = new Uint32Array(second);
|
||||||
|
for (let bit = 0; bit < 128; bit++) {
|
||||||
|
if (first[bit >>> 5] >>> 31 - (bit & 31) & 1) {
|
||||||
|
for (let index = 0; index < 4; index++)
|
||||||
|
result[index] ^= value[index];
|
||||||
|
}
|
||||||
|
const lowBit = value[3] & 1;
|
||||||
|
value[3] = (value[3] >>> 1 | (value[2] & 1) << 31) >>> 0;
|
||||||
|
value[2] = (value[2] >>> 1 | (value[1] & 1) << 31) >>> 0;
|
||||||
|
value[1] = (value[1] >>> 1 | (value[0] & 1) << 31) >>> 0;
|
||||||
|
value[0] >>>= 1;
|
||||||
|
if (lowBit)
|
||||||
|
value[0] = (value[0] ^ 3774873600) >>> 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function calculateGhash(ciphertext, hashKey) {
|
||||||
|
let state = new Uint32Array(4);
|
||||||
|
for (let offset = 0; offset < ciphertext.length; offset += 16) {
|
||||||
|
const block = new Uint8Array(16);
|
||||||
|
block.set(ciphertext.subarray(offset, Math.min(offset + 16, ciphertext.length)));
|
||||||
|
const words = bytesToWords(block);
|
||||||
|
for (let index = 0; index < 4; index++)
|
||||||
|
state[index] ^= words[index];
|
||||||
|
state = multiplyGhash(state, hashKey);
|
||||||
|
}
|
||||||
|
const lengthBlock = new Uint8Array(16);
|
||||||
|
const bitLength = ciphertext.length * 8;
|
||||||
|
const view = new DataView(lengthBlock.buffer);
|
||||||
|
view.setUint32(12, bitLength >>> 0);
|
||||||
|
view.setUint32(8, Math.floor(bitLength / 4294967296));
|
||||||
|
const lengths = bytesToWords(lengthBlock);
|
||||||
|
for (let index = 0; index < 4; index++)
|
||||||
|
state[index] ^= lengths[index];
|
||||||
|
return multiplyGhash(state, hashKey);
|
||||||
|
}
|
||||||
|
function incrementCounter(counter) {
|
||||||
|
for (let index = 15; index >= 12; index--) {
|
||||||
|
counter[index] = counter[index] + 1 & 255;
|
||||||
|
if (counter[index])
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function decryptAesGcm(payload, key) {
|
||||||
|
if (payload.length < 28)
|
||||||
|
throw new Error("Invalid encrypted payload");
|
||||||
|
const nonce = payload.subarray(0, 12);
|
||||||
|
const ciphertext = payload.subarray(12, payload.length - 16);
|
||||||
|
const expectedTag = payload.subarray(payload.length - 16);
|
||||||
|
const words = expandAes256Key(key);
|
||||||
|
const zero = new Uint8Array(16);
|
||||||
|
const hashKey = bytesToWords(encryptAesBlock(zero, words));
|
||||||
|
const initialCounter = new Uint8Array(16);
|
||||||
|
initialCounter.set(nonce);
|
||||||
|
initialCounter[15] = 1;
|
||||||
|
const authentication = wordsToBytes(calculateGhash(ciphertext, hashKey));
|
||||||
|
const encryptedCounter = encryptAesBlock(initialCounter, words);
|
||||||
|
let mismatch = 0;
|
||||||
|
for (let index = 0; index < 16; index++)
|
||||||
|
mismatch |= authentication[index] ^ encryptedCounter[index] ^ expectedTag[index];
|
||||||
|
if (mismatch)
|
||||||
|
throw new Error("Invalid encrypted payload tag");
|
||||||
|
const output = new Uint8Array(ciphertext.length);
|
||||||
|
const counter = new Uint8Array(initialCounter);
|
||||||
|
for (let offset = 0; offset < ciphertext.length; offset += 16) {
|
||||||
|
incrementCounter(counter);
|
||||||
|
const stream = encryptAesBlock(counter, words);
|
||||||
|
const length = Math.min(16, ciphertext.length - offset);
|
||||||
|
for (let index = 0; index < length; index++)
|
||||||
|
output[offset + index] = ciphertext[offset + index] ^ stream[index];
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
function derivePayloadKey(apiKey, now) {
|
||||||
|
const hour = Math.floor(now / 36e5);
|
||||||
|
const dynamic = new Uint8Array(16);
|
||||||
|
for (let index = 0; index < dynamic.length; index++) {
|
||||||
|
dynamic[index] = index + 1 ^ Math.floor(hour / 2 ** (index & 7)) & 255;
|
||||||
|
}
|
||||||
|
let derived = concatBytes(encodeUtf8(apiKey), dynamic);
|
||||||
|
for (let iteration = 0; iteration < 600; iteration++)
|
||||||
|
derived = sha256(derived);
|
||||||
|
const timeBucket = Math.floor(now / 3e5);
|
||||||
|
return hmacSha256(derived, `${timeBucket}${apiKey}`);
|
||||||
|
}
|
||||||
|
function initDecoder() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function get_img_key() {
|
||||||
|
const timestamp = Math.floor(Date.now() / 1e3);
|
||||||
|
const fingerprint = `1365x768:24:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36:Linux x86_64:en-US:-180:${timestamp}:${CANVAS_FINGERPRINT}`;
|
||||||
|
return toHex(sha256(encodeUtf8(toHex(sha256(encodeUtf8(fingerprint))))));
|
||||||
|
}
|
||||||
|
function process_img_data(value, apiKey) {
|
||||||
|
const key = derivePayloadKey(apiKey, Date.now());
|
||||||
|
return decodeUtf8(decryptAesGcm(decodeBase64(value), key));
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/flixer/extractor.js
|
||||||
|
var NATO_ORDER = [
|
||||||
|
"alpha",
|
||||||
|
"bravo",
|
||||||
|
"charlie",
|
||||||
|
"delta",
|
||||||
|
"echo",
|
||||||
|
"foxtrot",
|
||||||
|
"golf",
|
||||||
|
"hotel",
|
||||||
|
"india",
|
||||||
|
"juliet",
|
||||||
|
"kilo",
|
||||||
|
"lima",
|
||||||
|
"mike",
|
||||||
|
"november",
|
||||||
|
"oscar",
|
||||||
|
"papa",
|
||||||
|
"quebec",
|
||||||
|
"romeo",
|
||||||
|
"sierra",
|
||||||
|
"tango",
|
||||||
|
"uniform",
|
||||||
|
"victor",
|
||||||
|
"whiskey",
|
||||||
|
"xray",
|
||||||
|
"yankee",
|
||||||
|
"zulu"
|
||||||
|
];
|
||||||
|
var SERVER_LABELS = {
|
||||||
|
bravo: "Balder",
|
||||||
|
delta: "Dionysus",
|
||||||
|
echo: "Eros",
|
||||||
|
golf: "Gaia",
|
||||||
|
hotel: "Hades",
|
||||||
|
india: "Isis",
|
||||||
|
juliet: "Juno",
|
||||||
|
kilo: "Kronos",
|
||||||
|
lima: "Loki"
|
||||||
|
};
|
||||||
|
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))
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getServers(payload) {
|
||||||
|
const values = [];
|
||||||
|
if (payload && Array.isArray(payload.sources)) {
|
||||||
|
for (const source of payload.sources) {
|
||||||
|
const server = source && String(source.server || "").toLowerCase();
|
||||||
|
if (server && !values.includes(server))
|
||||||
|
values.push(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (payload && payload.servers && typeof payload.servers === "object") {
|
||||||
|
for (const server of Object.keys(payload.servers)) {
|
||||||
|
const normalized = server.toLowerCase();
|
||||||
|
if (!values.includes(normalized))
|
||||||
|
values.push(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NATO_ORDER.filter((server) => values.includes(server)).concat(values.filter((server) => !NATO_ORDER.includes(server)));
|
||||||
|
}
|
||||||
|
function findUrl(payload, server) {
|
||||||
|
if (payload && Array.isArray(payload.sources)) {
|
||||||
|
const exact = payload.sources.find((source) => source && String(source.server || "").toLowerCase() === server);
|
||||||
|
const candidate = exact || payload.sources.find((source) => source && source.url);
|
||||||
|
return candidate && typeof candidate.url === "string" ? candidate.url.trim() : "";
|
||||||
|
}
|
||||||
|
if (payload && payload.sources && typeof payload.sources === "object") {
|
||||||
|
const value = payload.sources.file || payload.sources.url;
|
||||||
|
return typeof value === "string" ? value.trim() : "";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
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
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
module.exports = { getStreams };
|
||||||
+64
-13
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* miruro - Built from src/miruro/
|
* miruro - Built from src/miruro/
|
||||||
* Generated: 2026-07-19T17:01:21.823Z
|
* Generated: 2026-07-19T17:21:26.205Z
|
||||||
*/
|
*/
|
||||||
var __defProp = Object.defineProperty;
|
var __defProp = Object.defineProperty;
|
||||||
var __defProps = Object.defineProperties;
|
var __defProps = Object.defineProperties;
|
||||||
@@ -573,6 +573,7 @@ function secureGet(_0) {
|
|||||||
// src/miruro/extractor.js
|
// src/miruro/extractor.js
|
||||||
var ANIMAP_URL = "https://animap.id/api/map";
|
var ANIMAP_URL = "https://animap.id/api/map";
|
||||||
var ANILIST_URL = "https://graphql.anilist.co";
|
var ANILIST_URL = "https://graphql.anilist.co";
|
||||||
|
var HLS_QUALITY_TIMEOUT_MS = 4e3;
|
||||||
var PROVIDERS = [
|
var PROVIDERS = [
|
||||||
{ id: "bonk", label: "Bonk", subCategory: "ssub" },
|
{ id: "bonk", label: "Bonk", subCategory: "ssub" },
|
||||||
{ id: "kiwi", label: "Kiwi", subCategory: "sub" },
|
{ id: "kiwi", label: "Kiwi", subCategory: "sub" },
|
||||||
@@ -762,38 +763,88 @@ function getOrigin(url) {
|
|||||||
const match = typeof url === "string" ? url.match(/^(https?:\/\/[^/]+)/i) : null;
|
const match = typeof url === "string" ? url.match(/^(https?:\/\/[^/]+)/i) : null;
|
||||||
return match ? match[1] : BASE_URL;
|
return match ? match[1] : BASE_URL;
|
||||||
}
|
}
|
||||||
function getQuality(stream) {
|
function getDeclaredQuality(stream) {
|
||||||
const value = String(stream.quality || stream.server || "HD");
|
const value = `${stream.quality || ""} ${stream.resolution || ""} ${stream.server || ""}`;
|
||||||
const resolution = value.match(/(2160|1440|1080|720|480|360)p?/i);
|
const resolution = value.match(/(2160|1440|1080|720|480|360)p?/i);
|
||||||
if (resolution) {
|
if (resolution) {
|
||||||
return `${resolution[1]}p`;
|
return `${resolution[1]}p`;
|
||||||
}
|
}
|
||||||
return /4k/i.test(value) ? "4K" : "HD";
|
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";
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "HD";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function isPlayableStream(stream) {
|
function isPlayableStream(stream) {
|
||||||
const type = String(stream && stream.type || "").toLowerCase();
|
const type = String(stream && stream.type || "").toLowerCase();
|
||||||
return !!(stream && stream.url) && (type === "hls" || type === "m3u8" || type === "mp4" || type === "application/x-mpegurl");
|
return !!(stream && stream.url) && (type === "hls" || type === "m3u8" || type === "mp4" || type === "application/x-mpegurl");
|
||||||
}
|
}
|
||||||
function normalizeStreams(sourcePayload, provider, variant) {
|
function normalizeStreams(sourcePayload, provider, variant) {
|
||||||
|
return __async(this, null, function* () {
|
||||||
const subtitleCount = Array.isArray(sourcePayload.subtitles) ? sourcePayload.subtitles.length : 0;
|
const subtitleCount = Array.isArray(sourcePayload.subtitles) ? sourcePayload.subtitles.length : 0;
|
||||||
const language = variant === "dub" ? "DUB" : "SUB";
|
const language = variant === "dub" ? "DUB" : "SUB";
|
||||||
return (sourcePayload.streams || []).filter(isPlayableStream).map((stream) => {
|
return Promise.all((sourcePayload.streams || []).filter(isPlayableStream).map((stream) => __async(this, null, function* () {
|
||||||
const referer = stream.referer || BASE_URL;
|
const streamHeaders = stream.headers && typeof stream.headers === "object" ? stream.headers : {};
|
||||||
const server = stream.server || getQuality(stream);
|
const referer = stream.referer || streamHeaders.Referer || streamHeaders.referer || BASE_URL;
|
||||||
const subtitleLabel = subtitleCount > 0 && variant !== "dub" ? " \xB7 CC" : "";
|
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 {
|
return {
|
||||||
// Nuvio's source picker prominently displays `name`, so include the
|
// Nuvio's source picker prominently displays `name`, so include the
|
||||||
// Miruro server and audio variant here instead of only in `title`.
|
// Miruro server and audio variant here instead of only in `title`.
|
||||||
name: `Miruro \xB7 ${provider.label} \xB7 ${language}`,
|
name: `Miruro \xB7 ${provider.label} \xB7 ${language}`,
|
||||||
title: `[${language}] ${provider.label} \xB7 ${server}${subtitleLabel}`,
|
title: `[${language}] ${provider.label} \xB7 ${server}${subtitleLabel}`,
|
||||||
url: stream.url,
|
url: stream.url,
|
||||||
quality: getQuality(stream),
|
quality,
|
||||||
headers: {
|
headers
|
||||||
"Referer": referer,
|
|
||||||
"Origin": getOrigin(referer),
|
|
||||||
"User-Agent": HEADERS["User-Agent"]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function getProviderStreams(provider, providerPayload, anilistId, episodeNumber) {
|
function getProviderStreams(provider, providerPayload, anilistId, episodeNumber) {
|
||||||
|
|||||||
+80
-15
@@ -2,10 +2,11 @@
|
|||||||
* Resolve TMDB media to AniList, then retrieve Miruro episode sources.
|
* Resolve TMDB media to AniList, then retrieve Miruro episode sources.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BASE_URL, fetchJson, HEADERS, secureGet } from './http.js';
|
import { BASE_URL, fetchJson, fetchText, HEADERS, secureGet } from './http.js';
|
||||||
|
|
||||||
const ANIMAP_URL = 'https://animap.id/api/map';
|
const ANIMAP_URL = 'https://animap.id/api/map';
|
||||||
const ANILIST_URL = 'https://graphql.anilist.co';
|
const ANILIST_URL = 'https://graphql.anilist.co';
|
||||||
|
const HLS_QUALITY_TIMEOUT_MS = 4000;
|
||||||
|
|
||||||
// Miruro's visible native-player providers and their preferred subtitle mode.
|
// Miruro's visible native-player providers and their preferred subtitle mode.
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
@@ -270,15 +271,71 @@ function getOrigin(url) {
|
|||||||
return match ? match[1] : BASE_URL;
|
return match ? match[1] : BASE_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getQuality(stream) {
|
function getDeclaredQuality(stream) {
|
||||||
const value = String(stream.quality || stream.server || 'HD');
|
const value = `${stream.quality || ''} ${stream.resolution || ''} ${stream.server || ''}`;
|
||||||
const resolution = value.match(/(2160|1440|1080|720|480|360)p?/i);
|
const resolution = value.match(/(2160|1440|1080|720|480|360)p?/i);
|
||||||
|
|
||||||
if (resolution) {
|
if (resolution) {
|
||||||
return `${resolution[1]}p`;
|
return `${resolution[1]}p`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return /4k/i.test(value) ? '4K' : 'HD';
|
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 (_) {
|
||||||
|
// A stream can still be playable when its manifest rejects this
|
||||||
|
// metadata request, so retain it with an unknown HD quality.
|
||||||
|
} finally {
|
||||||
|
if (timeoutId != null) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'HD';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlayableStream(stream) {
|
function isPlayableStream(stream) {
|
||||||
@@ -291,14 +348,26 @@ function isPlayableStream(stream) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeStreams(sourcePayload, provider, variant) {
|
async function normalizeStreams(sourcePayload, provider, variant) {
|
||||||
const subtitleCount = Array.isArray(sourcePayload.subtitles) ? sourcePayload.subtitles.length : 0;
|
const subtitleCount = Array.isArray(sourcePayload.subtitles) ? sourcePayload.subtitles.length : 0;
|
||||||
const language = variant === 'dub' ? 'DUB' : 'SUB';
|
const language = variant === 'dub' ? 'DUB' : 'SUB';
|
||||||
|
|
||||||
return (sourcePayload.streams || []).filter(isPlayableStream).map((stream) => {
|
return Promise.all((sourcePayload.streams || []).filter(isPlayableStream).map(async (stream) => {
|
||||||
const referer = stream.referer || BASE_URL;
|
const streamHeaders = stream.headers && typeof stream.headers === 'object'
|
||||||
const server = stream.server || getQuality(stream);
|
? stream.headers
|
||||||
|
: {};
|
||||||
|
const referer = stream.referer || streamHeaders.Referer || streamHeaders.referer || BASE_URL;
|
||||||
const subtitleLabel = subtitleCount > 0 && variant !== 'dub' ? ' · CC' : '';
|
const subtitleLabel = subtitleCount > 0 && variant !== 'dub' ? ' · CC' : '';
|
||||||
|
const headers = {
|
||||||
|
...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 {
|
return {
|
||||||
// Nuvio's source picker prominently displays `name`, so include the
|
// Nuvio's source picker prominently displays `name`, so include the
|
||||||
@@ -306,14 +375,10 @@ function normalizeStreams(sourcePayload, provider, variant) {
|
|||||||
name: `Miruro · ${provider.label} · ${language}`,
|
name: `Miruro · ${provider.label} · ${language}`,
|
||||||
title: `[${language}] ${provider.label} · ${server}${subtitleLabel}`,
|
title: `[${language}] ${provider.label} · ${server}${subtitleLabel}`,
|
||||||
url: stream.url,
|
url: stream.url,
|
||||||
quality: getQuality(stream),
|
quality,
|
||||||
headers: {
|
headers
|
||||||
'Referer': referer,
|
|
||||||
'Origin': getOrigin(referer),
|
|
||||||
'User-Agent': HEADERS['User-Agent']
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getProviderStreams(provider, providerPayload, anilistId, episodeNumber) {
|
async function getProviderStreams(provider, providerPayload, anilistId, episodeNumber) {
|
||||||
|
|||||||
Reference in new issue
Block a user