class wattpad { constructor() { this.baseUrl = "https://wattpad.com"; this.type = "book-board"; this.mediaType = "ln"; this.version = "1.0" } async search(queryObj) { const query = queryObj.query?.trim() || ""; const limit = 15; const offset = 0; const url = `${this.baseUrl}/v4/search/stories?` + `query=${encodeURIComponent(query)}` + `&limit=${limit}&offset=${offset}&mature=false`; const json = await fetch(url).then(r => r.json()); return json.stories.map(n => ({ id: n.id, title: n.title, image: n.cover, sampleImageUrl: n.cover, tags: n.tags, type: "book" })); } 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 { 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 html = await fetch(`${this.baseUrl}/story/${bookId}`).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 []; const jsonText = script.match(/window\.__remixContext\s*=\s*({[\s\S]*?});/)?.[1]; if (!jsonText) return []; let ctx; try { ctx = JSON.parse(jsonText); } catch { return []; } const story = ctx?.state?.loaderData?.["routes/story.$storyid"]?.story; if (!story?.parts) return []; 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 html = await fetch(`https://www.wattpad.com/amp/${chapterId}`).then(r => r.text()); const $ = this.cheerio.load(html); const title = $('h2').first().text().trim(); const container = $('.story-body-type'); if (!container.length) return ""; container.find('[data-media-type="image"]').remove(); const parts = []; container.find('p').each((_, el) => { const text = $(el) .html() .replace(/\u00A0/g, " ") .replace(/[ \t]+/g, " ") .trim(); if (text) parts.push(`
${text}
`); }); 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(`