updated marketplace and extensions

This commit is contained in:
2026-01-01 20:44:41 +01:00
parent 1f52ac678e
commit ff24046f61
9 changed files with 1285 additions and 186 deletions

338
book/mangafire.js Normal file
View File

@@ -0,0 +1,338 @@
class MangaFire {
constructor() {
this.baseUrl = "https://mangafire.to";
this.type = "book-board";
this.version = "1.0";
this.mediaType = "manga";
}
async search(queryObj) {
const query = queryObj.query.trim();
const vrf = this.generate(query);
const res = await fetch(
`${this.baseUrl}/ajax/manga/search?keyword=${query.replaceAll(" ", "+")}&vrf=${vrf}`
);
const data = await res.json();
if (!data?.result?.html) return [];
const $ = this.cheerio.load(data.result.html);
return $("a.unit")
.map((_, e) => {
const el = $(e);
return {
id: el.attr("href")?.replace("/manga/", ""),
title: el.find("h6").text().trim(),
image: el.find("img").attr("src") || null,
rating: null,
type: "book",
};
})
.get();
}
async getMetadata(id) {
const res = await fetch(`${this.baseUrl}/manga/${id}`);
const html = await res.text();
const $ = this.cheerio.load(html);
const info = $(".info").first();
const title = info.find("h1[itemprop='name']").text().trim();
const scoreText = info
.find("span b")
.filter((_, e) => $(e).text().includes("MAL"))
.first()
.text();
const score = parseFloat(scoreText.replace(/[^\d.]/g, "")) || 0;
const genres = $("span:contains('Genres:')")
.next("span")
.find("a")
.map((_, e) => $(e).text().trim())
.get()
.join(", ");
const status = info.find("p").first().text().trim().toLowerCase();
const published = $("span:contains('Published:')")
.next("span")
.text()
.trim();
const summary = $(".description").text().trim();
const image =
$(".poster img[itemprop='image']").attr("src") || null;
return {
id,
title,
format: "MANGA",
score,
genres,
status,
published,
summary,
chapters: 0,
image,
};
}
async findChapters(mangaId) {
const res = await fetch(`${this.baseUrl}/manga/${mangaId}`);
const html = await res.text();
const $ = this.cheerio.load(html);
const langs = this.extractLanguageCodes($);
const all = [];
for (const lang of langs) {
const chapters = await this.fetchChaptersForLanguage(mangaId, lang);
all.push(...chapters);
}
return all;
}
extractLanguageCodes($) {
const map = new Map();
$("[data-code][data-title]").each((_, e) => {
let code = $(e).attr("data-code")?.toLowerCase() || "";
const title = $(e).attr("data-title") || "";
if (code === "es" && title.includes("LATAM")) code = "es-la";
else if (code === "pt" && title.includes("Br")) code = "pt-br";
map.set(code, code);
});
return [...map.values()];
}
async fetchChaptersForLanguage(mangaId, lang) {
const mangaIdShort = mangaId.split(".").pop();
const vrf = this.generate(mangaIdShort + "@chapter@" + lang);
const res = await fetch(
`${this.baseUrl}/ajax/read/${mangaIdShort}/chapter/${lang}?vrf=${vrf}`
);
const data = await res.json();
if (!data?.result?.html) return [];
const $ = this.cheerio.load(data.result.html);
const chapters = [];
$("a[data-number][data-id]").each((i, e) => {
chapters.push({
id: $(e).attr("data-id"),
title: $(e).attr("title") || "",
number: Number($(e).attr("data-number")) || i + 1,
language: this.normalizeLanguageCode(lang),
releaseDate: null,
index: i,
});
});
return chapters.reverse().map((c, i) => ({ ...c, index: i }));
}
normalizeLanguageCode(lang) {
const map = {
en: "en",
fr: "fr",
es: "es",
"es-la": "es-419",
pt: "pt",
"pt-br": "pt-br",
ja: "ja",
de: "de",
it: "it",
ru: "ru",
ko: "ko",
zh: "zh",
"zh-cn": "zh-cn",
"zh-tw": "zh-tw",
ar: "ar",
tr: "tr",
};
return map[lang] || lang;
}
async findChapterPages(chapterId) {
const vrf = this.generate("chapter@" + chapterId);
const res = await fetch(
`${this.baseUrl}/ajax/read/chapter/${chapterId}?vrf=${vrf}`
);
const data = await res.json();
const images = data?.result?.images;
if (!images?.length) return [];
return images.map((img, i) => ({
url: img[0],
index: i,
headers: {
Referer: this.baseUrl,
},
}));
}
textEncode(str) {
return Uint8Array.from(Buffer.from(str, "utf-8"));
}
textDecode(bytes) {
return Buffer.from(bytes).toString("utf-8");
}
atob(data) {
return Uint8Array.from(Buffer.from(data, "base64"));
}
btoa(data) {
return Buffer.from(data).toString("base64");
}
add8(n) {
return (c) => (c + n) & 0xff;
}
sub8(n) {
return (c) => (c - n + 256) & 0xff;
}
xor8(n) {
return (c) => (c ^ n) & 0xff;
}
rotl8(n) {
return (c) => ((c << n) | (c >> (8 - n))) & 0xff;
}
rotr8(n) {
return (c) => ((c >> n) | (c << (8 - n))) & 0xff;
}
scheduleC = [
this.sub8(223), this.rotr8(4), this.rotr8(4), this.add8(234), this.rotr8(7),
this.rotr8(2), this.rotr8(7), this.sub8(223), this.rotr8(7), this.rotr8(6),
];
scheduleY = [
this.add8(19), this.rotr8(7), this.add8(19), this.rotr8(6), this.add8(19),
this.rotr8(1), this.add8(19), this.rotr8(6), this.rotr8(7), this.rotr8(4),
];
scheduleB = [
this.sub8(223), this.rotr8(1), this.add8(19), this.sub8(223), this.rotl8(2),
this.sub8(223), this.add8(19), this.rotl8(1), this.rotl8(2), this.rotl8(1),
];
scheduleJ = [
this.add8(19), this.rotl8(1), this.rotl8(1), this.rotr8(1), this.add8(234),
this.rotl8(1), this.sub8(223), this.rotl8(6), this.rotl8(4), this.rotl8(1),
];
scheduleE = [
this.rotr8(1), this.rotl8(1), this.rotl8(6), this.rotr8(1), this.rotl8(2),
this.rotr8(4), this.rotl8(1), this.rotl8(1), this.sub8(223), this.rotl8(2),
];
rc4Keys = {
l: "FgxyJUQDPUGSzwbAq/ToWn4/e8jYzvabE+dLMb1XU1o=",
g: "CQx3CLwswJAnM1VxOqX+y+f3eUns03ulxv8Z+0gUyik=",
B: "fAS+otFLkKsKAJzu3yU+rGOlbbFVq+u+LaS6+s1eCJs=",
m: "Oy45fQVK9kq9019+VysXVlz1F9S1YwYKgXyzGlZrijo=",
F: "aoDIdXezm2l3HrcnQdkPJTDT8+W6mcl2/02ewBHfPzg=",
};
seeds32 = {
A: "yH6MXnMEcDVWO/9a6P9W92BAh1eRLVFxFlWTHUqQ474=",
V: "RK7y4dZ0azs9Uqz+bbFB46Bx2K9EHg74ndxknY9uknA=",
N: "rqr9HeTQOg8TlFiIGZpJaxcvAaKHwMwrkqojJCpcvoc=",
P: "/4GPpmZXYpn5RpkP7FC/dt8SXz7W30nUZTe8wb+3xmU=",
k: "wsSGSBXKWA9q1oDJpjtJddVxH+evCfL5SO9HZnUDFU8=",
};
prefixKeys = {
O: "l9PavRg=",
v: "Ml2v7ag1Jg==",
L: "i/Va0UxrbMo=",
p: "WFjKAHGEkQM=",
W: "5Rr27rWd",
};
rc4(key, input) {
const s = new Uint8Array(256);
for (let i = 0; i < 256; i++) s[i] = i;
let j = 0;
for (let i = 0; i < 256; i++) {
j = (j + s[i] + key[i % key.length]) & 0xff;
[s[i], s[j]] = [s[j], s[i]];
}
const output = new Uint8Array(input.length);
let i = 0;
j = 0;
for (let y = 0; y < input.length; y++) {
i = (i + 1) & 0xff;
j = (j + s[i]) & 0xff;
[s[i], s[j]] = [s[j], s[i]];
const k = s[(s[i] + s[j]) & 0xff];
output[y] = input[y] ^ k;
}
return output;
}
transform(input, initSeedBytes, prefixKeyBytes, prefixLen, schedule) {
const out = [];
for (let i = 0; i < input.length; i++) {
if (i < prefixLen) {
out.push(prefixKeyBytes[i] || 0);
}
const transformed = schedule[i % 10]((input[i] ^ initSeedBytes[i % 32]) & 0xff) & 0xff;
out.push(transformed);
}
return new Uint8Array(out);
}
generate(input) {
let encodedInput = encodeURIComponent(input);
let bytes = this.textEncode(encodedInput);
// Etapa 1: RC4 con clave "l" + Transform con schedule_c
bytes = this.rc4(this.atob(this.rc4Keys["l"]), bytes);
const prefix_O = this.atob(this.prefixKeys["O"]);
bytes = this.transform(bytes, this.atob(this.seeds32["A"]), prefix_O, prefix_O.length, this.scheduleC);
// Etapa 2: RC4 con clave "g" + Transform con schedule_y
bytes = this.rc4(this.atob(this.rc4Keys["g"]), bytes);
const prefix_v = this.atob(this.prefixKeys["v"]);
bytes = this.transform(bytes, this.atob(this.seeds32["V"]), prefix_v, prefix_v.length, this.scheduleY);
// Etapa 3: RC4 con clave "B" + Transform con schedule_b
bytes = this.rc4(this.atob(this.rc4Keys["B"]), bytes);
const prefix_L = this.atob(this.prefixKeys["L"]);
bytes = this.transform(bytes, this.atob(this.seeds32["N"]), prefix_L, prefix_L.length, this.scheduleB);
// Etapa 4: RC4 con clave "m" + Transform con schedule_j
bytes = this.rc4(this.atob(this.rc4Keys["m"]), bytes);
const prefix_p = this.atob(this.prefixKeys["p"]);
bytes = this.transform(bytes, this.atob(this.seeds32["P"]), prefix_p, prefix_p.length, this.scheduleJ);
// Etapa 5: RC4 con clave "F" + Transform con schedule_e
bytes = this.rc4(this.atob(this.rc4Keys["F"]), bytes);
const prefix_W = this.atob(this.prefixKeys["W"]);
bytes = this.transform(bytes, this.atob(this.seeds32["k"]), prefix_W, prefix_W.length, this.scheduleE);
// Base64URL encode
return this.btoa(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
}
module.exports = MangaFire;