Files
WaifuBoard-Extensions/mangadex.js
2025-11-23 23:23:26 +01:00

180 lines
6.9 KiB
JavaScript

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 };