Files
WaifuBoard-Extensions/book/nhentai.js

140 lines
4.2 KiB
JavaScript

class NHentai {
constructor() {
this.baseUrl = "https://nhentai.net";
this.type = "book-board";
this.version = "1.1";
this.mediaType = "manga";
}
shortenTitle(title) {
return title.replace(/(\[[^]]*]|[({][^)}]*[)}])/g, "").trim();
}
parseId(str) {
return str.replace(/\D/g, "");
}
extractJson(scriptText) {
const m = scriptText.match(/JSON\.parse\("([\s\S]*?)"\)/);
if (!m) throw new Error("JSON.parse no encontrado");
const unicodeFixed = m[1].replace(
/\\u([0-9A-Fa-f]{4})/g,
(_, h) => String.fromCharCode(parseInt(h, 16))
);
return JSON.parse(unicodeFixed);
}
async search({ query = "", page = 1 }) {
if (query.startsWith("id:") || (!isNaN(query) && query.length <= 7)) {
return [await this.getMetadata(this.parseId(query))];
}
const url = `${this.baseUrl}/search/?q=${encodeURIComponent(query)}&page=${page}`;
const { result } = await this.scrape(
url,
page =>
page.evaluate(() => document.documentElement.innerHTML),
{ waitSelector: ".gallery" }
);
const $ = this.cheerio.load(result);
return $(".gallery").map((_, el) => ({
id: this.parseId($(el).find("a").attr("href")),
image: $(el).find("img").attr("data-src") || $(el).find("img").attr("src"),
title: this.shortenTitle($(el).find(".caption").text()),
type: "book"
})).get();
}
async getMetadata(id) {
const url = `${this.baseUrl}/g/${id}/`;
const { result } = await this.scrape(
url,
page =>
page.evaluate(() => {
const html = document.documentElement.innerHTML;
const script = [...document.querySelectorAll("script")]
.find(s =>
s.textContent.includes("JSON.parse") &&
!s.textContent.includes("media_server") &&
!s.textContent.includes("avatar_url")
)?.textContent || null;
const thumbMatch = html.match(/thumb_cdn_urls:\s*(\[[^\]]*])/);
const thumbCdns = thumbMatch ? JSON.parse(thumbMatch[1]) : [];
return { script, thumbCdns };
})
);
if (!result?.script) {
throw new Error("Script de datos no encontrado");
}
const data = this.extractJson(result.script);
const cdn = result.thumbCdns[0] || "t3.nhentai.net";
return {
id: id.toString(),
title: data.title.pretty || data.title.english,
format: "MANGA",
status: "completed",
genres: data.tags
.filter(t => t.type === "tag")
.map(t => t.name),
published: new Date(data.upload_date * 1000).toLocaleDateString(),
summary: `Pages: ${data.images.pages.length}\nFavorites: ${data.num_favorites}`,
chapters: 1,
image: `https://${cdn}/galleries/${data.media_id}/cover.webp`
};
}
async findChapters(mangaId) {
return [{
id: mangaId.toString(),
title: "Chapter",
number: 1,
index: 0
}];
}
async findChapterPages(chapterId) {
const url = `${this.baseUrl}/g/${chapterId}/`;
const { result } = await this.scrape(
url,
page =>
page.evaluate(() => {
const html = document.documentElement.innerHTML;
const cdnMatch = html.match(/image_cdn_urls:\s*(\[[^\]]*])/);
const s = [...document.querySelectorAll("script")]
.find(x =>
x.textContent.includes("JSON.parse") &&
!x.textContent.includes("media_server") &&
!x.textContent.includes("avatar_url")
);
return {
script: s?.textContent || null,
cdns: cdnMatch ? JSON.parse(cdnMatch[1]) : ["i.nhentai.net"]
};
})
);
if (!result?.script) throw new Error("Datos no encontrados");
const data = this.extractJson(result.script);
const cdn = result.cdns[0];
return data.images.pages.map((p, i) => {
const ext = p.t === "j" ? "jpg" : p.t === "p" ? "png" : "webp";
return {
index: i,
url: `https://${cdn}/galleries/${data.media_id}/${i + 1}.${ext}`
};
});
}
}
module.exports = NHentai;