class MangaDex { constructor() { this.baseUrl = "https://mangadex.org"; this.apiUrl = "https://api.mangadex.org"; this.type = "book-board"; this.mediaType = "manga"; this.version = "1.0" } getHeaders() { return { 'User-Agent': 'MangaDex-Client-Adapter/1.0', 'Content-Type': 'application/json' }; } async search(queryObj) { const query = queryObj.query?.trim() || ""; const limit = 25; const offset = (1 - 1) * limit; const url = `${this.apiUrl}/manga?title=${encodeURIComponent(query)}&limit=${limit}&offset=${offset}&includes[]=cover_art&contentRating[]=safe&contentRating[]=suggestive&availableTranslatedLanguage[]=en`; try { const response = await fetch(url, { headers: this.getHeaders() }); if (!response.ok) { console.error(`MangaDex API Error: ${response.statusText}`); return []; } const json = await response.json(); if (!json || !Array.isArray(json.data)) { return []; } return json.data.map(manga => { const attributes = manga.attributes; const titleObject = attributes.title || {}; const title = titleObject.en || Object.values(titleObject)[0] || 'Unknown Title'; const coverRelationship = manga.relationships?.find(rel => rel.type === 'cover_art'); const coverFileName = coverRelationship?.attributes?.fileName; const coverUrl = coverFileName ? `https://uploads.mangadex.org/covers/${manga.id}/${coverFileName}.256.jpg` : ''; return { id: manga.id, image: coverUrl, title: title, rating: null, type: 'book' }; }); } catch (e) { console.error("Error during MangaDex search:", e); return []; } } async getMetadata(id) { try { const res = await fetch(`https://api.mangadex.org/manga/${id}?includes[]=cover_art`); if (!res.ok) throw new Error("MangaDex API error"); const json = await res.json(); const manga = json.data; const attr = manga.attributes; const title = attr.title?.en || Object.values(attr.title || {})[0] || ""; const summary = attr.description?.en || Object.values(attr.description || {})[0] || ""; const genres = manga.relationships ?.filter(r => r.type === "tag") ?.map(r => r.attributes?.name?.en || Object.values(r.attributes?.name || {})[0] ) ?.filter(Boolean) || []; const coverRel = manga.relationships.find(r => r.type === "cover_art"); const coverFile = coverRel?.attributes?.fileName; const image = coverFile ? `https://uploads.mangadex.org/covers/${id}/${coverFile}.512.jpg` : ""; const score100 = 0; const statusMap = { ongoing: "Ongoing", completed: "Completed", hiatus: "Hiatus", cancelled: "Cancelled" }; return { id, title, format: "Manga", score: score100, genres, status: statusMap[attr.status] || "", published: attr.year ? String(attr.year) : "???", summary, chapters: attr.lastChapter ? Number(attr.lastChapter) || 0 : 0, image }; } catch (e) { console.error("MangaDex getMetadata error:", e); return { id, title: "", format: "Manga", score: 0, genres: [], status: "", published: "???", summary: "", chapters: 0, image: "" }; } } async findChapters(mangaId) { if (!mangaId) return []; const url = `${this.apiUrl}/manga/${mangaId}/feed?translatedLanguage[]=en&order[chapter]=asc&limit=500&includes[]=scanlation_group`; try { const response = await fetch(url, { headers: this.getHeaders() }); let chapters = []; if (response.ok) { const json = await response.json(); if (json && Array.isArray(json.data)) { const allChapters = json.data .filter(ch => ch.attributes.chapter && !ch.attributes.externalUrl) .map((ch, index) => ({ id: ch.id, title: ch.attributes.title || `Chapter ${ch.attributes.chapter}`, number: ch.attributes.chapter, index: index, language: ch.attributes.translatedLanguage })); const seenChapters = new Set(); allChapters.forEach(ch => { if (!seenChapters.has(ch.chapter)) { seenChapters.add(ch.chapter); chapters.push(ch); } }); chapters.sort((a, b) => parseFloat(a.chapter) - parseFloat(b.chapter)); } } return chapters; } catch (e) { console.error("Error finding MangaDex chapters:", e); return { chapters: [], cover: null }; } } async findChapterPages(chapterId) { if (!chapterId) return []; const url = `${this.apiUrl}/at-home/server/${chapterId}`; try { const response = await fetch(url, { headers: this.getHeaders() }); if (!response.ok) throw new Error(`Failed to fetch pages: ${response.statusText}`); const json = await response.json(); if (!json || !json.baseUrl || !json.chapter) return []; const baseUrl = json.baseUrl; const chapterHash = json.chapter.hash; const imageFilenames = json.chapter.data; return imageFilenames.map((filename, index) => ({ url: `${baseUrl}/data/${chapterHash}/${filename}`, index: index, headers: { 'Referer': `https://mangadex.org/chapter/${chapterId}` } })); } catch (e) { console.error("Error finding MangaDex pages:", e); return []; } } } module.exports = MangaDex;