updated marketplace and extensions
This commit is contained in:
338
book/mangafire.js
Normal file
338
book/mangafire.js
Normal 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;
|
||||
Reference in New Issue
Block a user