all extensions to new format :P

This commit is contained in:
2025-12-15 19:40:07 +01:00
parent 9986b64ace
commit 9fe48f93fe
19 changed files with 1725 additions and 1375 deletions

View File

@@ -1,59 +1,23 @@
class wattpad {
constructor(fetchPath, cheerioPath, browser) {
this.browser = browser;
this.fetch = require(fetchPath);
this.cheerio = require(cheerioPath);
constructor() {
this.baseUrl = "https://wattpad.com";
this.type = "book-board";
this.mediaType = "ln";
}
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
};
}
async search(queryObj) {
const query = queryObj.query?.trim() || "";
const limit = 15;
const offset = (page - 1) * limit;
const offset = 0;
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 url =
`${this.baseUrl}/v4/search/stories?` +
`query=${encodeURIComponent(query)}` +
`&limit=${limit}&offset=${offset}&mature=false`;
const res = await this.fetch(url);
const json = await res.json();
const json = await fetch(url).then(r => r.json());
const results = json.stories.map(n => ({
return json.stories.map(n => ({
id: n.id,
title: n.title,
image: n.cover,
@@ -61,104 +25,118 @@ class wattpad {
tags: n.tags,
type: "book"
}));
}
const totalPages = Math.ceil(json.total / limit);
const hasNextPage = page < totalPages;
async getMetadata(id) {
const html = await fetch(`${this.baseUrl}/story/${id}`).then(r => r.text());
const $ = this.cheerio.load(html);
const script = $('script')
.map((_, el) => $(el).html())
.get()
.find(t => t?.includes('window.__remixContext'));
if (!script) return null;
const jsonText = script.match(/window\.__remixContext\s*=\s*({[\s\S]*?});/)?.[1];
if (!jsonText) return null;
let ctx;
try {
ctx = JSON.parse(jsonText);
} catch {
return null;
}
const route = ctx?.state?.loaderData?.["routes/story.$storyid"];
const story = route?.story;
const meta = route?.meta;
if (!story) return null;
return {
results,
hasNextPage,
page
id: story.id,
title: story.title,
format: "Novel",
score: story.voteCount ?? null,
genres: story.tags || [],
status: story.completed ? "Completed" : "Ongoing",
published: story.createDate?.split("T")[0] || "???",
summary: story.description || meta?.description || "",
chapters: story.numParts || story.parts?.length || 1,
image: story.cover || meta?.image || "",
language: story.language?.name?.toLowerCase() || "unknown",
};
}
async findChapters(bookId) {
const res = await this.fetch(`${this.baseUrl}/story/${bookId}`);
const html = await res.text();
const html = await fetch(`${this.baseUrl}/story/${bookId}`).then(r => r.text());
const $ = this.cheerio.load(html);
const chapters = [];
const script = $('script')
.map((_, el) => $(el).html())
.get()
.find(t => t?.includes('window.__remixContext'));
$('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;
if (!script) return [];
const titleText = $(el).find('div.wpYp-').text().trim();
const jsonText = script.match(/window\.__remixContext\s*=\s*({[\s\S]*?});/)?.[1];
if (!jsonText) return [];
let chapterNumber = i + 1;
let title = titleText;
let ctx;
try {
ctx = JSON.parse(jsonText);
} catch {
return [];
}
const match2 = titleText.match(/^(\d+)\s*-\s*(.*)$/);
if (match2) {
chapterNumber = parseInt(match[1], 10);
title = match2[2].trim();
}
const story = ctx?.state?.loaderData?.["routes/story.$storyid"]?.story;
if (!story?.parts) return [];
chapters.push({
id: id,
title,
chapter: chapterNumber,
language: "en"
});
});
return { chapters };
return story.parts.map((p, i) => ({
id: String(p.id),
title: p.title || `Chapter ${i + 1}`,
number: i + 1,
language: story.language?.name?.toLowerCase() || "en",
index: i
}));
}
async findChapterPages(chapterId) {
const ampUrl = `https://www.wattpad.com/amp/${chapterId}`;
const res = await this.fetch(ampUrl);
const html = await res.text();
const html = await fetch(`https://www.wattpad.com/amp/${chapterId}`).then(r => r.text());
const $ = this.cheerio.load(html);
const title = $("#amp-reading h2").first().text().trim();
const titleHtml = title ? `<h1>${title}</h1>\n\n` : "";
const title = $('h2').first().text().trim();
const paragraphsHtml = [];
const container = $('.story-body-type');
if (!container.length) return "";
$(".story-body-type p").each((i, el) => {
const p = $(el);
container.find('[data-media-type="image"]').remove();
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();
const parts = [];
if (h.length > 0) {
paragraphsHtml.push(`<p>${h}</p>`);
}
return;
}
container.find('p').each((_, el) => {
const text = $(el)
.html()
.replace(/\u00A0/g, " ")
.replace(/[ \t]+/g, " ")
.trim();
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}">`
);
}
}
if (text) parts.push(`<p>${text}</p>`);
});
const cleanHTML = titleHtml + paragraphsHtml.join("\n\n");
return [
{
type: "text",
content: cleanHTML.trim(),
index: 0
}
];
container.find('amp-img').each((_, el) => {
const src = $(el).attr('src');
const w = $(el).attr('width');
const h = $(el).attr('height');
if (src) parts.push(`<img src="${src}" width="${w}" height="${h}">`);
});
return (
(title ? `<h1>${title}</h1>\n\n` : "") +
parts.join("\n\n")
).trim();
}
}
module.exports = { novelupdates: wattpad };
module.exports = wattpad;