updates and new extensions
This commit is contained in:
@@ -2,47 +2,162 @@ class MangaPill {
|
||||
constructor() {
|
||||
this.baseUrl = "https://mangapill.com";
|
||||
this.type = "book-board";
|
||||
this.version = "1.0";
|
||||
this.version = "1.1";
|
||||
this.mediaType = "manga";
|
||||
}
|
||||
|
||||
async fetch(url) {
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0",
|
||||
Referer: this.baseUrl,
|
||||
getFilters() {
|
||||
return {
|
||||
type: {
|
||||
label: "Type",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "", label: "All" },
|
||||
{ value: "manga", label: "Manga" },
|
||||
{ value: "novel", label: "Novel" },
|
||||
{ value: "one-shot", label: "One-Shot" },
|
||||
{ value: "doujinshi", label: "Doujinshi" },
|
||||
{ value: "manhwa", label: "Manhwa" },
|
||||
{ value: "manhua", label: "Manhua" },
|
||||
{ value: "oel", label: "OEL" }
|
||||
]
|
||||
},
|
||||
});
|
||||
status: {
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
{ value: "", label: "All" },
|
||||
{ value: "publishing", label: "Publishing" },
|
||||
{ value: "finished", label: "Finished" },
|
||||
{ value: "on hiatus", label: "On Hiatus" },
|
||||
{ value: "discontinued", label: "Discontinued" },
|
||||
{ value: "not yet published", label: "Not Yet Published" }
|
||||
]
|
||||
},
|
||||
genre: {
|
||||
label: "Genres",
|
||||
type: "multiselect",
|
||||
options: [
|
||||
{ value: "Action", label: "Action" },
|
||||
{ value: "Adventure", label: "Adventure" },
|
||||
{ value: "Cars", label: "Cars" },
|
||||
{ value: "Comedy", label: "Comedy" },
|
||||
{ value: "Dementia", label: "Dementia" },
|
||||
{ value: "Demons", label: "Demons" },
|
||||
{ value: "Drama", label: "Drama" },
|
||||
{ value: "Ecchi", label: "Ecchi" },
|
||||
{ value: "Fantasy", label: "Fantasy" },
|
||||
{ value: "Game", label: "Game" },
|
||||
{ value: "Harem", label: "Harem" },
|
||||
{ value: "Hentai", label: "Hentai" },
|
||||
{ value: "Historical", label: "Historical" },
|
||||
{ value: "Horror", label: "Horror" },
|
||||
{ value: "Josei", label: "Josei" },
|
||||
{ value: "Kids", label: "Kids" },
|
||||
{ value: "Magic", label: "Magic" },
|
||||
{ value: "Martial Arts", label: "Martial Arts" },
|
||||
{ value: "Mecha", label: "Mecha" },
|
||||
{ value: "Military", label: "Military" },
|
||||
{ value: "Music", label: "Music" },
|
||||
{ value: "Mystery", label: "Mystery" },
|
||||
{ value: "Parody", label: "Parody" },
|
||||
{ value: "Police", label: "Police" },
|
||||
{ value: "Psychological", label: "Psychological" },
|
||||
{ value: "Romance", label: "Romance" },
|
||||
{ value: "Samurai", label: "Samurai" },
|
||||
{ value: "School", label: "School" },
|
||||
{ value: "Sci-Fi", label: "Sci-Fi" },
|
||||
{ value: "Seinen", label: "Seinen" },
|
||||
{ value: "Shoujo", label: "Shoujo" },
|
||||
{ value: "Shoujo Ai", label: "Shoujo Ai" },
|
||||
{ value: "Shounen", label: "Shounen" },
|
||||
{ value: "Shounen Ai", label: "Shounen Ai" },
|
||||
{ value: "Slice of Life", label: "Slice of Life" },
|
||||
{ value: "Space", label: "Space" },
|
||||
{ value: "Sports", label: "Sports" },
|
||||
{ value: "Super Power", label: "Super Power" },
|
||||
{ value: "Supernatural", label: "Supernatural" },
|
||||
{ value: "Thriller", label: "Thriller" },
|
||||
{ value: "Vampire", label: "Vampire" },
|
||||
{ value: "Yaoi", label: "Yaoi" },
|
||||
{ value: "Yuri", label: "Yuri" }
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async search(queryObj) {
|
||||
const q = queryObj.query || "";
|
||||
const res = await this.fetch(
|
||||
`${this.baseUrl}/search?q=${encodeURIComponent(q)}`
|
||||
);
|
||||
get headers() {
|
||||
return {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Referer": this.baseUrl + "/"
|
||||
};
|
||||
}
|
||||
|
||||
async search({ query, page = 1, filters }) {
|
||||
const url = new URL(`${this.baseUrl}/search`);
|
||||
|
||||
if (query) url.searchParams.set("q", query.trim());
|
||||
url.searchParams.set("page", page.toString());
|
||||
|
||||
if (filters) {
|
||||
if (filters.type) url.searchParams.set("type", filters.type);
|
||||
if (filters.status) url.searchParams.set("status", filters.status);
|
||||
|
||||
if (filters.genre) {
|
||||
// MangaPill espera ?genre=Action&genre=Comedy
|
||||
const genres = String(filters.genre).split(',');
|
||||
genres.forEach(g => {
|
||||
if (g.trim()) url.searchParams.append("genre", g.trim());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch(url.toString(), { headers: this.headers });
|
||||
if (!res.ok) throw new Error(`Search failed: ${res.status}`);
|
||||
|
||||
const html = await res.text();
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
const results = [];
|
||||
|
||||
$("div.container div.my-3.justify-end > div").each((_, el) => {
|
||||
const link = $(el).find("a").attr("href");
|
||||
if (!link) return;
|
||||
// Selector actualizado basado en la extensión de Kotlin (.grid > div:not([class]))
|
||||
// Buscamos dentro del grid de resultados
|
||||
$(".grid > div:not([class]), div.container div.my-3.justify-end > div").each((_, el) => {
|
||||
const a = $(el).find("a").first();
|
||||
const href = a.attr("href");
|
||||
if (!href) return;
|
||||
|
||||
const id = link.split("/manga/")[1].replace(/\//g, "$");
|
||||
const title = $(el).find("div > a > div.mt-3").text().trim();
|
||||
const image = $(el).find("a img").attr("data-src") || null;
|
||||
// Extraer ID (manga/123/title -> 123$title)
|
||||
const parts = href.split("/manga/");
|
||||
if (parts.length < 2) return;
|
||||
const id = parts[1].replace(/\//g, "$");
|
||||
|
||||
// Título: A veces es un div hermano, a veces dentro del anchor
|
||||
let title = $(el).find("div > a > div").text().trim(); // Selector antiguo
|
||||
if (!title) title = $(el).find("a:not(:first-child) > div").text().trim(); // Selector nuevo
|
||||
if (!title) title = $(el).find(".font-bold, div[class*='font-bold']").text().trim(); // Fallback
|
||||
|
||||
const img = $(el).find("img").attr("data-src") || $(el).find("img").attr("src");
|
||||
|
||||
results.push({
|
||||
id,
|
||||
title,
|
||||
image,
|
||||
rating: null,
|
||||
type: "book",
|
||||
image: img || "",
|
||||
type: "book"
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
// Eliminar duplicados si el selector doble atrapó cosas repetidas
|
||||
const uniqueResults = [];
|
||||
const seen = new Set();
|
||||
for (const r of results) {
|
||||
if (!seen.has(r.id)) {
|
||||
seen.add(r.id);
|
||||
uniqueResults.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueResults;
|
||||
}
|
||||
|
||||
async getMetadata(id) {
|
||||
@@ -52,83 +167,55 @@ class MangaPill {
|
||||
redirect: "manual",
|
||||
});
|
||||
|
||||
// follow redirect manually
|
||||
if (res.status === 301 || res.status === 302) {
|
||||
const loc = res.headers.get("location");
|
||||
if (loc) {
|
||||
res = await fetch(`${this.baseUrl}${loc}`, {
|
||||
headers: this.headers,
|
||||
});
|
||||
res = await fetch(`${this.baseUrl}${loc}`, { headers: this.headers });
|
||||
}
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
return {
|
||||
id,
|
||||
title: "",
|
||||
format: "MANGA",
|
||||
score: 0,
|
||||
genres: "",
|
||||
status: "unknown",
|
||||
published: "",
|
||||
summary: "",
|
||||
chapters: "???",
|
||||
image: null,
|
||||
};
|
||||
}
|
||||
if (!res.ok) throw new Error("Failed to fetch metadata");
|
||||
|
||||
const html = await res.text();
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
const title = $("h1.font-bold").first().text().trim();
|
||||
const summary = $("div.mb-3 p.text-sm").first().text().trim() || "";
|
||||
|
||||
const summary =
|
||||
$("div.mb-3 p.text-sm").first().text().trim() || "";
|
||||
|
||||
const status =
|
||||
$("label:contains('Status')")
|
||||
.next("div")
|
||||
.text()
|
||||
.trim() || "unknown";
|
||||
|
||||
const published =
|
||||
$("label:contains('Year')")
|
||||
.next("div")
|
||||
.text()
|
||||
.trim() || "";
|
||||
// Status y Published suelen estar en labels
|
||||
const status = $("label:contains('Status')").next("div").text().trim().toLowerCase() || "unknown";
|
||||
const published = $("label:contains('Year')").next("div").text().trim() || "";
|
||||
|
||||
const genres = [];
|
||||
$("label:contains('Genres')")
|
||||
.parent()
|
||||
.find("a")
|
||||
.each((_, a) => genres.push($(a).text().trim()));
|
||||
$("a[href*='genre']").each((_, el) => {
|
||||
genres.push($(el).text().trim());
|
||||
});
|
||||
|
||||
const image =
|
||||
$("img[data-src]").first().attr("data-src") || null;
|
||||
const image = $("img[data-src]").first().attr("data-src") || "";
|
||||
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
format: "MANGA",
|
||||
score: 0,
|
||||
genres: genres.join(", "),
|
||||
genres: genres, // Array de strings
|
||||
status,
|
||||
published,
|
||||
summary,
|
||||
chapters: "???",
|
||||
chapters: 0, // Se calcula dinámicamente si es necesario
|
||||
image
|
||||
};
|
||||
}
|
||||
|
||||
async findChapters(mangaId) {
|
||||
const uriId = mangaId.replace(/\$/g, "/");
|
||||
const res = await this.fetch(`${this.baseUrl}/manga/${uriId}`);
|
||||
const res = await fetch(`${this.baseUrl}/manga/${uriId}`, { headers: this.headers });
|
||||
const html = await res.text();
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
const chapters = [];
|
||||
|
||||
$("div#chapters a").each((_, el) => {
|
||||
$("#chapters > div > a").each((_, el) => {
|
||||
const href = $(el).attr("href");
|
||||
if (!href) return;
|
||||
|
||||
@@ -153,24 +240,22 @@ class MangaPill {
|
||||
|
||||
async findChapterPages(chapterId) {
|
||||
const uriId = chapterId.replace(/\$/g, "/");
|
||||
const res = await this.fetch(`${this.baseUrl}/chapters/${uriId}`);
|
||||
const res = await fetch(`${this.baseUrl}/chapters/${uriId}`, { headers: this.headers });
|
||||
const html = await res.text();
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
const pages = [];
|
||||
|
||||
$("chapter-page").each((i, el) => {
|
||||
const img = $(el).find("div picture img").attr("data-src");
|
||||
$("picture img").each((i, el) => {
|
||||
const img = $(el).attr("data-src");
|
||||
if (!img) return;
|
||||
|
||||
pages.push({
|
||||
url: img,
|
||||
index: i,
|
||||
headers: {
|
||||
Referer: "https://mangapill.com/",
|
||||
Origin: "https://mangapill.com",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
Accept: "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
|
||||
"Referer": this.baseUrl + "/",
|
||||
"User-Agent": this.headers["User-Agent"]
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user