source/mangafire/mangafire.js
2025-12-03 15:47:30 +00:00

439 lines
No EOL
11 KiB
JavaScript

async function searchContent(input,page=0){
function parseSearchResults(html) {
const results = [];
const regex = /<div class="unit item-\d+">[\s\S]*?<a href="\/manga\/([\w\-\.]+)"[^>]*class="poster"[\s\S]*?<img src="([^"]*)"[^>]*alt="([^"]*)"/g;
let match;
while ((match = regex.exec(html)) !== null) {
results.push({
title: match[3],
imageURL: match[2],
id: match[1]
});
}
return results;
} const vrf = generate_vrf(input);
const response = await fetch("https://mangafire.to/filter?keyword=" + encodeURIComponent(input) + "&vrf=" + vrf);
const data = await response.text();
console.log(JSON.stringify(parseSearchResults(data)));
return parseSearchResults(data);
}
async function getContentData(url) {
function parseHtmlData(htmlContent) {
const genreRegex = /<a href="\/genre\/[^"]*">([^<]+)<\/a>/g;
const tags = [];
let match;
while ((match = genreRegex.exec(htmlContent)) !== null) {
if (match[1].trim()) tags.push(match[1].trim());
}
const uniqueTags = [...new Set(tags)];
const ogDescriptionRegex =
/<meta property="og:description" content=['"]([^'"]*)['"]/i;
const ogMatch = htmlContent.match(ogDescriptionRegex);
let description = ogMatch ? ogMatch[1] : "";
if (!description) {
const metaDescriptionRegex =
/<meta name="description" content=['"]([^'"]*)['"]/i;
const metaMatch = htmlContent.match(metaDescriptionRegex);
description = metaMatch ? metaMatch[1] : "";
}
description = description
.replace(/(&quot;)/g, '"')
.replace(/(&amp;)/g, '&')
.replace(/(&lt;)/g, '<')
.replace(/(&gt;)/g, '>')
.replace(/\s+/g, ' ')
.trim();
if (uniqueTags.length === 0) {
uniqueTags.push("Unknown");
}
return {
description: description,
tags: uniqueTags
};
}
const response = await fetch(`https://mangafire.to${url}`);
const data = await response.text();
console.log(JSON.stringify(parseHtmlData(data)));
return parseHtmlData(data);
}
async function getChapters(url) {
const mangaIdMatch = url.match(/\.([a-z0-9]+)$/);
const mangaId = mangaIdMatch ? mangaIdMatch[1] : null;
vrf = generate_vrf(`${mangaId}@chapter@en`);
if (!mangaId) {
console.error("Could not extract manga ID from URL");
return null;
}
function parseChapters(htmlContent) {
const chapters = {};
const chapterRegex =
/<a href="\/read\/[^"]+\/([^/]+)\/chapter-[^"]*" data-number="([^"]+)" data-id="([^"]+)"[^>]*>(.*?)<\/a>/gs;
let match;
while ((match = chapterRegex.exec(htmlContent)) !== null) {
const langCode = match[1];
const chapterNumber = match[2];
const chapterId = match[3];
const title = match[4].replace(/<[^>]*>/g, '').trim();
if (!chapters[langCode]) chapters[langCode] = [];
chapters[langCode].push([
chapterNumber,
[
{
id: chapterId,
title: title,
chapter: Number(chapterNumber),
scanlation_group: "Mangafire"
}
]
]);
}
Object.keys(chapters).forEach(lang => chapters[lang].reverse());
return chapters;
}
try {
const response = await fetch(`https://mangafire.to/ajax/read/${mangaId}/chapter/en?vrf=${vrf}`);
const data = await response.json();
if (data.status === 200 && data.result && data.result.html) {
const chapters = parseChapters(data.result.html);
console.log(JSON.stringify(chapters));
return chapters;
} else {
console.error("Invalid response from server");
return null;
}
} catch (error) {
console.error("Error fetching chapters:" + error);
return null;
}
}
async function getChapterImages(ID) {
vrf = generate_vrf(`chapter@${ID}`);
try {
const response = await fetch(`https://mangafire.to/ajax/read/chapter/${ID}?vrf=${vrf}`);
const data = await response.json();
if (data.status === 200 && data.result && data.result.images) {
const images = data.result.images.map(img => img[0]);
console.log(JSON.stringify(images));
return images;
} else {
console.error("Invalid response from server");
return null;
}
} catch (error) {
console.error("Error fetching chapters:" + error);
return null;
}
}
function b64encode(data) {
const keystr =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
function atobLookup(chr) {
const index = keystr.indexOf(chr);
return index < 0 ? undefined : index;
}
data = `${data}`;
data = data.replace(/[ \t\n\f\r]/g, "");
if (data.length % 4 === 0) {
data = data.replace(/==?$/, "");
}
if (data.length % 4 === 1 || /[^+/0-9A-Za-z]/.test(data)) {
return null;
}
let output = "";
let buffer = 0;
let accumulatedBits = 0;
for (let i = 0; i < data.length; i++) {
buffer <<= 6;
buffer |= atobLookup(data[i]);
accumulatedBits += 6;
if (accumulatedBits === 24) {
output += String.fromCharCode((buffer & 0xff0000) >> 16);
output += String.fromCharCode((buffer & 0xff00) >> 8);
output += String.fromCharCode(buffer & 0xff);
buffer = accumulatedBits = 0;
}
}
if (accumulatedBits === 12) {
buffer >>= 4;
output += String.fromCharCode(buffer);
} else if (accumulatedBits === 18) {
buffer >>= 2;
output += String.fromCharCode((buffer & 0xff00) >> 8);
output += String.fromCharCode(buffer & 0xff);
}
return output;
}
function b64decode(s) {
const keystr =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
function btoaLookup(index) {
if (index >= 0 && index < 64) {
return keystr[index];
}
return undefined;
}
let i;
s = `${s}`;
for (i = 0; i < s.length; i++) {
if (s.charCodeAt(i) > 255) {
return null;
}
}
let out = "";
for (i = 0; i < s.length; i += 3) {
const groupsOfSix = [undefined, undefined, undefined, undefined];
groupsOfSix[0] = s.charCodeAt(i) >> 2;
groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4;
if (s.length > i + 1) {
groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4;
groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2;
}
if (s.length > i + 2) {
groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6;
groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f;
}
for (let j = 0; j < groupsOfSix.length; j++) {
if (typeof groupsOfSix[j] === "undefined") {
out += "=";
} else {
out += btoaLookup(groupsOfSix[j]);
}
}
}
return out;
}
const toBytes = (str) => Array.from(str, (c) => c.charCodeAt(0) & 0xff);
const fromBytes = (bytes) =>
bytes.map((b) => String.fromCharCode(b & 0xff)).join("");
function rc4Bytes(key, input) {
const s = Array.from({ length: 256 }, (_, i) => i);
let j = 0;
for (let i = 0; i < 256; i++) {
j = (j + s[i] + key.charCodeAt(i % key.length)) & 0xff;
[s[i], s[j]] = [s[j], s[i]];
}
const out = new Array(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];
out[y] = (input[y] ^ k) & 0xff;
}
return out;
}
function transform(input, initSeedBytes, prefixKeyBytes, prefixLen, schedule) {
const out = [];
for (let i = 0; i < input.length; i++) {
if (i < prefixLen) out.push(prefixKeyBytes[i]);
out.push(
schedule[i % 10]((input[i] ^ initSeedBytes[i % 32]) & 0xff) & 0xff
);
}
return out;
}
const add8 = (n) => (c) => (c + n) & 0xff;
const sub8 = (n) => (c) => (c - n + 256) & 0xff;
const xor8 = (n) => (c) => (c ^ n) & 0xff;
const rotl8 = (n) => (c) => ((c << n) | (c >>> (8 - n))) & 0xff;
function base64UrlEncodeBytes(bytes) {
const std = b64decode(fromBytes(bytes));
return std.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function bytesFromBase64(b64) {
return toBytes(b64encode(b64));
}
const rotr8 = (n) => (c) => ((c >>> n) | (c << (8 - n))) & 0xff;
function generate_vrf(input) {
const schedule0 = [
sub8(223),
rotr8(4),
rotr8(4),
add8(234),
rotr8(7),
rotr8(2),
rotr8(7),
sub8(223),
rotr8(7),
rotr8(6),
];
const schedule1 = [
add8(19),
rotr8(7),
add8(19),
rotr8(6),
add8(19),
rotr8(1),
add8(19),
rotr8(6),
rotr8(7),
rotr8(4),
];
const schedule2 = [
sub8(223),
rotr8(1),
add8(19),
sub8(223),
rotl8(2),
sub8(223),
add8(19),
rotl8(1),
rotl8(2),
rotl8(1),
];
const schedule3 = [
add8(19),
rotl8(1),
rotl8(1),
rotr8(1),
add8(234),
rotl8(1),
sub8(223),
rotl8(6),
rotl8(4),
rotl8(1),
];
const schedule4 = [
rotr8(1),
rotl8(1),
rotl8(6),
rotr8(1),
rotl8(2),
rotr8(4),
rotl8(1),
rotl8(1),
sub8(223),
rotl8(2),
];
const CONST = {
"rc4Keys": [
"FgxyJUQDPUGSzwbAq/ToWn4/e8jYzvabE+dLMb1XU1o=",
"CQx3CLwswJAnM1VxOqX+y+f3eUns03ulxv8Z+0gUyik=",
"fAS+otFLkKsKAJzu3yU+rGOlbbFVq+u+LaS6+s1eCJs=",
"Oy45fQVK9kq9019+VysXVlz1F9S1YwYKgXyzGlZrijo=",
"aoDIdXezm2l3HrcnQdkPJTDT8+W6mcl2/02ewBHfPzg=",
],
"seeds32": [
"yH6MXnMEcDVWO/9a6P9W92BAh1eRLVFxFlWTHUqQ474=",
"RK7y4dZ0azs9Uqz+bbFB46Bx2K9EHg74ndxknY9uknA=",
"rqr9HeTQOg8TlFiIGZpJaxcvAaKHwMwrkqojJCpcvoc=",
"/4GPpmZXYpn5RpkP7FC/dt8SXz7W30nUZTe8wb+3xmU=",
"wsSGSBXKWA9q1oDJpjtJddVxH+evCfL5SO9HZnUDFU8=",
],
"prefixKeys": [
"l9PavRg=",
"Ml2v7ag1Jg==",
"i/Va0UxrbMo=",
"WFjKAHGEkQM=",
"5Rr27rWd",
],
};
// Stage 0: normalize to URI-encoded bytes
let bytes = toBytes(encodeURIComponent(input));
// RC4
bytes = rc4Bytes(b64encode(CONST.rc4Keys[0]), bytes);
const prefixKey0 = bytesFromBase64(CONST.prefixKeys[0]);
bytes = transform(
bytes,
bytesFromBase64(CONST.seeds32[0]),
prefixKey0,
prefixKey0.length,
schedule0
);
bytes = rc4Bytes(b64encode(CONST.rc4Keys[1]), bytes);
const prefixKey1 = bytesFromBase64(CONST.prefixKeys[1]);
bytes = transform(
bytes,
bytesFromBase64(CONST.seeds32[1]),
prefixKey1,
prefixKey1.length,
schedule1
);
bytes = rc4Bytes(b64encode(CONST.rc4Keys[2]), bytes);
const prefixKey2 = bytesFromBase64(CONST.prefixKeys[2]);
bytes = transform(
bytes,
bytesFromBase64(CONST.seeds32[2]),
prefixKey2,
prefixKey2.length,
schedule2
);
bytes = rc4Bytes(b64encode(CONST.rc4Keys[3]), bytes);
const prefixKey3 = bytesFromBase64(CONST.prefixKeys[3]);
bytes = transform(
bytes,
bytesFromBase64(CONST.seeds32[3]),
prefixKey3,
prefixKey3.length,
schedule3
);
bytes = rc4Bytes(b64encode(CONST.rc4Keys[4]), bytes);
const prefixKey4 = bytesFromBase64(CONST.prefixKeys[4]);
bytes = transform(
bytes,
bytesFromBase64(CONST.seeds32[4]),
prefixKey4,
prefixKey4.length,
schedule4
);
return base64UrlEncodeBytes(bytes);
}