new extensions

This commit is contained in:
2025-11-24 12:36:50 +01:00
parent 357ed76ddb
commit 9986b64ace
2 changed files with 289 additions and 0 deletions

125
asmhentai.js Normal file
View File

@@ -0,0 +1,125 @@
class asmhentai {
constructor(fetchPath, cheerioPath, browser) {
this.baseUrl = "https://asmhentai.com/";
this.fetch = require(fetchPath);
this.cheerio = require(cheerioPath);
this.browser = browser;
this.type = "book-board";
}
async fetchSearchResult(query = "", page = 1) {
const q = query.trim().replace(/\s+/g, "+");
const url = q ? `${this.baseUrl}/search/?q=${q}&page=${page}` : `${this.baseUrl}/?q=&page=${page}`;
const res = await this.fetch(url);
const html = await res.text();
const $ = this.cheerio.load(html);
const items = $(".ov_item .preview_item");
const results = [];
items.each((_, el) => {
const $el = $(el);
const href = $el.find(".image a").attr("href") || "";
const id = href.match(/\d+/)?.[0] || null;
const img = $el.find(".image img");
const raw = img.attr("data-src") || img.attr("src") || "";
let image = raw.startsWith("//") ? "https:" + raw : raw;
const sampleImageUrl = image.replace("thumb", "cover");
image = image.replace(/[^\/]+$/, "1.jpg");
const title = ($el.find(".cpt h2.caption").text() || "").trim();
const tagsRaw = $el.attr("data-tags") || "";
const tags = tagsRaw.split(" ").filter(Boolean);
results.push({
id,
image,
sampleImageUrl,
title,
tags,
type: "book"
});
});
const hasNextPage = $('ul.pagination a.page-link').filter((_, el) => $(el).text().trim().toLowerCase() === "next").length > 0;
return {
results,
hasNextPage,
page
};
}
async findChapters(mangaId) {
const res = await this.fetch(`${this.baseUrl}/g/${mangaId}/`);
const html = await res.text();
const $ = this.cheerio.load(html);
const title = $(".right .info h1").first().text().trim() || "";
let cover = $(".cover img").attr("data-src") || $(".cover img").attr("src") || "";
if (cover.startsWith("//")) cover = "https:" + cover;
const firstThumb = $('.gallery a img').first();
let t = firstThumb.attr("data-src") || "";
if (t.startsWith("//")) t = "https:" + t;
// ex: https://images.asmhentai.com/017/598614/8t.jpg
const baseMatch = t.match(/https:\/\/[^\/]+\/\d+\/\d+\//);
const basePath = baseMatch ? baseMatch[0] : null;
const pagesText = $(".pages h3").text(); // "Pages: 39"
const pagesMatch = pagesText.match(/(\d+)/);
const pages = pagesMatch ? parseInt(pagesMatch[1]) : 0;
let ext = "jpg";
const extMatch = t.match(/\.(jpg|png|jpeg|gif)/i);
if (extMatch) ext = extMatch[1];
let language = "unknown";
const langFlag = $('.info a[href^="/language/"] img').attr("src") || "";
if (langFlag.includes("en")) language = "english";
if (langFlag.includes("jp")) language = "japanese";
const encodedChapterId = Buffer.from(
JSON.stringify({
base: basePath,
pages,
ext
})
).toString("base64");
return {
chapters: [
{
id: encodedChapterId,
title,
chapter: 1,
index: 0,
language
}
],
cover
};
}
async findChapterPages(chapterId) {
const decoded = JSON.parse(
Buffer.from(chapterId, "base64").toString("utf8")
);
const { base, pages, ext } = decoded;
return Array.from({ length: pages }, (_, i) => ({
url: `${base}${i + 1}.${ext}`,
index: i,
}));
}
}
module.exports = { asmhentai };

164
wattpad.js Normal file
View File

@@ -0,0 +1,164 @@
class wattpad {
constructor(fetchPath, cheerioPath, browser) {
this.browser = browser;
this.fetch = require(fetchPath);
this.cheerio = require(cheerioPath);
this.baseUrl = "https://wattpad.com";
this.type = "book-board";
}
async fetchSearchResult(query = "", page = 1) {
if (!query || query.trim() === "") {
const res = await this.fetch("https://www.wattpad.com/");
const html = await res.text();
const $ = this.cheerio.load(html);
const results = [];
$("li.splide__slide").each((_, el) => {
const li = $(el);
const link = li.find("a[data-testid='coverLink']").attr("href") || "";
const img = li.find("img[data-testid='image']").attr("src") || "";
const title = li.find("img[data-testid='image']").attr("alt") || "";
if (link && img) {
const id = link.split("/story/")[1]?.split(/[^0-9]/)[0] || null;
if (id) {
results.push({
id,
title,
image: img,
sampleImageUrl: img,
tags: [],
type: "book"
});
}
}
});
return {
results,
hasNextPage: false,
page: 1
};
}
const limit = 15;
const offset = (page - 1) * limit;
const url = `${this.baseUrl}/v4/search/stories?query=${query}&fields=stories%28id%2Ctitle%2CvoteCount%2CreadCount%2CcommentCount%2Cdescription%2Ccompleted%2Cmature%2Ccover%2Curl%2CisPaywalled%2CpaidModel%2Clength%2Clanguage%28id%29%2Cuser%28name%29%2CnumParts%2ClastPublishedPart%28createDate%29%2Cpromoted%2Csponsor%28name%2Cavatar%29%2Ctags%2Ctracking%28clickUrl%2CimpressionUrl%2CthirdParty%28impressionUrls%2CclickUrls%29%29%2Ccontest%28endDate%2CctaLabel%2CctaURL%29%29%2Ctotal%2Ctags%2Cnexturl&limit=${limit}&mature=false&offset=${offset}`;
const res = await this.fetch(url);
const json = await res.json();
const results = json.stories.map(n => ({
id: n.id,
title: n.title,
image: n.cover,
sampleImageUrl: n.cover,
tags: n.tags,
type: "book"
}));
const totalPages = Math.ceil(json.total / limit);
const hasNextPage = page < totalPages;
return {
results,
hasNextPage,
page
};
}
async findChapters(bookId) {
const res = await this.fetch(`${this.baseUrl}/story/${bookId}`);
const html = await res.text();
const $ = this.cheerio.load(html);
const chapters = [];
$('div.Y26Ib ul[aria-label="story-parts"] li a').each((i, el) => {
const href = $(el).attr("href") || "";
const match = href.match(/wattpad\.com\/(\d+)/);
const id = match ? match[1] : null;
const titleText = $(el).find('div.wpYp-').text().trim();
let chapterNumber = i + 1;
let title = titleText;
const match2 = titleText.match(/^(\d+)\s*-\s*(.*)$/);
if (match2) {
chapterNumber = parseInt(match[1], 10);
title = match2[2].trim();
}
chapters.push({
id: id,
title,
chapter: chapterNumber,
language: "en"
});
});
return { chapters };
}
async findChapterPages(chapterId) {
const ampUrl = `https://www.wattpad.com/amp/${chapterId}`;
const res = await this.fetch(ampUrl);
const html = await res.text();
const $ = this.cheerio.load(html);
const title = $("#amp-reading h2").first().text().trim();
const titleHtml = title ? `<h1>${title}</h1>\n\n` : "";
const paragraphsHtml = [];
$(".story-body-type p").each((i, el) => {
const p = $(el);
if (p.attr("data-media-type") !== "image") {
let h = p.html() || "";
h = h
.replace(/<br\s*\/?>/gi, "<br>")
.replace(/\u00A0/g, " ")
.replace(/[ \t]+/g, " ")
.trim();
if (h.length > 0) {
paragraphsHtml.push(`<p>${h}</p>`);
}
return;
}
const ampImg = p.find("amp-img").first();
if (ampImg.length) {
const src = ampImg.attr("src") || "";
const width = ampImg.attr("width") || "";
const height = ampImg.attr("height") || "";
if (src) {
paragraphsHtml.push(
`<img src="${src}" width="${width}" height="${height}">`
);
}
}
});
const cleanHTML = titleHtml + paragraphsHtml.join("\n\n");
return [
{
type: "text",
content: cleanHTML.trim(),
index: 0
}
];
}
}
module.exports = { novelupdates: wattpad };