updates and new extensions
This commit is contained in:
238
book/mangadex.js
238
book/mangadex.js
@@ -4,7 +4,7 @@ class MangaDex {
|
||||
this.apiUrl = "https://api.mangadex.org";
|
||||
this.type = "book-board";
|
||||
this.mediaType = "manga";
|
||||
this.version = "1.0"
|
||||
this.version = "1.1";
|
||||
}
|
||||
|
||||
getHeaders() {
|
||||
@@ -14,15 +14,226 @@ class MangaDex {
|
||||
};
|
||||
}
|
||||
|
||||
async search(queryObj) {
|
||||
const query = queryObj.query?.trim() || "";
|
||||
getFilters() {
|
||||
return {
|
||||
sort: {
|
||||
label: "Sort By",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "relevance", label: "Relevance" },
|
||||
{ value: "latestUploadedChapter", label: "Latest Upload" },
|
||||
{ value: "followedCount", label: "Most Follows" },
|
||||
{ value: "createdAt", label: "Created At" },
|
||||
{ value: "updatedAt", label: "Updated At" },
|
||||
{ value: "title", label: "Title" },
|
||||
{ value: "year", label: "Year" },
|
||||
{ value: "rating", label: "Rating" }
|
||||
],
|
||||
default: "relevance"
|
||||
},
|
||||
status: {
|
||||
label: "Status",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
{ value: "ongoing", label: "Ongoing" },
|
||||
{ value: "completed", label: "Completed" },
|
||||
{ value: "hiatus", label: "Hiatus" },
|
||||
{ value: "cancelled", label: "Cancelled" }
|
||||
]
|
||||
},
|
||||
demographic: {
|
||||
label: "Demographic",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
{ value: "shounen", label: "Shounen" },
|
||||
{ value: "shoujo", label: "Shoujo" },
|
||||
{ value: "seinen", label: "Seinen" },
|
||||
{ value: "josei", label: "Josei" },
|
||||
{ value: "none", label: "None" }
|
||||
]
|
||||
},
|
||||
content_rating: {
|
||||
label: "Content Rating",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
{ value: "safe", label: "Safe" },
|
||||
{ value: "suggestive", label: "Suggestive" },
|
||||
{ value: "erotica", label: "Erotica" },
|
||||
{ value: "pornographic", label: "Pornographic" }
|
||||
],
|
||||
default: "safe,suggestive"
|
||||
},
|
||||
original_language: {
|
||||
label: "Original Language",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
{ value: "ja", label: "Japanese" },
|
||||
{ value: "zh", label: "Chinese" },
|
||||
{ value: "ko", label: "Korean" }
|
||||
]
|
||||
},
|
||||
tags_mode: {
|
||||
label: "Tags Mode",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "AND", label: "AND (Match All)" },
|
||||
{ value: "OR", label: "OR (Match Any)" }
|
||||
],
|
||||
default: "AND"
|
||||
},
|
||||
tags: {
|
||||
label: "Tags",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
// Genres
|
||||
{ value: "391b0423-d847-456f-aff0-8b0cfc03066b", label: "Action" },
|
||||
{ value: "87cc87cd-a395-47af-b27a-93258283bbc6", label: "Adventure" },
|
||||
{ value: "5920b825-4181-4a17-beeb-9918b0ff7a30", label: "Boys Love" },
|
||||
{ value: "4d32cc48-9f00-4cca-9b5a-a839f0764984", label: "Comedy" },
|
||||
{ value: "5ca48985-9a9d-4bd8-be29-80dc0303db72", label: "Crime" },
|
||||
{ value: "b9af3a63-f058-46de-a9a0-e0c13906197a", label: "Drama" },
|
||||
{ value: "cdc58593-87dd-415e-bbc0-2ec27bf404cc", label: "Fantasy" },
|
||||
{ value: "a3c67850-4684-404e-9b7f-c69850ee5da6", label: "Girls Love" },
|
||||
{ value: "33771934-028e-4cb3-8744-691e866a923e", label: "Historical" },
|
||||
{ value: "cdad7e68-1419-41dd-bdce-27753074a640", label: "Horror" },
|
||||
{ value: "ace04997-f6bd-436e-b261-779182193d3d", label: "Isekai" },
|
||||
{ value: "81c836c9-914a-4eca-981a-560dad663e73", label: "Magical Girls" },
|
||||
{ value: "50880a9d-5440-4732-9afb-8f457127e836", label: "Mecha" },
|
||||
{ value: "c8cbe35b-1b2b-4a3f-9c37-db84c4514856", label: "Medical" },
|
||||
{ value: "ee968100-4191-4968-93d3-f82d72be7e46", label: "Mystery" },
|
||||
{ value: "b1e97889-25b4-4258-b28b-cd7f4d28ea9b", label: "Philosophical" },
|
||||
{ value: "423e2eae-a7a2-4a8b-ac03-a8351462d71d", label: "Romance" },
|
||||
{ value: "256c8bd9-4904-4360-bf4f-508a76d67183", label: "Sci-Fi" },
|
||||
{ value: "e5301a23-ebd9-49dd-a0cb-2add944c7fe9", label: "Slice of Life" },
|
||||
{ value: "69964a64-2f90-4d33-beeb-f3ed2875eb4c", label: "Sports" },
|
||||
{ value: "7064a261-a137-4d3a-8848-2d385de3a99c", label: "Superhero" },
|
||||
{ value: "07251805-a27e-4d59-b488-f0bfbec15168", label: "Thriller" },
|
||||
{ value: "f8f62932-27da-4fe4-8ee1-6779a8c5edba", label: "Tragedy" },
|
||||
{ value: "acc803a4-c95a-4c22-86fc-eb6b582d82a2", label: "Wuxia" },
|
||||
// Themes
|
||||
{ value: "e64f6742-c834-471d-8d72-dd51fc02b835", label: "Aliens" },
|
||||
{ value: "3de8c75d-8ee3-48ff-98ee-e20a65c86451", label: "Animals" },
|
||||
{ value: "ea2bc92d-1c26-4930-9b7c-d5c0dc1b6869", label: "Cooking" },
|
||||
{ value: "9ab53f92-3eed-4e9b-903a-917c86035ee3", label: "Crossdressing" },
|
||||
{ value: "da2d50ca-3018-4cc0-ac7a-6b7d472a29ea", label: "Delinquents" },
|
||||
{ value: "39730448-9a5f-48a2-85b0-a70db87b1233", label: "Demons" },
|
||||
{ value: "2bd2e8d0-f146-434a-9b51-fc9ff2c5fe6a", label: "Genderswap" },
|
||||
{ value: "3bb26d85-09d5-4d2e-880c-c34b974339e9", label: "Ghosts" },
|
||||
{ value: "fad12b5e-68ba-460e-b933-9ae8318f5b65", label: "Gyaru" },
|
||||
{ value: "aafb99c1-7f60-43fa-b75f-fc9502ce29c7", label: "Harem" },
|
||||
{ value: "5bd0e105-4481-44ca-b6e7-7544da56b1a3", label: "Incest" },
|
||||
{ value: "2d1f5d56-a1e5-4d0d-a961-2193588b08ec", label: "Loli" },
|
||||
{ value: "85daba54-a71c-4554-8a28-9901a8b0afad", label: "Mafia" },
|
||||
{ value: "a1f53773-c69a-4ce5-8cab-fffcd90b1565", label: "Magic" },
|
||||
{ value: "799c202e-7daa-44eb-9cf7-8a3c0441531e", label: "Martial Arts" },
|
||||
{ value: "ac72833b-c4e9-4878-b9db-6c8a4a99444a", label: "Military" },
|
||||
{ value: "dd1f77c5-dea9-4e2b-97ae-224af09caf99", label: "Monster Girls" },
|
||||
{ value: "36fd93ea-e8b8-445e-b836-358f02b3d33d", label: "Monsters" },
|
||||
{ value: "f42fbf9e-188a-447b-9fdc-f19dc1e4d685", label: "Music" },
|
||||
{ value: "489dd859-9b61-4c37-af75-5b18e88daafc", label: "Ninja" },
|
||||
{ value: "92d6d951-ca5e-429c-ac78-451071cbf064", label: "Office Workers" },
|
||||
{ value: "df33b754-73a3-4c54-80e6-1a74a8058539", label: "Police" },
|
||||
{ value: "9467335a-1b83-4497-9231-765337a00b96", label: "Post-Apocalyptic" },
|
||||
{ value: "3b60b75c-a2d7-4860-ab56-05f391bb889c", label: "Psychological" },
|
||||
{ value: "0bc90acb-ccc1-44ca-a34a-b9f3a73259d0", label: "Reincarnation" },
|
||||
{ value: "65761a2a-415e-47f3-bef2-a9dababba7a6", label: "Reverse Harem" },
|
||||
{ value: "81183756-1453-4c81-aa9e-f6e1b63be016", label: "Samurai" },
|
||||
{ value: "caaa44eb-cd40-4177-b930-79d3ef2afe87", label: "School Life" },
|
||||
{ value: "ddefd648-5140-4e5f-ba18-4eca4071d19b", label: "Shota" },
|
||||
{ value: "eabc5b4c-6aff-42f3-b657-3e90cbd00b75", label: "Supernatural" },
|
||||
{ value: "5fff9cde-849c-4d78-aab0-0d52b2ee1d25", label: "Survival" },
|
||||
{ value: "292e862b-2d17-4062-90a2-0356caa4ae27", label: "Time Travel" },
|
||||
{ value: "d7d1730f-6eb0-4ba6-9437-602cac38664c", label: "Vampires" },
|
||||
{ value: "9438db5a-7e2a-4ac0-b39e-e0d95a34b8a8", label: "Video Games" },
|
||||
{ value: "d14322ac-4d6f-4e9b-afd9-629d5f4d8a41", label: "Villainess" },
|
||||
{ value: "631ef465-9aba-4afb-b0fc-ea10efe274a8", label: "Zombies" },
|
||||
// Content
|
||||
{ value: "b29d6a3d-1569-4e7a-8caf-7557bc92cd5d", label: "Gore" },
|
||||
{ value: "97893a4c-12af-4dac-b6be-0dffb353568e", label: "Sexual Violence" }
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async search({ query = "", page = 1, filters = {} }) {
|
||||
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`;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const url = new URL(`${this.apiUrl}/manga`);
|
||||
|
||||
// --- 1. Query ---
|
||||
if (query.trim()) {
|
||||
url.searchParams.append("title", query.trim());
|
||||
}
|
||||
|
||||
// --- 2. Parámetros Fijos ---
|
||||
url.searchParams.append("limit", limit.toString());
|
||||
url.searchParams.append("offset", offset.toString());
|
||||
url.searchParams.append("includes[]", "cover_art");
|
||||
url.searchParams.append("availableTranslatedLanguage[]", "en");
|
||||
|
||||
// --- 3. Filtros Dinámicos ---
|
||||
|
||||
// A) Content Rating (Si no se especifica, usa safe+suggestive por defecto)
|
||||
if (filters.content_rating) {
|
||||
const ratings = String(filters.content_rating).split(",");
|
||||
ratings.forEach(r => {
|
||||
if (r.trim()) url.searchParams.append("contentRating[]", r.trim());
|
||||
});
|
||||
} else {
|
||||
// Default behavior if not set
|
||||
url.searchParams.append("contentRating[]", "safe");
|
||||
url.searchParams.append("contentRating[]", "suggestive");
|
||||
}
|
||||
|
||||
// B) Tags (includedTags)
|
||||
if (filters.tags) {
|
||||
const tags = String(filters.tags).split(",");
|
||||
tags.forEach(t => {
|
||||
if (t.trim()) url.searchParams.append("includedTags[]", t.trim());
|
||||
});
|
||||
}
|
||||
|
||||
// C) Tag Inclusion Mode
|
||||
if (filters.tags_mode) {
|
||||
url.searchParams.append("includedTagsMode", filters.tags_mode);
|
||||
}
|
||||
|
||||
// D) Status
|
||||
if (filters.status) {
|
||||
const stats = String(filters.status).split(",");
|
||||
stats.forEach(s => {
|
||||
if (s.trim()) url.searchParams.append("status[]", s.trim());
|
||||
});
|
||||
}
|
||||
|
||||
// E) Demographic
|
||||
if (filters.demographic) {
|
||||
const demos = String(filters.demographic).split(",");
|
||||
demos.forEach(d => {
|
||||
if (d.trim()) url.searchParams.append("publicationDemographic[]", d.trim());
|
||||
});
|
||||
}
|
||||
|
||||
// F) Original Language
|
||||
if (filters.original_language) {
|
||||
const langs = String(filters.original_language).split(",");
|
||||
langs.forEach(l => {
|
||||
if (l.trim()) url.searchParams.append("originalLanguage[]", l.trim());
|
||||
});
|
||||
}
|
||||
|
||||
// G) Sort
|
||||
// MangaDex usa order[KEY]=asc/desc
|
||||
const sortVal = filters.sort || "relevance";
|
||||
const orderDir = (sortVal === "title") ? "asc" : "desc";
|
||||
|
||||
// Si hay búsqueda por texto, relevance es útil, sino latestUploaded
|
||||
url.searchParams.append(`order[${sortVal}]`, orderDir);
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { headers: this.getHeaders() });
|
||||
const response = await fetch(url.toString(), { headers: this.getHeaders() });
|
||||
if (!response.ok) {
|
||||
console.error(`MangaDex API Error: ${response.statusText}`);
|
||||
return [];
|
||||
@@ -45,7 +256,6 @@ class MangaDex {
|
||||
? `https://uploads.mangadex.org/covers/${manga.id}/${coverFileName}.256.jpg`
|
||||
: '';
|
||||
|
||||
|
||||
return {
|
||||
id: manga.id,
|
||||
image: coverUrl,
|
||||
@@ -94,8 +304,6 @@ class MangaDex {
|
||||
? `https://uploads.mangadex.org/covers/${id}/${coverFile}.512.jpg`
|
||||
: "";
|
||||
|
||||
const score100 = 0;
|
||||
|
||||
const statusMap = {
|
||||
ongoing: "Ongoing",
|
||||
completed: "Completed",
|
||||
@@ -107,7 +315,7 @@ class MangaDex {
|
||||
id,
|
||||
title,
|
||||
format: "Manga",
|
||||
score: score100,
|
||||
score: 0,
|
||||
genres,
|
||||
status: statusMap[attr.status] || "",
|
||||
published: attr.year ? String(attr.year) : "???",
|
||||
@@ -141,7 +349,7 @@ class MangaDex {
|
||||
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)) {
|
||||
@@ -154,7 +362,7 @@ class MangaDex {
|
||||
index: index,
|
||||
language: ch.attributes.translatedLanguage
|
||||
}));
|
||||
|
||||
|
||||
const seenChapters = new Set();
|
||||
allChapters.forEach(ch => {
|
||||
if (!seenChapters.has(ch.chapter)) {
|
||||
@@ -170,7 +378,7 @@ class MangaDex {
|
||||
return chapters;
|
||||
} catch (e) {
|
||||
console.error("Error finding MangaDex chapters:", e);
|
||||
return { chapters: [], cover: null };
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +396,7 @@ class MangaDex {
|
||||
|
||||
const baseUrl = json.baseUrl;
|
||||
const chapterHash = json.chapter.hash;
|
||||
const imageFilenames = json.chapter.data;
|
||||
const imageFilenames = json.chapter.data;
|
||||
|
||||
return imageFilenames.map((filename, index) => ({
|
||||
url: `${baseUrl}/data/${chapterHash}/${filename}`,
|
||||
|
||||
Reference in New Issue
Block a user