class MangaDex { constructor(fetchPath, cheerioPath, browser) { this.fetchPath = fetchPath; this.browser = browser; this.baseUrl = "https://mangadex.org"; this.apiUrl = "https://api.mangadex.org"; this.type = "book-board"; } getHeaders() { return { 'User-Agent': 'MangaDex-Client-Adapter/1.0', 'Content-Type': 'application/json' }; } async _fetch(url, options = {}) { if (typeof fetch === 'function') { return fetch(url, options); } const nodeFetch = require(this.fetchPath); return nodeFetch(url, options); } async fetchSearchResult(query = "", page = 1) { const limit = 25; const offset = (page - 1) * limit; let url; if (!query || query.trim() === "") { url = `${this.apiUrl}/manga?limit=${limit}&offset=${offset}&includes[]=cover_art&contentRating[]=safe&contentRating[]=suggestive&availableTranslatedLanguage[]=en&order[followedCount]=desc`; } else { url = `${this.apiUrl}/manga?title=${encodeURIComponent(query)}&limit=${limit}&offset=${offset}&includes[]=cover_art&contentRating[]=safe&contentRating[]=suggestive&availableTranslatedLanguage[]=en`; } try { const response = await this._fetch(url, { headers: this.getHeaders() }); if (!response.ok) { console.error(`MangaDex API Error: ${response.statusText}`); return { results: [], hasNextPage: false, page }; } const json = await response.json(); if (!json || !Array.isArray(json.data)) { return { results: [], hasNextPage: false, page }; } const results = 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` : ''; const fullCoverUrl = coverFileName ? `https://uploads.mangadex.org/covers/${manga.id}/${coverFileName}` : ''; const tags = attributes.tags ? attributes.tags.map(t => t.attributes.name.en) : []; return { id: manga.id, image: coverUrl, sampleImageUrl: fullCoverUrl, title: title, tags: tags, type: 'book' }; }); const total = json.total || 0; const hasNextPage = (offset + limit) < total; return { results, hasNextPage, page }; } catch (e) { console.error("Error during MangaDex search:", e); return { results: [], hasNextPage: false, error: e.message }; } } 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 this._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}`, chapter: 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)); } } let highResCover = null; try { const mangaRes = await this._fetch(`${this.apiUrl}/manga/${mangaId}?includes[]=cover_art`); if (mangaRes.ok) { const mangaJson = await mangaRes.json(); const coverRel = mangaJson.data.relationships.find(r => r.type === 'cover_art'); if(coverRel && coverRel.attributes && coverRel.attributes.fileName) { highResCover = `https://uploads.mangadex.org/covers/${mangaId}/${coverRel.attributes.fileName}`; } } } catch(e) { } return { chapters: chapters, cover: highResCover }; } 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 this._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 };