updates and new extensions
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
class AnimeAV1 {
|
class AnimeAV1 {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.type = "anime-board";
|
this.type = "anime-board";
|
||||||
this.version = "1.3";
|
this.version = "1.4";
|
||||||
this.api = "https://animeav1.com";
|
this.api = "https://animeav1.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,23 +12,178 @@ class AnimeAV1 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(query) {
|
getFilters() {
|
||||||
const res = await fetch(`${this.api}/api/search`, {
|
return {
|
||||||
method: "POST",
|
letter: {
|
||||||
headers: { "Content-Type": "application/json" },
|
label: 'Letra',
|
||||||
body: JSON.stringify({ query: query.query }),
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ value: '', label: 'Seleccionar...' },
|
||||||
|
{ value: '#', label: '#' },
|
||||||
|
...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map(l => ({
|
||||||
|
value: l,
|
||||||
|
label: l
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
category: {
|
||||||
|
label: 'Tipo',
|
||||||
|
type: 'multiselect',
|
||||||
|
options: [
|
||||||
|
{ value: 'tv-anime', label: 'TV Anime' },
|
||||||
|
{ value: 'pelicula', label: 'Película' },
|
||||||
|
{ value: 'ova', label: 'OVA' },
|
||||||
|
{ value: 'ona', label: 'ONA' },
|
||||||
|
{ value: 'especial', label: 'Especial' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
genre: {
|
||||||
|
label: 'Género',
|
||||||
|
type: 'multiselect',
|
||||||
|
options: [
|
||||||
|
{ value: 'accion', label: 'Acción' },
|
||||||
|
{ value: 'aventura', label: 'Aventura' },
|
||||||
|
{ value: 'ciencia-ficcion', label: 'Ciencia Ficción' },
|
||||||
|
{ value: 'comedia', label: 'Comedia' },
|
||||||
|
{ value: 'deportes', label: 'Deportes' },
|
||||||
|
{ value: 'drama', label: 'Drama' },
|
||||||
|
{ value: 'fantasia', label: 'Fantasía' },
|
||||||
|
{ value: 'misterio', label: 'Misterio' },
|
||||||
|
{ value: 'recuentos-de-la-vida', label: 'Recuentos de la Vida' },
|
||||||
|
{ value: 'romance', label: 'Romance' },
|
||||||
|
{ value: 'seinen', label: 'Seinen' },
|
||||||
|
{ value: 'shoujo', label: 'Shoujo' },
|
||||||
|
{ value: 'shounen', label: 'Shounen' },
|
||||||
|
{ value: 'sobrenatural', label: 'Sobrenatural' },
|
||||||
|
{ value: 'suspenso', label: 'Suspenso' },
|
||||||
|
{ value: 'terror', label: 'Terror' },
|
||||||
|
{ value: 'artes-marciales', label: 'Artes Marciales' },
|
||||||
|
{ value: 'ecchi', label: 'Ecchi' },
|
||||||
|
{ value: 'escolares', label: 'Escolares' },
|
||||||
|
{ value: 'gore', label: 'Gore' },
|
||||||
|
{ value: 'harem', label: 'Harem' },
|
||||||
|
{ value: 'historico', label: 'Histórico' },
|
||||||
|
{ value: 'isekai', label: 'Isekai' },
|
||||||
|
{ value: 'josei', label: 'Josei' },
|
||||||
|
{ value: 'magia', label: 'Magia' },
|
||||||
|
{ value: 'mecha', label: 'Mecha' },
|
||||||
|
{ value: 'militar', label: 'Militar' },
|
||||||
|
{ value: 'mitologia', label: 'Mitología' },
|
||||||
|
{ value: 'musica', label: 'Música' },
|
||||||
|
{ value: 'parodia', label: 'Parodia' },
|
||||||
|
{ value: 'psicologico', label: 'Psicológico' },
|
||||||
|
{ value: 'superpoderes', label: 'Superpoderes' },
|
||||||
|
{ value: 'vampiros', label: 'Vampiros' },
|
||||||
|
{ value: 'yuri', label: 'Yuri' },
|
||||||
|
{ value: 'yaoi', label: 'Yaoi' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
year: {
|
||||||
|
label: 'Año (Máximo)',
|
||||||
|
type: 'number'
|
||||||
|
},
|
||||||
|
|
||||||
|
status: {
|
||||||
|
label: 'Estado',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ value: 'emision', label: 'En emisión' },
|
||||||
|
{ value: 'finalizado', label: 'Finalizado' },
|
||||||
|
{ value: 'proximamente', label: 'Próximamente' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
order: {
|
||||||
|
label: 'Ordenar por',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ value: 'default', label: 'Por defecto' },
|
||||||
|
{ value: 'updated', label: 'Recientes' },
|
||||||
|
{ value: 'likes', label: 'Populares' },
|
||||||
|
{ value: 'title', label: 'Alfabético' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async search({ query, filters }) {
|
||||||
|
|
||||||
|
if (query && (!filters || Object.keys(filters).length === 0)) {
|
||||||
|
const res = await fetch(`${this.api}/api/search`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ query }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) return [];
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
return data.map(anime => ({
|
||||||
|
id: anime.slug,
|
||||||
|
title: anime.title,
|
||||||
|
url: `${this.api}/media/${anime.slug}`,
|
||||||
|
image: `https://cdn.animeav1.com/covers/${anime.id}.jpg`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (filters) {
|
||||||
|
|
||||||
|
if (filters.category) {
|
||||||
|
const cats = String(filters.category).split(',');
|
||||||
|
cats.forEach(c => {
|
||||||
|
if(c.trim()) params.append('category', c.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.genre) {
|
||||||
|
const genres = String(filters.genre).split(',');
|
||||||
|
genres.forEach(g => {
|
||||||
|
if(g.trim()) params.append('genre', g.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.year) params.set('maxYear', String(filters.year));
|
||||||
|
if (filters.status) params.set('status', filters.status);
|
||||||
|
if (filters.letter) params.set('letter', filters.letter);
|
||||||
|
if (filters.order && filters.order !== 'default') params.set('order', filters.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${this.api}/catalogo?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) return [];
|
||||||
|
|
||||||
|
const html = await res.text();
|
||||||
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
$('article.group\\/item').each((_, el) => {
|
||||||
|
const card = $(el);
|
||||||
|
|
||||||
|
const title = card.find('h3').first().text().trim();
|
||||||
|
const href = card.find('a[href^="/media/"]').attr('href');
|
||||||
|
const img = card.find('img').first().attr('src');
|
||||||
|
|
||||||
|
if (!href) return;
|
||||||
|
|
||||||
|
const slug = href.replace('/media/', '');
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
id: slug,
|
||||||
|
title,
|
||||||
|
url: `${this.api}${href}`,
|
||||||
|
image: img || '',
|
||||||
|
year: null
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) return [];
|
return results;
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
return data.map((anime) => ({
|
|
||||||
id: anime.slug,
|
|
||||||
title: anime.title,
|
|
||||||
url: `${this.api}/media/${anime.slug}`,
|
|
||||||
subOrDub: "both",
|
|
||||||
image: `https://cdn.animeav1.com/covers/${anime.id}.jpg`,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMetadata(id) {
|
async getMetadata(id) {
|
||||||
@@ -74,17 +229,9 @@ class AnimeAV1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findEpisodeServer(episodeOrId, _server, category = "sub") {
|
async findEpisodeServer(episodeOrId, _server, category = "sub") {
|
||||||
const ep =
|
const ep = typeof episodeOrId === "string"
|
||||||
typeof episodeOrId === "string"
|
? JSON.parse(episodeOrId)
|
||||||
? (() => {
|
: episodeOrId;
|
||||||
try {
|
|
||||||
return JSON.parse(episodeOrId);
|
|
||||||
} catch {
|
|
||||||
return { id: episodeOrId };
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
: episodeOrId;
|
|
||||||
|
|
||||||
|
|
||||||
let pageUrl = ep.url;
|
let pageUrl = ep.url;
|
||||||
|
|
||||||
@@ -97,36 +244,22 @@ class AnimeAV1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pageUrl) {
|
|
||||||
throw new Error(
|
|
||||||
`No se pudo determinar la URL del episodio (id=${ep.id}, number=${ep.number})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pageUrl) throw new Error("No se pudo determinar la URL del episodio.");
|
if (!pageUrl) throw new Error("No se pudo determinar la URL del episodio.");
|
||||||
|
|
||||||
const html = await fetch(pageUrl).then((r) => r.text());
|
const html = await fetch(pageUrl).then((r) => r.text());
|
||||||
const parsedData = this.parseSvelteData(html);
|
const parsedData = this.parseSvelteData(html);
|
||||||
const entry = parsedData.find((x) => x?.data?.embeds);
|
const entry = parsedData.find((x) => x?.data?.embeds);
|
||||||
const embeds = entry?.data?.embeds;
|
const embeds = entry?.data?.embeds;
|
||||||
|
|
||||||
if (!embeds) throw new Error("No embeds encontrados");
|
if (!embeds) throw new Error("No embeds encontrados");
|
||||||
|
|
||||||
const list =
|
const list = category === "dub" ? embeds.DUB : embeds.SUB;
|
||||||
category === "dub"
|
|
||||||
? embeds.DUB
|
|
||||||
: embeds.SUB;
|
|
||||||
|
|
||||||
if (!Array.isArray(list))
|
if (!Array.isArray(list)) throw new Error(`No hay streams ${category.toUpperCase()}`);
|
||||||
throw new Error(`No hay streams ${category.toUpperCase()}`);
|
|
||||||
|
|
||||||
const hls = list.find(
|
const hls = list.find(m => m.server === "HLS" && m.url?.includes("zilla-networks.com/play/"));
|
||||||
(m) =>
|
|
||||||
m.server === "HLS" &&
|
|
||||||
m.url?.includes("zilla-networks.com/play/")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hls)
|
if (!hls) throw new Error(`No se encontró stream HLS ${category.toUpperCase()}`);
|
||||||
throw new Error(`No se encontró stream HLS ${category.toUpperCase()}`);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
server: "HLS",
|
server: "HLS",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class OppaiStream {
|
|||||||
this.baseUrl = "https://oppai.stream";
|
this.baseUrl = "https://oppai.stream";
|
||||||
this.searchBaseUrl = "https://oppai.stream/actions/search.php?order=recent&page=1&limit=35&genres=&blacklist=&studio=&ibt=0&swa=1&text=";
|
this.searchBaseUrl = "https://oppai.stream/actions/search.php?order=recent&page=1&limit=35&genres=&blacklist=&studio=&ibt=0&swa=1&text=";
|
||||||
this.type = "anime-board";
|
this.type = "anime-board";
|
||||||
this.version = "1.0";
|
this.version = "1.2";
|
||||||
this.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36";
|
this.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36";
|
||||||
|
|
||||||
this.ScoreWeight = {
|
this.ScoreWeight = {
|
||||||
@@ -19,48 +19,178 @@ class OppaiStream {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(queryObj) {
|
getGenreOptions() {
|
||||||
let tempquery = queryObj.query;
|
return [
|
||||||
|
'4k','ahegao','anal','armpitmasturbation','bdsm','beach','bigboobs',
|
||||||
|
'blackhair','blondehair','blowjob','bluehair','bondage','boobjob',
|
||||||
|
'brownhair','censored','comedy','cosplay','cowgirl','creampie',
|
||||||
|
'darkskin','demon','doggy','dominantgirl','doublepenetration','elf',
|
||||||
|
'facial','fantasy','filmed','footjob','futanari','gangbang',
|
||||||
|
'girlsonly','glasses','greenhair','gyaru','hd','handjob','harem',
|
||||||
|
'horror','incest','inflation','invertednipples','lactation','loli',
|
||||||
|
'maid','masturbation','milf','mindbreak','mindcontrol','missionary',
|
||||||
|
'monster','ntr','nekomimi','nurse','old','orgy','pov','pinkhair',
|
||||||
|
'plot','pregnant','publicsex','purplehair','rape','redhair',
|
||||||
|
'reversegangbang','reverserape','rimjob','scat','schoolgirl',
|
||||||
|
'shorthair','shota','smallboobs','softcore','succubus','swimsuit',
|
||||||
|
'teacher','tentacle','threesome','toys','trap','tripplepenetration',
|
||||||
|
'tsundere','uglybastard','uncensored','vampire','vanilla','virgin',
|
||||||
|
'watersports','whitehair','x-ray','yaoi','yuri'
|
||||||
|
].map(g => ({ value: g, label: g }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getStudioOptions() {
|
||||||
|
return [
|
||||||
|
"44℃ Baidoku","AT-X","AXsiZ","Alice Soft","Antechinus","An♥Tekinus",
|
||||||
|
"BOOTLEG","BREAKBOTTLE","Bomb! Cute! Bomb!","Breakbottle","Bunny Walker",
|
||||||
|
"ChuChu","Collaboration Works","Cotton Doll","Digital Works",
|
||||||
|
"Global Solutions","HiLLS","Himajin Planning","JapanAnime","Jumondou",
|
||||||
|
"Kitty Media","Lune Pictures","MS Pictures","Magic Bus","Magin Label",
|
||||||
|
"Majin Petit","Majin petit","Majin","Mary Jane","Mediabank",
|
||||||
|
"Milky Animation Label","Mirai Koujou",
|
||||||
|
"NBCUniversal Entertainment Japan","Natural High","NewGeneration",
|
||||||
|
"Nippon Columbia","Nur","Office Nobu","Pashima","Pashmina","Passione",
|
||||||
|
"Peak Hunt","Pink Pineapple","PoRO petit","PoRO","Queen Bee",
|
||||||
|
"Rabbit Gate","Seven","Shion","Show-Ten","Shueisha","Studio 1st",
|
||||||
|
"Studio Gokumi","Studio Houkiboshi","Suzuki Mirano","T-Rex",
|
||||||
|
"TEATRO Nishi Tokyo Studio","TNK","Toranoana","WHITE BEAR","Y.O.U.C",
|
||||||
|
"YTV","Yomiuri TV Enterprise","ZIZ Entertainment","erozuki"
|
||||||
|
].map(s => ({ value: s, label: s }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilters() {
|
||||||
|
return {
|
||||||
|
order: {
|
||||||
|
label: 'Sort By',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ value: 'az', label: 'A-Z' },
|
||||||
|
{ value: 'za', label: 'Z-A' },
|
||||||
|
{ value: 'recent', label: 'Recently Released' },
|
||||||
|
{ value: 'old', label: 'Oldest Releases' },
|
||||||
|
{ value: 'views', label: 'Most Views' },
|
||||||
|
{ value: 'rating', label: 'Highest Rated' },
|
||||||
|
{ value: 'uploaded', label: 'Recently Uploaded' },
|
||||||
|
{ value: 'random', label: 'Randomize' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// TRI-STATE SIMULADO CON MULTISELECT
|
||||||
|
genre_include: {
|
||||||
|
label: 'Genre (Include)',
|
||||||
|
type: 'multiselect',
|
||||||
|
options: this.getGenreOptions()
|
||||||
|
},
|
||||||
|
|
||||||
|
genre_exclude: {
|
||||||
|
label: 'Genre (Exclude)',
|
||||||
|
type: 'multiselect',
|
||||||
|
options: this.getGenreOptions()
|
||||||
|
},
|
||||||
|
|
||||||
|
studio: {
|
||||||
|
label: 'Studio',
|
||||||
|
type: 'multiselect',
|
||||||
|
options: this.getStudioOptions()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async search({ query = "", filters }) {
|
||||||
|
let tempquery = query || "";
|
||||||
|
|
||||||
|
// 👉 si no hay texto pero sí filtros, hacemos una sola búsqueda
|
||||||
|
const hasFilters = filters && Object.keys(filters).length > 0;
|
||||||
|
let firstRun = true;
|
||||||
|
|
||||||
|
while (firstRun || tempquery !== "") {
|
||||||
|
firstRun = false;
|
||||||
|
|
||||||
while (tempquery !== "") {
|
|
||||||
try {
|
try {
|
||||||
const url = this.searchBaseUrl + encodeURIComponent(tempquery);
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
// SOLO ponemos text si existe
|
||||||
|
if (tempquery) params.set("text", tempquery);
|
||||||
|
|
||||||
|
if (filters) {
|
||||||
|
if (filters.order) params.set("order", filters.order);
|
||||||
|
|
||||||
|
if (filters.genre_include) {
|
||||||
|
const inc = String(filters.genre_include).split(',').map(x => x.trim()).filter(Boolean);
|
||||||
|
if (inc.length) params.set("genres", inc.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.genre_exclude) {
|
||||||
|
const exc = String(filters.genre_exclude).split(',').map(x => x.trim()).filter(Boolean);
|
||||||
|
if (exc.length) params.set("blacklist", exc.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.studio) {
|
||||||
|
const studios = String(filters.studio).split(',').map(x => x.trim()).filter(Boolean);
|
||||||
|
if (studios.length) params.set("studio", studios.join(','));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params.set("page", "1");
|
||||||
|
params.set("limit", "35");
|
||||||
|
|
||||||
|
const url = `${this.baseUrl}/actions/search.php?${params.toString()}`;
|
||||||
const html = await this.GETText(url);
|
const html = await this.GETText(url);
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const movies = $("div.in-grid.episode-shown");
|
const movies = $("div.in-grid.episode-shown");
|
||||||
|
|
||||||
|
// 👉 si no hay resultados:
|
||||||
if (movies.length <= 0) {
|
if (movies.length <= 0) {
|
||||||
|
// si hay filtros, no hacemos fallback por palabras
|
||||||
|
if (hasFilters || !tempquery) return [];
|
||||||
|
|
||||||
|
// fallback normal cuando hay texto
|
||||||
if (tempquery.includes(" ")) {
|
if (tempquery.includes(" ")) {
|
||||||
tempquery = tempquery.split(/[\s:']+/).slice(0, -1).join(" ");
|
tempquery = tempquery.split(/[\s:']+/).slice(0, -1).join(" ");
|
||||||
continue;
|
continue;
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const movieList = [];
|
const results = [];
|
||||||
movies.each((_, el) => {
|
movies.each((_, el) => {
|
||||||
const title = $(el).find(".title-ep").text().trim();
|
const elObj = $(el);
|
||||||
const href = $(el).find("a").attr("href");
|
const title = elObj.find(".title-ep .title").text().trim();
|
||||||
|
const href = elObj.find("a").attr("href");
|
||||||
const rawUrl = href ? href.replace("&for=search", "") : "";
|
const rawUrl = href ? href.replace("&for=search", "") : "";
|
||||||
|
const image = elObj.find(".cover-img-in").attr("src")
|
||||||
|
|| elObj.find(".cover-img-in").attr("original");
|
||||||
|
|
||||||
if (title && rawUrl) {
|
if (title && rawUrl) {
|
||||||
movieList.push({ Title: title, Url: rawUrl });
|
results.push({
|
||||||
|
id: encodeURIComponent(rawUrl),
|
||||||
|
title,
|
||||||
|
url: rawUrl,
|
||||||
|
image,
|
||||||
|
subOrDub: "sub",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const bestMovie = this.findBestTitle(movieList, queryObj.query);
|
// 👉 si hay query usamos tu sistema de score
|
||||||
|
if (query) {
|
||||||
|
const best = this.findBestTitle(
|
||||||
|
results.map(r => ({ Title: r.title, Url: r.url, Image: r.image })),
|
||||||
|
query
|
||||||
|
);
|
||||||
|
if (!best) return [];
|
||||||
|
return [{
|
||||||
|
id: encodeURIComponent(best.Url),
|
||||||
|
title: best.Title,
|
||||||
|
url: best.Url,
|
||||||
|
image: best.Image,
|
||||||
|
subOrDub: "sub",
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
if (!bestMovie) return [];
|
// 👉 si NO hay query, devolvemos todo (modo catálogo)
|
||||||
|
return results;
|
||||||
return [{
|
|
||||||
// Codificamos la URL para que sea un ID seguro para la URL de la app
|
|
||||||
id: encodeURIComponent(bestMovie.Url),
|
|
||||||
title: bestMovie.Title,
|
|
||||||
url: bestMovie.Url,
|
|
||||||
subOrDub: queryObj.dub ? "dub" : "sub",
|
|
||||||
}];
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -74,6 +204,7 @@ class OppaiStream {
|
|||||||
try {
|
try {
|
||||||
// Decodificamos el ID para obtener la URL real de OppaiStream
|
// Decodificamos el ID para obtener la URL real de OppaiStream
|
||||||
const decodedUrl = decodeURIComponent(id);
|
const decodedUrl = decodeURIComponent(id);
|
||||||
|
console.log(decodedUrl)
|
||||||
const html = await this.GETText(decodedUrl);
|
const html = await this.GETText(decodedUrl);
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
|
|||||||
437
anime/hentaila.js
Normal file
437
anime/hentaila.js
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
class Hentaila {
|
||||||
|
constructor() {
|
||||||
|
this.baseUrl = "https://hentaila.com";
|
||||||
|
this.cdnUrl = "https://cdn.hentaila.com";
|
||||||
|
this.type = "anime-board";
|
||||||
|
this.version = "1.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilters() {
|
||||||
|
return {
|
||||||
|
sort: {
|
||||||
|
label: "Ordenar por",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "latest_released", label: "Recientes" },
|
||||||
|
{ value: "popular", label: "Populares" }
|
||||||
|
],
|
||||||
|
default: "latest_released"
|
||||||
|
},
|
||||||
|
genres: {
|
||||||
|
label: "Géneros",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "3d", label: "3D" },
|
||||||
|
{ value: "ahegao", label: "Ahegao" },
|
||||||
|
{ value: "anal", label: "Anal" },
|
||||||
|
{ value: "casadas", label: "Casadas" },
|
||||||
|
{ value: "chikan", label: "Chikan" },
|
||||||
|
{ value: "ecchi", label: "Ecchi" },
|
||||||
|
{ value: "enfermeras", label: "Enfermeras" },
|
||||||
|
{ value: "futanari", label: "Futanari" },
|
||||||
|
{ value: "escolares", label: "Escolares" },
|
||||||
|
{ value: "gore", label: "Gore" },
|
||||||
|
{ value: "hardcore", label: "Hardcore" },
|
||||||
|
{ value: "harem", label: "Harem" },
|
||||||
|
{ value: "incesto", label: "Incesto" },
|
||||||
|
{ value: "juegos-sexuales", label: "Juegos Sexuales" },
|
||||||
|
{ value: "milfs", label: "Milfs" },
|
||||||
|
{ value: "maids", label: "Maids" },
|
||||||
|
{ value: "netorare", label: "Netorare" },
|
||||||
|
{ value: "ninfomania", label: "Ninfomanía" },
|
||||||
|
{ value: "ninjas", label: "Ninjas" },
|
||||||
|
{ value: "orgias", label: "Orgías" },
|
||||||
|
{ value: "romance", label: "Romance" },
|
||||||
|
{ value: "shota", label: "Shota" },
|
||||||
|
{ value: "softcore", label: "Softcore" },
|
||||||
|
{ value: "succubus", label: "Succubus" },
|
||||||
|
{ value: "teacher", label: "Teacher" },
|
||||||
|
{ value: "tentaculos", label: "Tentáculos" },
|
||||||
|
{ value: "tetonas", label: "Tetonas" },
|
||||||
|
{ value: "vanilla", label: "Vanilla" },
|
||||||
|
{ value: "violacion", label: "Violación" },
|
||||||
|
{ value: "virgenes", label: "Vírgenes" },
|
||||||
|
{ value: "yaoi", label: "Yaoi" },
|
||||||
|
{ value: "yuri", label: "Yuri" },
|
||||||
|
{ value: "bondage", label: "Bondage" },
|
||||||
|
{ value: "elfas", label: "Elfas" },
|
||||||
|
{ value: "petit", label: "Petit" },
|
||||||
|
{ value: "threesome", label: "Threesome" },
|
||||||
|
{ value: "paizuri", label: "Paizuri" },
|
||||||
|
{ value: "gal", label: "Gal" },
|
||||||
|
{ value: "oyakodon", label: "Oyakodon" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: "Estado",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "emision", label: "En Emisión" },
|
||||||
|
{ value: "finalizado", label: "Finalizado" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
uncensored: {
|
||||||
|
label: "Sin Censura",
|
||||||
|
type: "checkbox",
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getSettings() {
|
||||||
|
return {
|
||||||
|
episodeServers: ["StreamWish", "VidHide"], //"VIP" works but the stream is blocked even with the headers.
|
||||||
|
supportsDub: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_resolveRemixData(json) {
|
||||||
|
if (!json || !json.nodes) return [];
|
||||||
|
|
||||||
|
for (const node of json.nodes) {
|
||||||
|
if (node && node.uses && node.uses.search_params) {
|
||||||
|
const data = node.data;
|
||||||
|
if (!data || data.length === 0) continue;
|
||||||
|
|
||||||
|
const rootConfig = data[0];
|
||||||
|
|
||||||
|
if (!rootConfig || typeof rootConfig.results !== 'number') continue;
|
||||||
|
|
||||||
|
const resultsIndex = rootConfig.results;
|
||||||
|
|
||||||
|
const animePointers = data[resultsIndex];
|
||||||
|
|
||||||
|
if (!Array.isArray(animePointers)) continue;
|
||||||
|
|
||||||
|
return animePointers.map(pointer => {
|
||||||
|
const rawObj = data[pointer];
|
||||||
|
|
||||||
|
if (!rawObj) return null;
|
||||||
|
|
||||||
|
const realId = data[rawObj.id];
|
||||||
|
const title = data[rawObj.title];
|
||||||
|
const slug = data[rawObj.slug];
|
||||||
|
|
||||||
|
// Validación básica
|
||||||
|
if (!title || !slug) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: slug,
|
||||||
|
title: title,
|
||||||
|
url: `${this.baseUrl}/media/${slug}`,
|
||||||
|
image: `${this.cdnUrl}/covers/${realId}.jpg`,
|
||||||
|
year: null
|
||||||
|
};
|
||||||
|
}).filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(queryObj) {
|
||||||
|
const { query, filters, page } = queryObj;
|
||||||
|
const pageNum = page || 1;
|
||||||
|
|
||||||
|
let url = `${this.baseUrl}/catalogo/__data.json?page=${pageNum}`;
|
||||||
|
|
||||||
|
if (query && query.trim() !== "") {
|
||||||
|
url += `&search=${encodeURIComponent(query)}`;
|
||||||
|
} else {
|
||||||
|
if (filters.sort) url += `&order=${filters.sort}`;
|
||||||
|
else url += `&order=latest_released`;
|
||||||
|
|
||||||
|
if (filters.genres) url += `&genre=${filters.genres}`;
|
||||||
|
|
||||||
|
if (filters.status) url += `&status=${filters.status}`;
|
||||||
|
|
||||||
|
if (filters.uncensored) url += `&uncensored=`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const json = await response.json();
|
||||||
|
return this._resolveRemixData(json);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error searching Hentaila:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMetadata(id) {
|
||||||
|
const url = `${this.baseUrl}/media/${id}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const html = await response.text();
|
||||||
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
|
const title = $(".grid.items-start h1.text-lead").first().text().trim();
|
||||||
|
const image = $("img.object-cover.w-full.aspect-poster").first().attr("src");
|
||||||
|
const summary = $(".entry.text-lead.text-sm p").text().trim();
|
||||||
|
|
||||||
|
// Estado
|
||||||
|
const statusText = $("div.flex.flex-wrap.items-center.text-sm span").text();
|
||||||
|
const status = statusText.includes("En emisión") ? "En Emisión" : "Finalizado";
|
||||||
|
|
||||||
|
// Géneros
|
||||||
|
const genres = [];
|
||||||
|
$(".flex-wrap.items-center .btn.btn-xs.rounded-full").each((i, el) => {
|
||||||
|
const txt = $(el).text().trim();
|
||||||
|
if (txt) genres.push(txt);
|
||||||
|
});
|
||||||
|
|
||||||
|
const episodeCount = $("article.group\\/item").length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: title,
|
||||||
|
summary: summary,
|
||||||
|
status: status,
|
||||||
|
genres: genres,
|
||||||
|
image: image,
|
||||||
|
episodes: episodeCount,
|
||||||
|
url: url
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting metadata:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findEpisodes(id) {
|
||||||
|
const url = `${this.baseUrl}/media/${id}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const html = await response.text();
|
||||||
|
const $ = this.cheerio.load(html);
|
||||||
|
const episodes = [];
|
||||||
|
|
||||||
|
$("article.group\\/item").each((i, el) => {
|
||||||
|
const $el = $(el);
|
||||||
|
|
||||||
|
const numberText = $el.find("span.text-lead").text().trim();
|
||||||
|
const number = parseFloat(numberText);
|
||||||
|
|
||||||
|
const relativeUrl = $el.find("a").attr("href");
|
||||||
|
|
||||||
|
const image = $el.find("img").attr("src");
|
||||||
|
|
||||||
|
if (!isNaN(number) && relativeUrl) {
|
||||||
|
episodes.push({
|
||||||
|
id: JSON.stringify({ slug: id, number: number }),
|
||||||
|
number: number,
|
||||||
|
title: `Episodio ${number}`,
|
||||||
|
url: `${this.baseUrl}${relativeUrl}`,
|
||||||
|
image: image
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return episodes;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error finding episodes:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findEpisodeServer(episodeOrId, _server, category = "sub") {
|
||||||
|
let slug, number;
|
||||||
|
|
||||||
|
const ep = typeof episodeOrId === "string"
|
||||||
|
? JSON.parse(episodeOrId)
|
||||||
|
: episodeOrId;
|
||||||
|
|
||||||
|
if (ep.id && typeof ep.id === "string" && ep.id.startsWith("{")) {
|
||||||
|
const p = JSON.parse(ep.id);
|
||||||
|
slug = p.slug;
|
||||||
|
number = p.number;
|
||||||
|
} else {
|
||||||
|
slug = ep.slug;
|
||||||
|
number = ep.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!slug || !number) throw new Error("No se pudo determinar episodio");
|
||||||
|
|
||||||
|
const url = `${this.baseUrl}/media/${slug}/${number}/__data.json`;
|
||||||
|
const json = await fetch(url).then(r => r.json());
|
||||||
|
|
||||||
|
let chosen = null;
|
||||||
|
const wanted = (_server || "VIP").toLowerCase();
|
||||||
|
|
||||||
|
if (json.nodes) {
|
||||||
|
for (const node of json.nodes) {
|
||||||
|
if (!node?.uses?.params?.includes("number")) continue;
|
||||||
|
|
||||||
|
const data = node.data;
|
||||||
|
const root = data?.[0];
|
||||||
|
if (!root || typeof root.embeds !== "number") continue;
|
||||||
|
|
||||||
|
const embeds = data[root.embeds];
|
||||||
|
const listIndex = category === "dub" ? embeds?.DUB : embeds?.SUB;
|
||||||
|
if (typeof listIndex !== "number") continue;
|
||||||
|
|
||||||
|
const list = data[listIndex];
|
||||||
|
if (!Array.isArray(list)) continue;
|
||||||
|
|
||||||
|
for (const i of list) {
|
||||||
|
const v = data[i];
|
||||||
|
const server = data[v.server];
|
||||||
|
const link = data[v.url];
|
||||||
|
if (!server || !link) continue;
|
||||||
|
|
||||||
|
if (server.toLowerCase() !== wanted) continue;
|
||||||
|
|
||||||
|
let finalUrl = link;
|
||||||
|
let type = "iframe";
|
||||||
|
|
||||||
|
// --- VIP → m3u8 directo ---
|
||||||
|
const serverName = server.toLowerCase();
|
||||||
|
|
||||||
|
// --- VIP ---
|
||||||
|
if (serverName === "vip") {
|
||||||
|
finalUrl = link.replace("/play/", "/m3u8/");
|
||||||
|
type = "m3u8";
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- STREAMWISH ---
|
||||||
|
else if (serverName === "streamwish") {
|
||||||
|
const m3u8 = await this.extractPackedM3U8(link);
|
||||||
|
if (m3u8) {
|
||||||
|
finalUrl = m3u8;
|
||||||
|
type = "m3u8";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (serverName === "vidhide") {
|
||||||
|
const m3u8 = await this.extractPackedM3U8(link);
|
||||||
|
if (m3u8) {
|
||||||
|
finalUrl = m3u8;
|
||||||
|
type = "m3u8";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chosen = {
|
||||||
|
url: finalUrl,
|
||||||
|
type,
|
||||||
|
quality: server,
|
||||||
|
subtitles: [],
|
||||||
|
subOrDub: category
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chosen) throw new Error(`No se encontró el server ${_server}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
server: _server || "VIP",
|
||||||
|
headers: {
|
||||||
|
Referer: "https://hentaila.com/",
|
||||||
|
Origin: "https://hentaila.com"
|
||||||
|
},
|
||||||
|
videoSources: [chosen]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async extractPackedM3U8(embedUrl) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { result } = await this.scrape(embedUrl, async (page) => {
|
||||||
|
try {
|
||||||
|
await page.waitForSelector('script', { state: 'attached', timeout: 5000 });
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return await page.evaluate(() => {
|
||||||
|
function unpack(code) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const regex = /}\s*\('(.*?)',\s*(\d+),\s*(\d+),\s*'(.*?)'\.split\('\|'\)/;
|
||||||
|
const m = code.match(regex);
|
||||||
|
|
||||||
|
if (!m) return null;
|
||||||
|
|
||||||
|
let payload = m[1].replace(/\\'/g, "'");
|
||||||
|
const radix = parseInt(m[2]);
|
||||||
|
const count = parseInt(m[3]);
|
||||||
|
const dict = m[4].split('|');
|
||||||
|
|
||||||
|
const unbase = (val) => {
|
||||||
|
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
if (radix > 62) return parseInt(val, radix);
|
||||||
|
|
||||||
|
const alphabet = chars.slice(0, radix);
|
||||||
|
let ret = 0;
|
||||||
|
const reversed = val.split('').reverse().join('');
|
||||||
|
for (let i = 0; i < reversed.length; i++) {
|
||||||
|
const index = alphabet.indexOf(reversed[i]);
|
||||||
|
if (index === -1) continue;
|
||||||
|
ret += index * Math.pow(radix, i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
return payload.replace(/\b\w+\b/g, (word) => {
|
||||||
|
const index = unbase(word);
|
||||||
|
if (dict[index]) return dict[index];
|
||||||
|
return word;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return "ERROR_IN_UNPACKER: " + e.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- BÚSQUEDA DEL SCRIPT ---
|
||||||
|
const scripts = Array.from(document.getElementsByTagName('script'));
|
||||||
|
|
||||||
|
for (const script of scripts) {
|
||||||
|
const content = script.textContent;
|
||||||
|
if (!content) continue;
|
||||||
|
|
||||||
|
// Buscamos la firma del packer
|
||||||
|
if (content.includes('eval(function(p,a,c,k,e,d)') || content.includes('eval(function(p,a,c')) {
|
||||||
|
// Intentamos desempaquetar
|
||||||
|
const unpacked = unpack(content);
|
||||||
|
|
||||||
|
// Si funcionó y parece contener HTML/JS válido
|
||||||
|
if (unpacked && unpacked.length > 20 && !unpacked.startsWith("ERROR")) {
|
||||||
|
return unpacked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "NO_PACKER_FOUND";
|
||||||
|
});
|
||||||
|
|
||||||
|
}, {
|
||||||
|
waitUntil: "domcontentloaded",
|
||||||
|
renderWaitTime: 2000
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result || result === "NO_PACKER_FOUND") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.startsWith("ERROR")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const m3u8Regex = /(https?:\/\/[^"']+\.m3u8[^"']*)/;
|
||||||
|
const match = result.match(m3u8Regex);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
return match[1];
|
||||||
|
} else {
|
||||||
|
console.log("[DEBUG] ⚠️ Script desempaquetado pero SIN m3u8. Dump parcial:", result.substring(0, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Hentaila;
|
||||||
266
anime/missav.js
Normal file
266
anime/missav.js
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
class MissAV {
|
||||||
|
constructor() {
|
||||||
|
this.type = "anime-board";
|
||||||
|
this.version = "1.0";
|
||||||
|
this.baseUrl = "https://missav.live";
|
||||||
|
}
|
||||||
|
|
||||||
|
getSettings() {
|
||||||
|
return {
|
||||||
|
supportsDub: false,
|
||||||
|
episodeServers: ["Default"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= FILTERS ================= */
|
||||||
|
|
||||||
|
getFilters() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: "sort",
|
||||||
|
name: "Sort by",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "", label: "Any" },
|
||||||
|
{ value: "released_at", label: "Release date" },
|
||||||
|
{ value: "published_at", label: "Recent update" },
|
||||||
|
{ value: "today_views", label: "Today views" },
|
||||||
|
{ value: "weekly_views", label: "Weekly views" },
|
||||||
|
{ value: "monthly_views", label: "Monthly views" },
|
||||||
|
{ value: "views", label: "Total views" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "genre",
|
||||||
|
name: "Genres",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "", label: "<Select>" },
|
||||||
|
{ value: "en/uncensored-leak", label: "Uncensored Leak" },
|
||||||
|
{ value: "en/genres/Hd", label: "Hd" },
|
||||||
|
{ value: "en/genres/Exclusive", label: "Exclusive" },
|
||||||
|
{ value: "en/genres/Creampie", label: "Creampie" },
|
||||||
|
{ value: "en/genres/Big%20Breasts", label: "Big Breasts" },
|
||||||
|
{ value: "en/genres/Individual", label: "Individual" },
|
||||||
|
{ value: "en/genres/Wife", label: "Wife" },
|
||||||
|
{ value: "en/genres/Mature%20Woman", label: "Mature Woman" },
|
||||||
|
{ value: "en/genres/Pretty%20Girl", label: "Pretty Girl" },
|
||||||
|
{ value: "en/genres/Orgy", label: "Orgy" },
|
||||||
|
{ value: "en/genres/Lesbian", label: "Lesbian" },
|
||||||
|
{ value: "en/genres/Ntr", label: "NTR" },
|
||||||
|
{ value: "en/genres/Cosplay", label: "Cosplay" },
|
||||||
|
{ value: "en/genres/Uniform", label: "Uniform" },
|
||||||
|
{ value: "en/genres/Swimsuit", label: "Swimsuit" },
|
||||||
|
{ value: "en/genres/4K", label: "4K" },
|
||||||
|
{ value: "en/genres/Vr", label: "VR" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "note",
|
||||||
|
text: "Genre filters ignored with text search!",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= SEARCH ================= */
|
||||||
|
|
||||||
|
async search(query) {
|
||||||
|
const filters = query?.filters || {};
|
||||||
|
const hasText = !!(query?.query && query.query.trim());
|
||||||
|
|
||||||
|
let url;
|
||||||
|
if (hasText) {
|
||||||
|
url = `${this.baseUrl}/en/search/${encodeURIComponent(query.query)}`;
|
||||||
|
} else if (filters.genre) {
|
||||||
|
url = `${this.baseUrl}/${filters.genre}`;
|
||||||
|
} else {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters.sort) params.set("sort", filters.sort);
|
||||||
|
url = `${this.baseUrl}/en?${params.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result, requests } = await this.scrape(
|
||||||
|
url,
|
||||||
|
async (page) => {
|
||||||
|
|
||||||
|
const html = await page.content();
|
||||||
|
|
||||||
|
const items = await page.$$eval(
|
||||||
|
'div.thumbnail',
|
||||||
|
nodes => nodes.map(n => {
|
||||||
|
const a = n.querySelector('a[href^="https://missav.live/en/"]');
|
||||||
|
if (!a) return null;
|
||||||
|
|
||||||
|
const href = a.getAttribute("href");
|
||||||
|
|
||||||
|
const img =
|
||||||
|
n.querySelector('img')?.getAttribute("data-src") ||
|
||||||
|
n.querySelector('img')?.getAttribute("src");
|
||||||
|
|
||||||
|
const title =
|
||||||
|
n.querySelector('div.text-sm a')?.textContent?.trim() ||
|
||||||
|
n.querySelector('a')?.textContent?.trim();
|
||||||
|
|
||||||
|
if (!href || !img || !title) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: href.replace("https://missav.live", ""),
|
||||||
|
title,
|
||||||
|
image: img,
|
||||||
|
url: href,
|
||||||
|
};
|
||||||
|
}).filter(Boolean)
|
||||||
|
);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
waitUntil: "domcontentloaded",
|
||||||
|
renderWaitTime: 1500,
|
||||||
|
scrollToBottom: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= METADATA ================= */
|
||||||
|
|
||||||
|
async getMetadata(animeId) {
|
||||||
|
const url = animeId.startsWith("http")
|
||||||
|
? animeId
|
||||||
|
: this.baseUrl + animeId;
|
||||||
|
|
||||||
|
console.log("[MissAV][meta] url =", url);
|
||||||
|
|
||||||
|
const { result, requests } = await this.scrape(
|
||||||
|
url,
|
||||||
|
async (page) => {
|
||||||
|
console.log("[MissAV][meta] page loaded");
|
||||||
|
|
||||||
|
const htmlSize = await page.content().then(h => h.length);
|
||||||
|
console.log("[MissAV][meta] html size =", htmlSize);
|
||||||
|
|
||||||
|
return await page.evaluate(() => {
|
||||||
|
const dbg = {};
|
||||||
|
|
||||||
|
const h1 = document.querySelector("h1");
|
||||||
|
dbg.hasH1 = !!h1;
|
||||||
|
|
||||||
|
const video = document.querySelector("video.player");
|
||||||
|
dbg.hasVideo = !!video;
|
||||||
|
|
||||||
|
const og = document.querySelector('meta[property="og:image"]');
|
||||||
|
dbg.hasOg = !!og;
|
||||||
|
|
||||||
|
const genreLinks = document.querySelectorAll('a[href^="/en/genres/"]');
|
||||||
|
dbg.genreCount = genreLinks.length;
|
||||||
|
|
||||||
|
const title =
|
||||||
|
document.querySelector("h1.text-base")?.textContent?.trim() ||
|
||||||
|
document.querySelector("h1")?.textContent?.trim() ||
|
||||||
|
"Unknown";
|
||||||
|
|
||||||
|
const poster =
|
||||||
|
video?.getAttribute("data-poster") ||
|
||||||
|
og?.content ||
|
||||||
|
null;
|
||||||
|
|
||||||
|
const description = "";
|
||||||
|
|
||||||
|
const genres = Array.from(genreLinks).map(a =>
|
||||||
|
a.textContent.trim()
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dbg,
|
||||||
|
title,
|
||||||
|
poster,
|
||||||
|
description,
|
||||||
|
genres,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
waitUntil: "domcontentloaded",
|
||||||
|
timeout: 20000,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: result.title,
|
||||||
|
image: result.poster,
|
||||||
|
description: result.description,
|
||||||
|
genres: result.genres,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ================= EPISODES ================= */
|
||||||
|
|
||||||
|
async findEpisodes(animeId) {
|
||||||
|
// MissAV es 1 video = 1 episodio
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: animeId,
|
||||||
|
number: 1,
|
||||||
|
title: "Video",
|
||||||
|
url: animeId.startsWith("http")
|
||||||
|
? animeId
|
||||||
|
: this.baseUrl + animeId,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= SERVERS ================= */
|
||||||
|
|
||||||
|
async findEpisodeServer(episode) {
|
||||||
|
const url = episode.url.startsWith("http")
|
||||||
|
? episode.url
|
||||||
|
: this.baseUrl + episode.url;
|
||||||
|
|
||||||
|
const { requests } = await this.scrape(
|
||||||
|
url,
|
||||||
|
async () => true,
|
||||||
|
{
|
||||||
|
waitUntil: "domcontentloaded",
|
||||||
|
timeout: 20000,
|
||||||
|
renderWaitTime: 1500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const m3u8s = requests
|
||||||
|
.map(r => r.url)
|
||||||
|
.filter(u => u.includes(".m3u8"));
|
||||||
|
|
||||||
|
if (!m3u8s.length) throw new Error("No m3u8 in network");
|
||||||
|
|
||||||
|
// regla:
|
||||||
|
// - si existe .../playlist.m3u8 -> master
|
||||||
|
// - si no -> usar video.m3u8 (o el único que haya)
|
||||||
|
let finalUrl =
|
||||||
|
m3u8s.find(u => /\/playlist\.m3u8(\?|$)/.test(u)) ||
|
||||||
|
m3u8s.find(u => /\/video\.m3u8(\?|$)/.test(u)) ||
|
||||||
|
m3u8s[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
server: "Default",
|
||||||
|
videoSources: [
|
||||||
|
{
|
||||||
|
url: finalUrl,
|
||||||
|
type: "m3u8",
|
||||||
|
quality: "auto",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ================= UTILS ================= */
|
||||||
|
|
||||||
|
safeString(str) {
|
||||||
|
return typeof str === "string" ? str : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MissAV;
|
||||||
290
anime/rouvideo.js
Normal file
290
anime/rouvideo.js
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
class RouVideo {
|
||||||
|
constructor() {
|
||||||
|
this.baseUrl = "https://rou.video";
|
||||||
|
this.apiUrl = "https://rou.video/api";
|
||||||
|
this.type = "anime-board";
|
||||||
|
this.version = "1.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilters() {
|
||||||
|
return {
|
||||||
|
sort: {
|
||||||
|
label: "Ordenar por",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "createdAt", label: "Recent" },
|
||||||
|
{ value: "viewCount", label: "Most viewed" },
|
||||||
|
{ value: "likeCount", label: "Most liked" }
|
||||||
|
],
|
||||||
|
default: "createdAt"
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
label: "Categoría",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "all", label: "Todos los videos" },
|
||||||
|
{ value: "featured", label: "Destacados" },
|
||||||
|
{ value: "watching", label: "Viendo ahora" },
|
||||||
|
{ value: "國產AV", label: "Chinese AV" },
|
||||||
|
{ value: "中文字幕", label: "Chinese Sub" },
|
||||||
|
{ value: "麻豆傳媒", label: "Madou Media" },
|
||||||
|
{ value: "自拍流出", label: "Selfie Leaked" },
|
||||||
|
{ value: "探花", label: "Tanhua" },
|
||||||
|
{ value: "OnlyFans", label: "OnlyFans" },
|
||||||
|
{ value: "日本", label: "JAV" }
|
||||||
|
],
|
||||||
|
default: "all"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getSettings() {
|
||||||
|
return {
|
||||||
|
episodeServers: ["RouVideo"],
|
||||||
|
supportsDub: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(queryObj) {
|
||||||
|
const { query, filters, page } = queryObj;
|
||||||
|
const pageNum = page || 1;
|
||||||
|
const sort = filters?.sort || "createdAt";
|
||||||
|
const category = filters?.category || "all";
|
||||||
|
let url;
|
||||||
|
|
||||||
|
if (query && query.trim().length > 0) {
|
||||||
|
url = `${this.baseUrl}/search?q=${encodeURIComponent(query.trim())}&page=${pageNum}`;
|
||||||
|
if (category !== "all" && category !== "featured" && category !== "watching") {
|
||||||
|
url += `&t=${encodeURIComponent(category)}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (category === "watching") {
|
||||||
|
url = `${this.apiUrl}/v/watching`;
|
||||||
|
} else if (category === "featured") {
|
||||||
|
url = `${this.baseUrl}/home`;
|
||||||
|
} else if (category !== "all") {
|
||||||
|
url = `${this.baseUrl}/t/${encodeURIComponent(category)}?page=${pageNum}&order=${sort}`;
|
||||||
|
} else {
|
||||||
|
url = `${this.baseUrl}/v?page=${pageNum}&order=${sort}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (category === "watching" && !query) {
|
||||||
|
const response = await this.requestApi(url);
|
||||||
|
const json = JSON.parse(response);
|
||||||
|
return json.map(this.parseVideoItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.request(url);
|
||||||
|
const $ = this.cheerio.load(response);
|
||||||
|
const nextData = this.extractNextData($);
|
||||||
|
|
||||||
|
if (!nextData || !nextData.props || !nextData.props.pageProps) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = nextData.props.pageProps;
|
||||||
|
let videos = [];
|
||||||
|
|
||||||
|
if (props.videos) {
|
||||||
|
videos = props.videos;
|
||||||
|
} else if (props.hotSearches && query) {
|
||||||
|
videos = props.videos || [];
|
||||||
|
} else if (category === "featured" || url.includes("/home")) {
|
||||||
|
videos = [
|
||||||
|
...(props.latestVideos || []),
|
||||||
|
...(props.hotCNAV || []),
|
||||||
|
...(props.hot91 || []),
|
||||||
|
...(props.hotSelfie || [])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return videos.map(this.parseVideoItem);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error en search:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMetadata(id) {
|
||||||
|
try {
|
||||||
|
const url = `${this.baseUrl}/v/${id}`;
|
||||||
|
const response = await this.request(url);
|
||||||
|
const $ = this.cheerio.load(response);
|
||||||
|
const nextData = this.extractNextData($);
|
||||||
|
|
||||||
|
if (!nextData) return { id, title: "Unknown" };
|
||||||
|
|
||||||
|
const video = nextData.props.pageProps.video;
|
||||||
|
|
||||||
|
if (!video) return { id, title: "Unknown" };
|
||||||
|
|
||||||
|
let descText = "";
|
||||||
|
if (video.sources && video.sources.length > 0) {
|
||||||
|
descText += `Resolution: ${video.sources[0].resolution}p\n`;
|
||||||
|
}
|
||||||
|
descText += `Duration: ${this.formatDuration(video.duration)}\n`;
|
||||||
|
descText += `View: ${video.viewCount}`;
|
||||||
|
if (video.likeCount) descText += ` - Like: ${video.likeCount}`;
|
||||||
|
if (video.ref) descText += `\nRef: ${video.ref}`;
|
||||||
|
if (video.description) descText += `\n\n${video.description}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: video.id,
|
||||||
|
title: video.name,
|
||||||
|
cover: video.coverImageUrl,
|
||||||
|
description: descText,
|
||||||
|
genres: video.tags || [],
|
||||||
|
author: video.tags?.[0] || "",
|
||||||
|
status: "Completed",
|
||||||
|
url: url
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error en getMetadata:", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findEpisodes(id) {
|
||||||
|
try {
|
||||||
|
return [{
|
||||||
|
id: id,
|
||||||
|
number: 1,
|
||||||
|
title: "Movie"
|
||||||
|
}];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findEpisodeServer(episodeInput, server, category = "sub") {
|
||||||
|
let cleanId = "";
|
||||||
|
if (typeof episodeInput === 'object' && episodeInput !== null) {
|
||||||
|
cleanId = episodeInput.id;
|
||||||
|
} else {
|
||||||
|
cleanId = episodeInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String(cleanId).includes('/')) {
|
||||||
|
cleanId = String(cleanId).split('/').pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[RouVideo] Buscando servidor para ID: ${cleanId}`);
|
||||||
|
|
||||||
|
const apiUrl = `${this.apiUrl}/v/${cleanId}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const req = await fetch(apiUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'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': 'application/json, text/plain, */*',
|
||||||
|
'Host': 'rou.video',
|
||||||
|
'Origin': 'https://rou.video',
|
||||||
|
'Referer': 'https://rou.video/'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!req.ok) {
|
||||||
|
console.error(`[RouVideo] Error HTTP: ${req.status}`);
|
||||||
|
return { videoSources: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = await req.text();
|
||||||
|
|
||||||
|
if (text.trim().startsWith("<")) {
|
||||||
|
console.error("[RouVideo] Error: La API devolvió HTML");
|
||||||
|
return { videoSources: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = JSON.parse(text);
|
||||||
|
|
||||||
|
if (json?.video?.videoUrl) {
|
||||||
|
console.log("[RouVideo] Video URL encontrado:", json.video.videoUrl);
|
||||||
|
|
||||||
|
// Headers necesarios para reproducir el stream
|
||||||
|
const streamHeaders = {
|
||||||
|
"Referer": "https://rou.video/",
|
||||||
|
"Origin": "https://rou.video",
|
||||||
|
"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"
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
headers: streamHeaders,
|
||||||
|
videoSources: [{
|
||||||
|
server: "RouVideo",
|
||||||
|
url: json.video.videoUrl,
|
||||||
|
type: "m3u8",
|
||||||
|
quality: "Auto",
|
||||||
|
headers: streamHeaders
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.warn("[RouVideo] JSON válido pero sin videoUrl");
|
||||||
|
return { videoSources: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[RouVideo] Error fatal:", error);
|
||||||
|
return { videoSources: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extractNextData($) {
|
||||||
|
try {
|
||||||
|
const scriptContent = $('#__NEXT_DATA__').html();
|
||||||
|
if (scriptContent) {
|
||||||
|
return JSON.parse(scriptContent);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error parsing __NEXT_DATA__", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseVideoItem(video) {
|
||||||
|
return {
|
||||||
|
id: video.id,
|
||||||
|
title: video.name,
|
||||||
|
image: video.coverImageUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDuration(seconds) {
|
||||||
|
if (!seconds) return "0:00";
|
||||||
|
const h = Math.floor(seconds / 3600);
|
||||||
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
|
const s = Math.floor(seconds % 60);
|
||||||
|
const mStr = m < 10 && h > 0 ? `0${m}` : m;
|
||||||
|
const sStr = s < 10 ? `0${s}` : s;
|
||||||
|
return h > 0 ? `${h}:${mStr}:${sStr}` : `${mStr}:${sStr}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async request(url) {
|
||||||
|
const req = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'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}/`,
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return await req.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestApi(url) {
|
||||||
|
const req = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'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}/`,
|
||||||
|
'Origin': this.baseUrl,
|
||||||
|
'Accept': 'application/json, text/plain, */*'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return await req.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RouVideo;
|
||||||
186
anime/xvideos.js
Normal file
186
anime/xvideos.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
class Xvideos {
|
||||||
|
constructor() {
|
||||||
|
this.baseUrl = "https://www.xvideos.com";
|
||||||
|
this.type = "anime-board";
|
||||||
|
this.version = "1.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
getSettings() {
|
||||||
|
return {
|
||||||
|
episodeServers: ["Default"],
|
||||||
|
supportsDub: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_encodeId(href) {
|
||||||
|
if (!href) return "";
|
||||||
|
let id = href.startsWith("/") ? href.substring(1) : href;
|
||||||
|
return id.replace(/\//g, "___");
|
||||||
|
}
|
||||||
|
|
||||||
|
_decodeId(id) {
|
||||||
|
if (!id) return "";
|
||||||
|
return id.replace(/___/g, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
async search({ query, tag = "", page = 1 }) {
|
||||||
|
let url;
|
||||||
|
if (query && query.trim()) {
|
||||||
|
url = `${this.baseUrl}/?k=${encodeURIComponent(query)}&p=${page}`;
|
||||||
|
} else if (tag) {
|
||||||
|
url = `${this.baseUrl}/tags/${tag}/${page}`;
|
||||||
|
} else {
|
||||||
|
url = `${this.baseUrl}/new/${page}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url);
|
||||||
|
const html = await res.text();
|
||||||
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
|
const items = $("div#main div#content div.mozaique.cust-nb-cols > div").toArray();
|
||||||
|
|
||||||
|
const results = items.map(el => {
|
||||||
|
const a = $(el).find("div.thumb-inside div.thumb a");
|
||||||
|
const img = $(el).find("div.thumb-inside div.thumb a img");
|
||||||
|
|
||||||
|
const href = a.attr("href");
|
||||||
|
if (!href) return null;
|
||||||
|
|
||||||
|
const safeId = this._encodeId(href);
|
||||||
|
const thumbnail = img.attr("data-src") || img.attr("src") || "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: safeId,
|
||||||
|
|
||||||
|
title: $(el).find("div.thumb-under p.title").text().trim(),
|
||||||
|
url: this.baseUrl + href,
|
||||||
|
|
||||||
|
image: thumbnail,
|
||||||
|
subOrDub: "sub",
|
||||||
|
};
|
||||||
|
}).filter(i => i !== null);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ERROR] Fallo en Search:", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMetadata(id) {
|
||||||
|
const realPath = this._decodeId(id);
|
||||||
|
const fetchUrl = `${this.baseUrl}/${realPath}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(fetchUrl);
|
||||||
|
if (res.status === 404) throw new Error("Video no encontrado (404)");
|
||||||
|
|
||||||
|
const html = await res.text();
|
||||||
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
|
const title = $("h2.page-title").text().trim();
|
||||||
|
const genres = $("div.video-metadata ul li a span").toArray().map(e => $(e).text());
|
||||||
|
|
||||||
|
let image = "";
|
||||||
|
|
||||||
|
image = $('meta[property="og:image"]').attr('content');
|
||||||
|
|
||||||
|
if (!image) {
|
||||||
|
const regex = /html5player\.setThumbUrl\(\s*['"]([^'"]+)['"]/;
|
||||||
|
const match = html.match(regex);
|
||||||
|
if (match) image = match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: title || "Unknown Title",
|
||||||
|
summary: "",
|
||||||
|
episodes: 1,
|
||||||
|
characters: [],
|
||||||
|
season: "",
|
||||||
|
status: "Completed",
|
||||||
|
studio: "",
|
||||||
|
score: null,
|
||||||
|
year: null,
|
||||||
|
genres: genres,
|
||||||
|
image: image || "",
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ERROR] Fallo en getMetadata:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findEpisodes(id) {
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: "video_main",
|
||||||
|
number: 1,
|
||||||
|
title: "Video",
|
||||||
|
url: id,
|
||||||
|
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async findEpisodeServer(episode, server, category = "sub") {
|
||||||
|
const realPath = this._decodeId(episode.url);
|
||||||
|
const videoUrl = `${this.baseUrl}/${realPath}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(videoUrl);
|
||||||
|
const html = await res.text();
|
||||||
|
|
||||||
|
const videoSources = [];
|
||||||
|
|
||||||
|
const extract = (key) => {
|
||||||
|
const regex = new RegExp(`${key}\\s*\\(\\s*['"]([^'"]+)['"]`);
|
||||||
|
const match = html.match(regex);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const low = extract("html5player.setVideoUrlLow");
|
||||||
|
const high = extract("html5player.setVideoUrlHigh");
|
||||||
|
const hls = extract("html5player.setVideoHLS");
|
||||||
|
|
||||||
|
if (hls) {
|
||||||
|
videoSources.push({
|
||||||
|
url: hls,
|
||||||
|
type: "m3u8",
|
||||||
|
quality: "Auto",
|
||||||
|
subOrDub: category
|
||||||
|
});
|
||||||
|
} else if (high) {
|
||||||
|
videoSources.push({
|
||||||
|
url: high,
|
||||||
|
type: "mp4",
|
||||||
|
quality: "High",
|
||||||
|
subOrDub: category
|
||||||
|
});
|
||||||
|
} else if (low) {
|
||||||
|
videoSources.push({
|
||||||
|
url: low,
|
||||||
|
type: "mp4",
|
||||||
|
quality: "Low",
|
||||||
|
subOrDub: category
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoSources.length === 0) throw new Error("No sources found");
|
||||||
|
|
||||||
|
return {
|
||||||
|
server: server || "Default",
|
||||||
|
headers: { Referer: this.baseUrl },
|
||||||
|
videoSources: videoSources,
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ERROR] Fallo en findEpisodeServer:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Xvideos;
|
||||||
@@ -2,13 +2,50 @@ class asmhentai {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.baseUrl = "https://asmhentai.com";
|
this.baseUrl = "https://asmhentai.com";
|
||||||
this.type = "book-board";
|
this.type = "book-board";
|
||||||
this.version = "1.1"
|
this.version = "1.2";
|
||||||
this.mediaType = "manga";
|
this.mediaType = "manga";
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(queryObj) {
|
getFilters() {
|
||||||
const q = (queryObj.query || "").trim().replace(/\s+/g, "+");
|
return {
|
||||||
const html = await fetch(`${this.baseUrl}/search/?q=${q}&page=1`).then(r => r.text());
|
sort: {
|
||||||
|
label: "Order",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "latest", label: "Latest" },
|
||||||
|
{ value: "popular", label: "Popular" }
|
||||||
|
],
|
||||||
|
default: "latest"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async search({ query, page = 1, filters }) {
|
||||||
|
let url;
|
||||||
|
|
||||||
|
if (query && query.trim().length > 0) {
|
||||||
|
|
||||||
|
const q = query.trim().replace(/\s+/g, "+");
|
||||||
|
url = `${this.baseUrl}/search/?q=${q}&page=${page}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
const sort = filters?.sort || "latest";
|
||||||
|
|
||||||
|
if (sort === "popular") {
|
||||||
|
|
||||||
|
url = page === 1
|
||||||
|
? `${this.baseUrl}/popular/`
|
||||||
|
: `${this.baseUrl}/popular/pag/${page}/`;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
url = page === 1
|
||||||
|
? `${this.baseUrl}/`
|
||||||
|
: `${this.baseUrl}/pag/${page}/`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await fetch(url).then(r => r.text());
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
@@ -16,6 +53,7 @@ class asmhentai {
|
|||||||
$(".preview_item").each((_, el) => {
|
$(".preview_item").each((_, el) => {
|
||||||
const href = $(el).find(".image a").attr("href");
|
const href = $(el).find(".image a").attr("href");
|
||||||
const id = href?.match(/\/g\/(\d+)\//)?.[1];
|
const id = href?.match(/\/g\/(\d+)\//)?.[1];
|
||||||
|
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
let img = $(el).find(".image img").attr("data-src") || $(el).find(".image img").attr("src") || "";
|
let img = $(el).find(".image img").attr("data-src") || $(el).find(".image img").attr("src") || "";
|
||||||
@@ -49,7 +87,7 @@ class asmhentai {
|
|||||||
|
|
||||||
const genres = $(".tags a.tag")
|
const genres = $(".tags a.tag")
|
||||||
.map((_, el) => $(el).text().trim())
|
.map((_, el) => $(el).text().trim())
|
||||||
.get()
|
.get();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@@ -111,14 +149,10 @@ class asmhentai {
|
|||||||
return url;
|
return url;
|
||||||
})
|
})
|
||||||
.filter(url => {
|
.filter(url => {
|
||||||
// Mantenemos el filtro que te funcionó
|
|
||||||
return url && url.includes("images.") && !url.includes("/images/");
|
return url && url.includes("images.") && !url.includes("/images/");
|
||||||
})
|
})
|
||||||
.map((url, i) => {
|
.map((url, i) => {
|
||||||
// Reemplazamos "thumb" por el número del índice + 1
|
|
||||||
// Ejemplo: .../thumb.jpg -> .../1.jpg
|
|
||||||
const newUrl = url.replace("thumb", (i + 1).toString());
|
const newUrl = url.replace("thumb", (i + 1).toString());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
index: i,
|
index: i,
|
||||||
url: newUrl
|
url: newUrl
|
||||||
|
|||||||
240
book/comix.js
240
book/comix.js
@@ -3,7 +3,7 @@ class Comix {
|
|||||||
this.baseUrl = "https://comix.to";
|
this.baseUrl = "https://comix.to";
|
||||||
this.apiUrl = "https://comix.to/api/v2";
|
this.apiUrl = "https://comix.to/api/v2";
|
||||||
this.type = "book-board";
|
this.type = "book-board";
|
||||||
this.version = "1.0";
|
this.version = "1.1";
|
||||||
this.mediaType = "manga";
|
this.mediaType = "manga";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,18 +14,212 @@ class Comix {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilters() {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const years = [];
|
||||||
|
for (let i = currentYear; i >= 1990; i--) {
|
||||||
|
years.push({ value: i.toString(), label: i.toString() });
|
||||||
|
}
|
||||||
|
years.push({ value: "older", label: "Older" });
|
||||||
|
|
||||||
|
return {
|
||||||
|
sort: {
|
||||||
|
label: "Sort By",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "relevance", label: "Best Match" },
|
||||||
|
{ value: "views_30d", label: "Popular (30 days)" },
|
||||||
|
{ value: "views_total", label: "Total Views" },
|
||||||
|
{ value: "chapter_updated_at", label: "Latest Updates" },
|
||||||
|
{ value: "created_at", label: "Created Date" },
|
||||||
|
{ value: "title", label: "Title" },
|
||||||
|
{ value: "year", label: "Year" },
|
||||||
|
{ value: "follows_total", label: "Most Follows" }
|
||||||
|
],
|
||||||
|
default: "views_30d"
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: "Status",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "finished", label: "Finished" },
|
||||||
|
{ value: "releasing", label: "Releasing" },
|
||||||
|
{ value: "on_hiatus", label: "On Hiatus" },
|
||||||
|
{ value: "discontinued", label: "Discontinued" },
|
||||||
|
{ value: "not_yet_released", label: "Not Yet Released" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
label: "Type",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "manga", label: "Manga" },
|
||||||
|
{ value: "manhwa", label: "Manhwa" },
|
||||||
|
{ value: "manhua", label: "Manhua" },
|
||||||
|
{ value: "other", label: "Other" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
demographic: {
|
||||||
|
label: "Demographic",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "1", label: "Shoujo" },
|
||||||
|
{ value: "2", label: "Shounen" },
|
||||||
|
{ value: "3", label: "Josei" },
|
||||||
|
{ value: "4", label: "Seinen" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
genre: {
|
||||||
|
label: "Genres",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "6", label: "Action" },
|
||||||
|
{ value: "87264", label: "Adult" },
|
||||||
|
{ value: "7", label: "Adventure" },
|
||||||
|
{ value: "8", label: "Boys Love" },
|
||||||
|
{ value: "9", label: "Comedy" },
|
||||||
|
{ value: "10", label: "Crime" },
|
||||||
|
{ value: "11", label: "Drama" },
|
||||||
|
{ value: "87265", label: "Ecchi" },
|
||||||
|
{ value: "12", label: "Fantasy" },
|
||||||
|
{ value: "13", label: "Girls Love" },
|
||||||
|
{ value: "87266", label: "Hentai" },
|
||||||
|
{ value: "14", label: "Historical" },
|
||||||
|
{ value: "15", label: "Horror" },
|
||||||
|
{ value: "16", label: "Isekai" },
|
||||||
|
{ value: "17", label: "Magical Girls" },
|
||||||
|
{ value: "87267", label: "Mature" },
|
||||||
|
{ value: "18", label: "Mecha" },
|
||||||
|
{ value: "19", label: "Medical" },
|
||||||
|
{ value: "20", label: "Mystery" },
|
||||||
|
{ value: "21", label: "Philosophical" },
|
||||||
|
{ value: "22", label: "Psychological" },
|
||||||
|
{ value: "23", label: "Romance" },
|
||||||
|
{ value: "24", label: "Sci-Fi" },
|
||||||
|
{ value: "25", label: "Slice of Life" },
|
||||||
|
{ value: "87268", label: "Smut" },
|
||||||
|
{ value: "26", label: "Sports" },
|
||||||
|
{ value: "27", label: "Superhero" },
|
||||||
|
{ value: "28", label: "Thriller" },
|
||||||
|
{ value: "29", label: "Tragedy" },
|
||||||
|
{ value: "30", label: "Wuxia" },
|
||||||
|
{ value: "31", label: "Aliens" },
|
||||||
|
{ value: "32", label: "Animals" },
|
||||||
|
{ value: "33", label: "Cooking" },
|
||||||
|
{ value: "34", label: "Cross Dressing" },
|
||||||
|
{ value: "35", label: "Delinquents" },
|
||||||
|
{ value: "36", label: "Demons" },
|
||||||
|
{ value: "37", label: "Genderswap" },
|
||||||
|
{ value: "38", label: "Ghosts" },
|
||||||
|
{ value: "39", label: "Gyaru" },
|
||||||
|
{ value: "40", label: "Harem" },
|
||||||
|
{ value: "41", label: "Incest" },
|
||||||
|
{ value: "42", label: "Loli" },
|
||||||
|
{ value: "43", label: "Mafia" },
|
||||||
|
{ value: "44", label: "Magic" },
|
||||||
|
{ value: "45", label: "Martial Arts" },
|
||||||
|
{ value: "46", label: "Military" },
|
||||||
|
{ value: "47", label: "Monster Girls" },
|
||||||
|
{ value: "48", label: "Monsters" },
|
||||||
|
{ value: "49", label: "Music" },
|
||||||
|
{ value: "50", label: "Ninja" },
|
||||||
|
{ value: "51", label: "Office Workers" },
|
||||||
|
{ value: "52", label: "Police" },
|
||||||
|
{ value: "53", label: "Post-Apocalyptic" },
|
||||||
|
{ value: "54", label: "Reincarnation" },
|
||||||
|
{ value: "55", label: "Reverse Harem" },
|
||||||
|
{ value: "56", label: "Samurai" },
|
||||||
|
{ value: "57", label: "School Life" },
|
||||||
|
{ value: "58", label: "Shota" },
|
||||||
|
{ value: "59", label: "Supernatural" },
|
||||||
|
{ value: "60", label: "Survival" },
|
||||||
|
{ value: "61", label: "Time Travel" },
|
||||||
|
{ value: "62", label: "Traditional Games" },
|
||||||
|
{ value: "63", label: "Vampires" },
|
||||||
|
{ value: "64", label: "Video Games" },
|
||||||
|
{ value: "65", label: "Villainess" },
|
||||||
|
{ value: "66", label: "Virtual Reality" },
|
||||||
|
{ value: "67", label: "Zombies" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
year_from: {
|
||||||
|
label: "Year From",
|
||||||
|
type: "select",
|
||||||
|
options: [{ value: "", label: "Any" }, ...years]
|
||||||
|
},
|
||||||
|
year_to: {
|
||||||
|
label: "Year To",
|
||||||
|
type: "select",
|
||||||
|
options: [{ value: "", label: "Any" }, ...years]
|
||||||
|
},
|
||||||
|
min_chap: {
|
||||||
|
label: "Min. Chapters",
|
||||||
|
type: "number",
|
||||||
|
placeholder: "e.g. 10"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async search(queryObj) {
|
async search(queryObj) {
|
||||||
const q = (queryObj.query || "").trim().replace(/\s+/g, "+");
|
|
||||||
|
const { query, filters, page } = queryObj;
|
||||||
|
const q = (query || "").trim().replace(/\s+/g, "+");
|
||||||
|
const pageNum = page || 1;
|
||||||
|
|
||||||
const url = new URL(`${this.apiUrl}/manga`);
|
const url = new URL(`${this.apiUrl}/manga`);
|
||||||
|
|
||||||
|
let sortMode = "views_30d";
|
||||||
|
|
||||||
|
if (filters) {
|
||||||
|
|
||||||
|
if (filters.sort) {
|
||||||
|
sortMode = filters.sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.genre) {
|
||||||
|
const genres = String(filters.genre).split(',');
|
||||||
|
genres.forEach(g => {
|
||||||
|
if (g.trim()) url.searchParams.append("genres[]", g.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.status) {
|
||||||
|
const statuses = String(filters.status).split(',');
|
||||||
|
statuses.forEach(s => {
|
||||||
|
if (s.trim()) url.searchParams.append("statuses[]", s.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.type) {
|
||||||
|
const types = String(filters.type).split(',');
|
||||||
|
types.forEach(t => {
|
||||||
|
if (t.trim()) url.searchParams.append("types[]", t.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.demographic) {
|
||||||
|
const demos = String(filters.demographic).split(',');
|
||||||
|
demos.forEach(d => {
|
||||||
|
if (d.trim()) url.searchParams.append("demographics[]", d.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.year_from) url.searchParams.set("release_year[from]", filters.year_from);
|
||||||
|
if (filters.year_to) url.searchParams.set("release_year[to]", filters.year_to);
|
||||||
|
if (filters.min_chap) url.searchParams.set("min_chap", filters.min_chap);
|
||||||
|
}
|
||||||
|
|
||||||
if (q) {
|
if (q) {
|
||||||
url.searchParams.set("keyword", q);
|
url.searchParams.set("keyword", q);
|
||||||
url.searchParams.set("order[relevance]", "desc");
|
|
||||||
} else {
|
sortMode = "relevance";
|
||||||
url.searchParams.set("order[views_30d]", "desc");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const orderDirection = (sortMode === "title") ? "asc" : "desc";
|
||||||
|
url.searchParams.set(`order[${sortMode}]`, orderDirection);
|
||||||
|
|
||||||
url.searchParams.set("limit", "50");
|
url.searchParams.set("limit", "50");
|
||||||
url.searchParams.set("page", "1");
|
url.searchParams.set("page", pageNum.toString());
|
||||||
|
|
||||||
const res = await fetch(url, { headers: this.headers });
|
const res = await fetch(url, { headers: this.headers });
|
||||||
if (!res.ok) throw new Error(`Search failed: ${res.status}`);
|
if (!res.ok) throw new Error(`Search failed: ${res.status}`);
|
||||||
@@ -37,12 +231,15 @@ class Comix {
|
|||||||
title: m.title,
|
title: m.title,
|
||||||
image: m.poster?.large || null,
|
image: m.poster?.large || null,
|
||||||
rating: m.score ?? null,
|
rating: m.score ?? null,
|
||||||
type: "book"
|
type: "book",
|
||||||
|
|
||||||
|
year: m.year || null,
|
||||||
|
status: m.status || null
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMetadata(id) {
|
async getMetadata(id) {
|
||||||
const url = `${this.apiUrl}/manga/${id}?includes[]=genre&includes[]=author&includes[]=artist`;
|
const url = `${this.apiUrl}/manga/${id}?includes[]=genre&includes[]=author&includes[]=artist&includes[]=demographic`;
|
||||||
|
|
||||||
const res = await fetch(url, { headers: this.headers });
|
const res = await fetch(url, { headers: this.headers });
|
||||||
if (!res.ok) throw new Error(`Metadata failed: ${res.status}`);
|
if (!res.ok) throw new Error(`Metadata failed: ${res.status}`);
|
||||||
@@ -99,12 +296,13 @@ class Comix {
|
|||||||
if (ch.language !== "en") continue;
|
if (ch.language !== "en") continue;
|
||||||
|
|
||||||
const key = ch.number;
|
const key = ch.number;
|
||||||
|
|
||||||
if (!map.has(key) || ch.is_official === 1) {
|
if (!map.has(key) || ch.is_official === 1) {
|
||||||
map.set(key, ch);
|
map.set(key, ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(map.values())
|
const chapters = Array.from(map.values())
|
||||||
.sort((a, b) => a.number - b.number)
|
.sort((a, b) => a.number - b.number)
|
||||||
.map((ch, i) => ({
|
.map((ch, i) => ({
|
||||||
id: `${mangaId}|${slug}|${ch.chapter_id}|${ch.number}`,
|
id: `${mangaId}|${slug}|${ch.chapter_id}|${ch.number}`,
|
||||||
@@ -115,33 +313,27 @@ class Comix {
|
|||||||
releaseDate: ch.updated_at ?? null,
|
releaseDate: ch.updated_at ?? null,
|
||||||
index: i
|
index: i
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
return chapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findChapterPages(chapterId) {
|
async findChapterPages(chapterId) {
|
||||||
const parts = chapterId.split("|");
|
const parts = chapterId.split("|");
|
||||||
console.log(parts)
|
|
||||||
if (parts.length < 4) return [];
|
if (parts.length < 4) return [];
|
||||||
|
|
||||||
const [hashId, slug, chapterRealId, number] = parts;
|
const [hashId, slug, chapterRealId, number] = parts;
|
||||||
|
|
||||||
const readerUrl = `${this.baseUrl}/title/${hashId}-${slug}/${chapterRealId}-chapter-${number}`;
|
const readerUrl = `${this.baseUrl}/title/${hashId}-${slug}/${chapterRealId}-chapter-${number}`;
|
||||||
|
|
||||||
const res = await fetch(readerUrl, { headers: this.headers });
|
const apiUrl = `${this.apiUrl}/chapters/${chapterRealId}`;
|
||||||
|
|
||||||
|
const res = await fetch(apiUrl, { headers: this.headers });
|
||||||
if (!res.ok) return [];
|
if (!res.ok) return [];
|
||||||
|
|
||||||
const html = await res.text();
|
const json = await res.json();
|
||||||
|
if (!json.result || !json.result.images) return [];
|
||||||
|
|
||||||
const regex = /["\\]*images["\\]*\s*:\s*(\[[^\]]*\])/s;
|
return json.result.images.map((img, i) => ({
|
||||||
const match = html.match(regex);
|
|
||||||
if (!match?.[1]) return [];
|
|
||||||
|
|
||||||
let images;
|
|
||||||
try {
|
|
||||||
images = JSON.parse(match[1]);
|
|
||||||
} catch {
|
|
||||||
images = JSON.parse(match[1].replace(/\\"/g, '"'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return images.map((img, i) => ({
|
|
||||||
url: img.url,
|
url: img.url,
|
||||||
index: i,
|
index: i,
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
238
book/mangadex.js
238
book/mangadex.js
@@ -4,7 +4,7 @@ class MangaDex {
|
|||||||
this.apiUrl = "https://api.mangadex.org";
|
this.apiUrl = "https://api.mangadex.org";
|
||||||
this.type = "book-board";
|
this.type = "book-board";
|
||||||
this.mediaType = "manga";
|
this.mediaType = "manga";
|
||||||
this.version = "1.0"
|
this.version = "1.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeaders() {
|
getHeaders() {
|
||||||
@@ -14,15 +14,226 @@ class MangaDex {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(queryObj) {
|
getFilters() {
|
||||||
const query = queryObj.query?.trim() || "";
|
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 limit = 25;
|
||||||
const offset = (1 - 1) * limit;
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
const url = `${this.apiUrl}/manga?title=${encodeURIComponent(query)}&limit=${limit}&offset=${offset}&includes[]=cover_art&contentRating[]=safe&contentRating[]=suggestive&availableTranslatedLanguage[]=en`;
|
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 {
|
try {
|
||||||
const response = await fetch(url, { headers: this.getHeaders() });
|
const response = await fetch(url.toString(), { headers: this.getHeaders() });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error(`MangaDex API Error: ${response.statusText}`);
|
console.error(`MangaDex API Error: ${response.statusText}`);
|
||||||
return [];
|
return [];
|
||||||
@@ -45,7 +256,6 @@ class MangaDex {
|
|||||||
? `https://uploads.mangadex.org/covers/${manga.id}/${coverFileName}.256.jpg`
|
? `https://uploads.mangadex.org/covers/${manga.id}/${coverFileName}.256.jpg`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: manga.id,
|
id: manga.id,
|
||||||
image: coverUrl,
|
image: coverUrl,
|
||||||
@@ -94,8 +304,6 @@ class MangaDex {
|
|||||||
? `https://uploads.mangadex.org/covers/${id}/${coverFile}.512.jpg`
|
? `https://uploads.mangadex.org/covers/${id}/${coverFile}.512.jpg`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const score100 = 0;
|
|
||||||
|
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
ongoing: "Ongoing",
|
ongoing: "Ongoing",
|
||||||
completed: "Completed",
|
completed: "Completed",
|
||||||
@@ -107,7 +315,7 @@ class MangaDex {
|
|||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
format: "Manga",
|
format: "Manga",
|
||||||
score: score100,
|
score: 0,
|
||||||
genres,
|
genres,
|
||||||
status: statusMap[attr.status] || "",
|
status: statusMap[attr.status] || "",
|
||||||
published: attr.year ? String(attr.year) : "???",
|
published: attr.year ? String(attr.year) : "???",
|
||||||
@@ -141,7 +349,7 @@ class MangaDex {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(url, { headers: this.getHeaders() });
|
const response = await fetch(url, { headers: this.getHeaders() });
|
||||||
let chapters = [];
|
let chapters = [];
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json && Array.isArray(json.data)) {
|
if (json && Array.isArray(json.data)) {
|
||||||
@@ -154,7 +362,7 @@ class MangaDex {
|
|||||||
index: index,
|
index: index,
|
||||||
language: ch.attributes.translatedLanguage
|
language: ch.attributes.translatedLanguage
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const seenChapters = new Set();
|
const seenChapters = new Set();
|
||||||
allChapters.forEach(ch => {
|
allChapters.forEach(ch => {
|
||||||
if (!seenChapters.has(ch.chapter)) {
|
if (!seenChapters.has(ch.chapter)) {
|
||||||
@@ -170,7 +378,7 @@ class MangaDex {
|
|||||||
return chapters;
|
return chapters;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error finding MangaDex chapters:", e);
|
console.error("Error finding MangaDex chapters:", e);
|
||||||
return { chapters: [], cover: null };
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +396,7 @@ class MangaDex {
|
|||||||
|
|
||||||
const baseUrl = json.baseUrl;
|
const baseUrl = json.baseUrl;
|
||||||
const chapterHash = json.chapter.hash;
|
const chapterHash = json.chapter.hash;
|
||||||
const imageFilenames = json.chapter.data;
|
const imageFilenames = json.chapter.data;
|
||||||
|
|
||||||
return imageFilenames.map((filename, index) => ({
|
return imageFilenames.map((filename, index) => ({
|
||||||
url: `${baseUrl}/data/${chapterHash}/${filename}`,
|
url: `${baseUrl}/data/${chapterHash}/${filename}`,
|
||||||
|
|||||||
@@ -2,47 +2,162 @@ class MangaPill {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.baseUrl = "https://mangapill.com";
|
this.baseUrl = "https://mangapill.com";
|
||||||
this.type = "book-board";
|
this.type = "book-board";
|
||||||
this.version = "1.0";
|
this.version = "1.1";
|
||||||
this.mediaType = "manga";
|
this.mediaType = "manga";
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(url) {
|
getFilters() {
|
||||||
return fetch(url, {
|
return {
|
||||||
headers: {
|
type: {
|
||||||
"User-Agent": "Mozilla/5.0",
|
label: "Type",
|
||||||
Referer: this.baseUrl,
|
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) {
|
get headers() {
|
||||||
const q = queryObj.query || "";
|
return {
|
||||||
const res = await this.fetch(
|
"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",
|
||||||
`${this.baseUrl}/search?q=${encodeURIComponent(q)}`
|
"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 html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
|
|
||||||
$("div.container div.my-3.justify-end > div").each((_, el) => {
|
// Selector actualizado basado en la extensión de Kotlin (.grid > div:not([class]))
|
||||||
const link = $(el).find("a").attr("href");
|
// Buscamos dentro del grid de resultados
|
||||||
if (!link) return;
|
$(".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, "$");
|
// Extraer ID (manga/123/title -> 123$title)
|
||||||
const title = $(el).find("div > a > div.mt-3").text().trim();
|
const parts = href.split("/manga/");
|
||||||
const image = $(el).find("a img").attr("data-src") || null;
|
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({
|
results.push({
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
image,
|
image: img || "",
|
||||||
rating: null,
|
type: "book"
|
||||||
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) {
|
async getMetadata(id) {
|
||||||
@@ -52,83 +167,55 @@ class MangaPill {
|
|||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
});
|
});
|
||||||
|
|
||||||
// follow redirect manually
|
|
||||||
if (res.status === 301 || res.status === 302) {
|
if (res.status === 301 || res.status === 302) {
|
||||||
const loc = res.headers.get("location");
|
const loc = res.headers.get("location");
|
||||||
if (loc) {
|
if (loc) {
|
||||||
res = await fetch(`${this.baseUrl}${loc}`, {
|
res = await fetch(`${this.baseUrl}${loc}`, { headers: this.headers });
|
||||||
headers: this.headers,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) throw new Error("Failed to fetch metadata");
|
||||||
return {
|
|
||||||
id,
|
|
||||||
title: "",
|
|
||||||
format: "MANGA",
|
|
||||||
score: 0,
|
|
||||||
genres: "",
|
|
||||||
status: "unknown",
|
|
||||||
published: "",
|
|
||||||
summary: "",
|
|
||||||
chapters: "???",
|
|
||||||
image: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const title = $("h1.font-bold").first().text().trim();
|
const title = $("h1.font-bold").first().text().trim();
|
||||||
|
const summary = $("div.mb-3 p.text-sm").first().text().trim() || "";
|
||||||
|
|
||||||
const summary =
|
// Status y Published suelen estar en labels
|
||||||
$("div.mb-3 p.text-sm").first().text().trim() || "";
|
const status = $("label:contains('Status')").next("div").text().trim().toLowerCase() || "unknown";
|
||||||
|
const published = $("label:contains('Year')").next("div").text().trim() || "";
|
||||||
const status =
|
|
||||||
$("label:contains('Status')")
|
|
||||||
.next("div")
|
|
||||||
.text()
|
|
||||||
.trim() || "unknown";
|
|
||||||
|
|
||||||
const published =
|
|
||||||
$("label:contains('Year')")
|
|
||||||
.next("div")
|
|
||||||
.text()
|
|
||||||
.trim() || "";
|
|
||||||
|
|
||||||
const genres = [];
|
const genres = [];
|
||||||
$("label:contains('Genres')")
|
$("a[href*='genre']").each((_, el) => {
|
||||||
.parent()
|
genres.push($(el).text().trim());
|
||||||
.find("a")
|
});
|
||||||
.each((_, a) => genres.push($(a).text().trim()));
|
|
||||||
|
|
||||||
const image =
|
const image = $("img[data-src]").first().attr("data-src") || "";
|
||||||
$("img[data-src]").first().attr("data-src") || null;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
format: "MANGA",
|
format: "MANGA",
|
||||||
score: 0,
|
score: 0,
|
||||||
genres: genres.join(", "),
|
genres: genres, // Array de strings
|
||||||
status,
|
status,
|
||||||
published,
|
published,
|
||||||
summary,
|
summary,
|
||||||
chapters: "???",
|
chapters: 0, // Se calcula dinámicamente si es necesario
|
||||||
image
|
image
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async findChapters(mangaId) {
|
async findChapters(mangaId) {
|
||||||
const uriId = mangaId.replace(/\$/g, "/");
|
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 html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const chapters = [];
|
const chapters = [];
|
||||||
|
|
||||||
$("div#chapters a").each((_, el) => {
|
$("#chapters > div > a").each((_, el) => {
|
||||||
const href = $(el).attr("href");
|
const href = $(el).attr("href");
|
||||||
if (!href) return;
|
if (!href) return;
|
||||||
|
|
||||||
@@ -153,24 +240,22 @@ class MangaPill {
|
|||||||
|
|
||||||
async findChapterPages(chapterId) {
|
async findChapterPages(chapterId) {
|
||||||
const uriId = chapterId.replace(/\$/g, "/");
|
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 html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const pages = [];
|
const pages = [];
|
||||||
|
|
||||||
$("chapter-page").each((i, el) => {
|
$("picture img").each((i, el) => {
|
||||||
const img = $(el).find("div picture img").attr("data-src");
|
const img = $(el).attr("data-src");
|
||||||
if (!img) return;
|
if (!img) return;
|
||||||
|
|
||||||
pages.push({
|
pages.push({
|
||||||
url: img,
|
url: img,
|
||||||
index: i,
|
index: i,
|
||||||
headers: {
|
headers: {
|
||||||
Referer: "https://mangapill.com/",
|
"Referer": this.baseUrl + "/",
|
||||||
Origin: "https://mangapill.com",
|
"User-Agent": this.headers["User-Agent"]
|
||||||
"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",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
115
book/nhentai.js
115
book/nhentai.js
@@ -2,10 +2,67 @@ class NHentai {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.baseUrl = "https://nhentai.net";
|
this.baseUrl = "https://nhentai.net";
|
||||||
this.type = "book-board";
|
this.type = "book-board";
|
||||||
this.version = "1.1";
|
this.version = "1.2";
|
||||||
this.mediaType = "manga";
|
this.mediaType = "manga";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilters() {
|
||||||
|
return {
|
||||||
|
sort: {
|
||||||
|
label: "Order",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "date", label: "Recent" },
|
||||||
|
{ value: "popular", label: "Popular: All time" },
|
||||||
|
{ value: "popular-month", label: "Popular: Month" },
|
||||||
|
{ value: "popular-week", label: "Popular: Week" },
|
||||||
|
{ value: "popular-today", label: "Popular: Today" }
|
||||||
|
],
|
||||||
|
default: "date"
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
label: "Tags (separated by comma)",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "ej. big breasts, stocking"
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
label: "Categories",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "ej. doujinshi, manga"
|
||||||
|
},
|
||||||
|
groups: {
|
||||||
|
label: "Groups",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "ej. fakku"
|
||||||
|
},
|
||||||
|
artists: {
|
||||||
|
label: "Artists",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "ej. shindo l"
|
||||||
|
},
|
||||||
|
parodies: {
|
||||||
|
label: "Parodies",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "ej. naruto"
|
||||||
|
},
|
||||||
|
characters: {
|
||||||
|
label: "Characters",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "ej. sakura haruno"
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
label: "Pages (ej. >20)",
|
||||||
|
type: "text",
|
||||||
|
placeholder: ">20"
|
||||||
|
},
|
||||||
|
uploaded: {
|
||||||
|
label: "Uploaded (ej. >20d)",
|
||||||
|
type: "text",
|
||||||
|
placeholder: ">20d"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
shortenTitle(title) {
|
shortenTitle(title) {
|
||||||
return title.replace(/(\[[^]]*]|[({][^)}]*[)}])/g, "").trim();
|
return title.replace(/(\[[^]]*]|[({][^)}]*[)}])/g, "").trim();
|
||||||
}
|
}
|
||||||
@@ -26,16 +83,62 @@ class NHentai {
|
|||||||
return JSON.parse(unicodeFixed);
|
return JSON.parse(unicodeFixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
async search({ query = "", page = 1 }) {
|
async search({ query = "", page = 1, filters = null }) {
|
||||||
if (query.startsWith("id:") || (!isNaN(query) && query.length <= 7)) {
|
|
||||||
|
if (query.startsWith("id:") || (!isNaN(query) && query.length <= 7 && query.length > 0)) {
|
||||||
return [await this.getMetadata(this.parseId(query))];
|
return [await this.getMetadata(this.parseId(query))];
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `${this.baseUrl}/search/?q=${encodeURIComponent(query)}&page=${page}`;
|
let advQuery = "";
|
||||||
|
let sortParam = "";
|
||||||
|
|
||||||
|
if (filters) {
|
||||||
|
|
||||||
|
const textFilters = [
|
||||||
|
{ key: "tags", prefix: "tag" },
|
||||||
|
{ key: "categories", prefix: "category" },
|
||||||
|
{ key: "groups", prefix: "group" },
|
||||||
|
{ key: "artists", prefix: "artist" },
|
||||||
|
{ key: "parodies", prefix: "parody" },
|
||||||
|
{ key: "characters", prefix: "character" },
|
||||||
|
{ key: "uploaded", prefix: "uploaded", noQuote: true },
|
||||||
|
{ key: "pages", prefix: "pages", noQuote: true }
|
||||||
|
];
|
||||||
|
|
||||||
|
textFilters.forEach(({ key, prefix, noQuote }) => {
|
||||||
|
if (filters[key]) {
|
||||||
|
const terms = filters[key].split(",");
|
||||||
|
terms.forEach(term => {
|
||||||
|
const t = term.trim();
|
||||||
|
if (!t) return;
|
||||||
|
|
||||||
|
let currentPrefix = prefix;
|
||||||
|
let currentTerm = t;
|
||||||
|
let isExclusion = false;
|
||||||
|
|
||||||
|
if (t.startsWith("-")) {
|
||||||
|
isExclusion = true;
|
||||||
|
currentTerm = t.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
advQuery += ` ${isExclusion ? "-" : ""}${currentPrefix}:`;
|
||||||
|
advQuery += noQuote ? currentTerm : `"${currentTerm}"`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filters.sort && filters.sort !== "date") {
|
||||||
|
sortParam = `&sort=${filters.sort}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalQuery = (query + " " + advQuery).trim() || '""';
|
||||||
|
|
||||||
|
const url = `${this.baseUrl}/search/?q=${encodeURIComponent(finalQuery)}&page=${page}${sortParam}`;
|
||||||
|
|
||||||
const { result } = await this.scrape(
|
const { result } = await this.scrape(
|
||||||
url,
|
url,
|
||||||
page =>
|
page => page.evaluate(() => document.documentElement.innerHTML),
|
||||||
page.evaluate(() => document.documentElement.innerHTML),
|
|
||||||
{ waitSelector: ".gallery" }
|
{ waitSelector: ".gallery" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,663 @@ class NovelFire {
|
|||||||
this.baseUrl = "https://novelfire.net";
|
this.baseUrl = "https://novelfire.net";
|
||||||
this.type = "book-board";
|
this.type = "book-board";
|
||||||
this.mediaType = "ln";
|
this.mediaType = "ln";
|
||||||
this.version = "1.1"
|
this.version = "1.2";
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(queryObj) {
|
getFilters() {
|
||||||
const query = queryObj.query;
|
return {
|
||||||
|
sort: {
|
||||||
|
label: "Sort By",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "bookmark", label: "Bookmark Count (Most)" },
|
||||||
|
{ value: "date", label: "Last Updated (Newest)" },
|
||||||
|
{ value: "rank-top", label: "Rank (Top)" },
|
||||||
|
{ value: "rating-score-top", label: "Rating Score (Top)" },
|
||||||
|
{ value: "review", label: "Review Count (Most)" },
|
||||||
|
{ value: "comment", label: "Comment Count (Most)" },
|
||||||
|
{ value: "today-view", label: "Today Views (Most)" },
|
||||||
|
{ value: "monthly-view", label: "Monthly Views (Most)" },
|
||||||
|
{ value: "total-view", label: "Total Views (Most)" },
|
||||||
|
{ value: "chapter-count-most", label: "Chapter Count (Most)" },
|
||||||
|
{ value: "abc", label: "Title (A-Z)" },
|
||||||
|
{ value: "cba", label: "Title (Z-A)" }
|
||||||
|
],
|
||||||
|
default: "bookmark"
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: "Status",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "-1", label: "All" },
|
||||||
|
{ value: "1", label: "Completed" },
|
||||||
|
{ value: "0", label: "Ongoing" }
|
||||||
|
],
|
||||||
|
default: "-1"
|
||||||
|
},
|
||||||
|
chapters: {
|
||||||
|
label: "Chapter Count",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "0", label: "All" },
|
||||||
|
{ value: "1,49", label: "< 50" },
|
||||||
|
{ value: "50,100", label: "50 - 100" },
|
||||||
|
{ value: "100,200", label: "100 - 200" },
|
||||||
|
{ value: "200,500", label: "200 - 500" },
|
||||||
|
{ value: "500,1000", label: "500 - 1000" },
|
||||||
|
{ value: "1001,1000000", label: "> 1000" }
|
||||||
|
],
|
||||||
|
default: "0"
|
||||||
|
},
|
||||||
|
// Géneros (Categories)
|
||||||
|
genres: {
|
||||||
|
label: "Genres (Categories)",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "1", label: "Action" },
|
||||||
|
{ value: "2", label: "Adventure" },
|
||||||
|
{ value: "3", label: "Comedy" },
|
||||||
|
{ value: "4", label: "Drama" },
|
||||||
|
{ value: "5", label: "Ecchi" },
|
||||||
|
{ value: "6", label: "Fantasy" },
|
||||||
|
{ value: "7", label: "Harem" },
|
||||||
|
{ value: "8", label: "Historical" },
|
||||||
|
{ value: "9", label: "Horror" },
|
||||||
|
{ value: "10", label: "Josei" },
|
||||||
|
{ value: "11", label: "Martial Arts" },
|
||||||
|
{ value: "12", label: "Mature" },
|
||||||
|
{ value: "13", label: "Mecha" },
|
||||||
|
{ value: "14", label: "Mystery" },
|
||||||
|
{ value: "15", label: "Psychological" },
|
||||||
|
{ value: "16", label: "Romance" },
|
||||||
|
{ value: "17", label: "School Life" },
|
||||||
|
{ value: "18", label: "Sci-fi" },
|
||||||
|
{ value: "19", label: "Seinen" },
|
||||||
|
{ value: "20", label: "Shoujo" },
|
||||||
|
{ value: "21", label: "Shoujo Ai" },
|
||||||
|
{ value: "22", label: "Shounen" },
|
||||||
|
{ value: "23", label: "Shounen Ai" },
|
||||||
|
{ value: "24", label: "Slice of Life" },
|
||||||
|
{ value: "25", label: "Smut" },
|
||||||
|
{ value: "26", label: "Sports" },
|
||||||
|
{ value: "27", label: "Supernatural" },
|
||||||
|
{ value: "28", label: "Tragedy" },
|
||||||
|
{ value: "29", label: "Wuxia" },
|
||||||
|
{ value: "30", label: "Xianxia" },
|
||||||
|
{ value: "31", label: "Xuanhuan" },
|
||||||
|
{ value: "32", label: "Yaoi" },
|
||||||
|
{ value: "33", label: "Yuri" },
|
||||||
|
{ value: "34", label: "Adult" },
|
||||||
|
{ value: "35", label: "Gender Bender" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// Etiquetas (Tags) - Lista Masiva
|
||||||
|
tags: {
|
||||||
|
label: "Tags",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "10", label: "Unique Cultivation Technique" }, { value: "11", label: "Fast Cultivation" }, { value: "12", label: "Marriage" },
|
||||||
|
{ value: "13", label: "Hard-Working Protagonist" }, { value: "14", label: "Gods" }, { value: "15", label: "Monsters" },
|
||||||
|
{ value: "16", label: "Weak to Strong" }, { value: "17", label: "Broken Engagement" }, { value: "18", label: "Strong Love Interests" },
|
||||||
|
{ value: "19", label: "Romantic Subplot" }, { value: "20", label: "Power Couple" }, { value: "21", label: "Tragic Past" },
|
||||||
|
{ value: "22", label: "Demons" }, { value: "23", label: "Beautiful Female Lead" }, { value: "24", label: "Demon Lord" },
|
||||||
|
{ value: "25", label: "Genius Protagonist" }, { value: "26", label: "Bloodlines" }, { value: "27", label: "Sword Wielder" },
|
||||||
|
{ value: "28", label: "Black Belly" }, { value: "29", label: "Boss-Subordinate Relationship" }, { value: "30", label: "Money Grubber" },
|
||||||
|
{ value: "31", label: "Bickering Couple" }, { value: "32", label: "Calm Protagonist" }, { value: "33", label: "Female Protagonist" },
|
||||||
|
{ value: "34", label: "Childcare" }, { value: "35", label: "Handsome Male Lead" }, { value: "36", label: "Modern Day" },
|
||||||
|
{ value: "37", label: "Magic Formations" }, { value: "38", label: "Beast Companions" }, { value: "39", label: "Immortals" },
|
||||||
|
{ value: "40", label: "Archery" }, { value: "41", label: "Cunning Protagonist" }, { value: "42", label: "Ruthless Protagonist" },
|
||||||
|
{ value: "43", label: "Body Tempering" }, { value: "44", label: "Clever Protagonist" }, { value: "45", label: "Pets" },
|
||||||
|
{ value: "46", label: "Multiple Realms" }, { value: "47", label: "Near-Death Experience" }, { value: "48", label: "Artifacts" },
|
||||||
|
{ value: "49", label: "Beasts" }, { value: "50", label: "Buddhism" }, { value: "51", label: "Crafting" },
|
||||||
|
{ value: "52", label: "Alchemy" }, { value: "53", label: "Schemes And Conspiracies" }, { value: "54", label: "Determined Protagonist" },
|
||||||
|
{ value: "55", label: "Male Protagonist" }, { value: "56", label: "Non-humanoid Protagonist" }, { value: "57", label: "Demonic Cultivation Technique" },
|
||||||
|
{ value: "58", label: "Trap" }, { value: "59", label: "Cruel Characters" }, { value: "60", label: "Arrogant Characters" },
|
||||||
|
{ value: "61", label: "Evil Organizations" }, { value: "62", label: "Wars" }, { value: "63", label: "Strength-based Social Hierarchy" },
|
||||||
|
{ value: "64", label: "Artifact Crafting" }, { value: "65", label: "Dragons" }, { value: "66", label: "Late Romance" },
|
||||||
|
{ value: "67", label: "Revenge" }, { value: "68", label: "Doctors" }, { value: "69", label: "Reincarnation" },
|
||||||
|
{ value: "70", label: "Second Chance" }, { value: "71", label: "Rebirth" }, { value: "72", label: "Showbiz" },
|
||||||
|
{ value: "73", label: "Cute Children" }, { value: "74", label: "Previous Life Talent" }, { value: "75", label: "Celebrities" },
|
||||||
|
{ value: "76", label: "Acting" }, { value: "77", label: "Smart Couple" }, { value: "78", label: "Older Love Interests" },
|
||||||
|
{ value: "79", label: "Caring Protagonist" }, { value: "80", label: "Protagonist Falls in Love First" }, { value: "81", label: "Politics" },
|
||||||
|
{ value: "82", label: "Devoted Love Interests" }, { value: "83", label: "Guardian Relationship" }, { value: "84", label: "Ancient China" },
|
||||||
|
{ value: "85", label: "Character Growth" }, { value: "86", label: "Time Skip" }, { value: "87", label: "Nobles" },
|
||||||
|
{ value: "88", label: "Adapted to Drama CD" }, { value: "89", label: "Seme Protagonist" }, { value: "90", label: "Pragmatic Protagonist" },
|
||||||
|
{ value: "91", label: "Slow Romance" }, { value: "92", label: "Royalty" }, { value: "93", label: "Loyal Subordinates" },
|
||||||
|
{ value: "94", label: "Age Progression" }, { value: "95", label: "Cultivation" }, { value: "96", label: "Card Games" },
|
||||||
|
{ value: "97", label: "Adapted to Anime" }, { value: "98", label: "Game Elements" }, { value: "99", label: "Average-looking Protagonist" },
|
||||||
|
{ value: "100", label: "Firearms" }, { value: "101", label: "Fanfiction" }, { value: "102", label: "Survival Game" },
|
||||||
|
{ value: "103", label: "Betrayal" }, { value: "104", label: "Hunters" }, { value: "105", label: "Aliens" },
|
||||||
|
{ value: "106", label: "Evolution" }, { value: "107", label: "Apocalypse" }, { value: "108", label: "Transported to Another World" },
|
||||||
|
{ value: "109", label: "Military" }, { value: "110", label: "Adapted to Manhua" }, { value: "111", label: "Vampires" },
|
||||||
|
{ value: "112", label: "Movies" }, { value: "113", label: "Alternate World" }, { value: "114", label: "Ghosts" },
|
||||||
|
{ value: "115", label: "Eidetic Memory" }, { value: "116", label: "Limited Lifespan" }, { value: "117", label: "Misunderstandings" },
|
||||||
|
{ value: "118", label: "Cheats" }, { value: "119", label: "Fast Learner" }, { value: "120", label: "Souls" },
|
||||||
|
{ value: "121", label: "Jack of All Trades" }, { value: "122", label: "Transmigration" }, { value: "123", label: "Sudden Strength Gain" },
|
||||||
|
{ value: "124", label: "Parody" }, { value: "125", label: "Overpowered Protagonist" }, { value: "126", label: "Poisons" },
|
||||||
|
{ value: "127", label: "Library" }, { value: "128", label: "Dense Protagonist" }, { value: "129", label: "Protagonist with Multiple Bodies" },
|
||||||
|
{ value: "130", label: "Master-Disciple Relationship" }, { value: "131", label: "Shy Characters" }, { value: "132", label: "Manipulative Characters" },
|
||||||
|
{ value: "133", label: "Teachers" }, { value: "134", label: "Academy" }, { value: "135", label: "Pill Concocting" },
|
||||||
|
{ value: "136", label: "Enlightenment" }, { value: "137", label: "Possessive Characters" }, { value: "138", label: "Shameless Protagonist" },
|
||||||
|
{ value: "139", label: "Transported into Another World" }, { value: "140", label: "Sharp-tongued Characters" }, { value: "141", label: "Male Yandere" },
|
||||||
|
{ value: "142", label: "Complex Family Relationships" }, { value: "143", label: "Couple Growth" }, { value: "144", label: "Abusive Characters" },
|
||||||
|
{ value: "145", label: "Cold Love Interests" }, { value: "146", label: "Love Interest Falls in Love First" }, { value: "147", label: "Amnesia" },
|
||||||
|
{ value: "148", label: "Forced into a Relationship" }, { value: "149", label: "Cross-dressing" }, { value: "150", label: "Personality Changes" },
|
||||||
|
{ value: "151", label: "Weak Protagonist" }, { value: "152", label: "Technological Gap" }, { value: "153", label: "Kingdom Building" },
|
||||||
|
{ value: "154", label: "Economics Engineer" }, { value: "155", label: "Management" }, { value: "156", label: "Army" },
|
||||||
|
{ value: "157", label: "Fantasy World" }, { value: "158", label: "Discrimination" }, { value: "159", label: "Multiple POV" },
|
||||||
|
{ value: "160", label: "Strategic Battles" }, { value: "161", label: "Leadership" }, { value: "162", label: "Magic" },
|
||||||
|
{ value: "163", label: "Kingdoms Knights" }, { value: "164", label: "Domestic Affairs" }, { value: "165", label: "Early Romance" },
|
||||||
|
{ value: "166", label: "Modern Knowledge" }, { value: "167", label: "Stoic Characters" }, { value: "168", label: "Army Building" },
|
||||||
|
{ value: "169", label: "Medieval" }, { value: "170", label: "Mature Protagonist" }, { value: "171", label: "Depictions of Cruelty" },
|
||||||
|
{ value: "172", label: "Religions" }, { value: "173", label: "Industrialization" }, { value: "174", label: "Business Management" },
|
||||||
|
{ value: "175", label: "Pregnancy" }, { value: "176", label: "Jealousy" }, { value: "177", label: "Fleet Battles" },
|
||||||
|
{ value: "178", label: "Elves" }, { value: "179", label: "MMORPG" }, { value: "180", label: "Dungeons" },
|
||||||
|
{ value: "181", label: "Virtual Reality" }, { value: "182", label: "Battle Competition" }, { value: "183", label: "Game Ranking System" },
|
||||||
|
{ value: "184", label: "Dwarfs" }, { value: "185", label: "Poor to Rich" }, { value: "186", label: "Hiding True Identity" },
|
||||||
|
{ value: "187", label: "Spatial Manipulation" }, { value: "188", label: "Monster Society" }, { value: "189", label: "Sword And Magic" },
|
||||||
|
{ value: "190", label: "Beastkin" }, { value: "191", label: "Outer Space" }, { value: "192", label: "Spaceship" },
|
||||||
|
{ value: "193", label: "Businessmen" }, { value: "194", label: "Mercenaries" }, { value: "195", label: "Magical Space" },
|
||||||
|
{ value: "196", label: "Knights" }, { value: "197", label: "Level System" }, { value: "198", label: "Dragon Slayers" },
|
||||||
|
{ value: "199", label: "Previous Life" }, { value: "200", label: "Skill Books" }, { value: "201", label: "Creatures" },
|
||||||
|
{ value: "202", label: "Store Owner" }, { value: "203", label: "Famous Protagonist" }, { value: "204", label: "Gamers" },
|
||||||
|
{ value: "205", label: "Lucky Protagonist" }, { value: "206", label: "Blacksmith" }, { value: "207", label: "Friendship" },
|
||||||
|
{ value: "208", label: "Talent" }, { value: "209", label: "Fantasy" }, { value: "210", label: "Guilds" },
|
||||||
|
{ value: "211", label: "Aristocracy" }, { value: "212", label: "Harem" }, { value: "213", label: "ArtifactCrafting" },
|
||||||
|
{ value: "214", label: "BodyTempering" }, { value: "215", label: "BeautifulFemaleLead" }, { value: "216", label: "Strategist" },
|
||||||
|
{ value: "217", label: "Clubs" }, { value: "218", label: "Adapted to Drama" }, { value: "219", label: "e-Sports" },
|
||||||
|
{ value: "220", label: "Grinding" }, { value: "221", label: "Carefree Protagonist" }, { value: "222", label: "Past Plays a Big Role Protagonist" },
|
||||||
|
{ value: "223", label: "Confident Protagonist" }, { value: "224", label: "Secret Identity" }, { value: "225", label: "Strong from the Start" },
|
||||||
|
{ value: "226", label: "Wealthy Characters" }, { value: "227", label: "Mutated Creatures" }, { value: "228", label: "Underestimated Protagonist" },
|
||||||
|
{ value: "229", label: "Battle Academy" }, { value: "230", label: "Hiding True Abilities" }, { value: "231", label: "Enemies Become Allies" },
|
||||||
|
{ value: "232", label: "Adopted Children" }, { value: "233", label: "Mysterious Family Background" }, { value: "234", label: "Younger Sisters" },
|
||||||
|
{ value: "235", label: "Genetic Modifications" }, { value: "236", label: "Futuristic Setting" }, { value: "237", label: "Reverse Harem" },
|
||||||
|
{ value: "238", label: "Doting Love Interests" }, { value: "239", label: "Childhood Love" }, { value: "240", label: "Popular Love Interests" },
|
||||||
|
{ value: "241", label: "Hidden Abilities" }, { value: "242", label: "Kuudere" }, { value: "243", label: "Gore" },
|
||||||
|
{ value: "244", label: "Rape" }, { value: "245", label: "Special Abilities" }, { value: "246", label: "Cold Protagonist" },
|
||||||
|
{ value: "247", label: "Spirits" }, { value: "248", label: "Prophecies" }, { value: "249", label: "Godly Powers" },
|
||||||
|
{ value: "250", label: "Inheritance" }, { value: "251", label: "Martial Spirits" }, { value: "252", label: "Transplanted Memories" },
|
||||||
|
{ value: "253", label: "Sexual Cultivation Technique" }, { value: "254", label: "Polygamy" }, { value: "255", label: "Murders" },
|
||||||
|
{ value: "256", label: "Skill Assimilation" }, { value: "257", label: "Reincarnated in a Game World" }, { value: "258", label: "Assassins" },
|
||||||
|
{ value: "259", label: "Action" }, { value: "260", label: "Transported into a Game World" }, { value: "261", label: "Engineer" },
|
||||||
|
{ value: "262", label: "Adventurers" }, { value: "263", label: "Narcissistic Protagonist" }, { value: "264", label: "Secret Organizations" },
|
||||||
|
{ value: "265", label: "Multiple Identities" }, { value: "266", label: "Cautious Protagonist" }, { value: "267", label: "Accelerated Growth" },
|
||||||
|
{ value: "268", label: "Gunfighters" }, { value: "269", label: "Cosmic Wars" }, { value: "270", label: "Selfish Protagonist" },
|
||||||
|
{ value: "271", label: "Artificial Intelligence" }, { value: "272", label: "Arms Dealers" }, { value: "273", label: "Biochip" },
|
||||||
|
{ value: "274", label: "Automatons" }, { value: "275", label: "Mob Protagonist" }, { value: "276", label: "Magical Technology" },
|
||||||
|
{ value: "277", label: "Nationalism" }, { value: "278", label: "Comedic Undertone" }, { value: "279", label: "Divorce" },
|
||||||
|
{ value: "280", label: "Arranged Marriage" }, { value: "281", label: "Long Separations" }, { value: "282", label: "Rape Victim Becomes Lover" },
|
||||||
|
{ value: "283", label: "Secret Relationship" }, { value: "284", label: "Secret Crush" }, { value: "285", label: "Secrets" },
|
||||||
|
{ value: "286", label: "Death" }, { value: "287", label: "Love at First Sight" }, { value: "288", label: "Secretive Protagonist" },
|
||||||
|
{ value: "289", label: "Tsundere" }, { value: "290", label: "Sexual Abuse" }, { value: "291", label: "College/University" },
|
||||||
|
{ value: "292", label: "Eye Powers" }, { value: "293", label: "Sect Development" }, { value: "294", label: "Slaves" },
|
||||||
|
{ value: "295", label: "Race Change" }, { value: "296", label: "Heterochromia" }, { value: "297", label: "God Protagonist" },
|
||||||
|
{ value: "298", label: "Fox Spirits" }, { value: "299", label: "Soul Power" }, { value: "300", label: "Elderly Protagonist" },
|
||||||
|
{ value: "301", label: "Sibling Rivalry" }, { value: "302", label: "Hated Protagonist" }, { value: "303", label: "Evil Protagonist" },
|
||||||
|
{ value: "304", label: "Dark" }, { value: "305", label: "Merchants" }, { value: "306", label: "Orphans" },
|
||||||
|
{ value: "307", label: "Magic Beasts" }, { value: "308", label: "Philosophical" }, { value: "309", label: "Proactive Protagonist" },
|
||||||
|
{ value: "310", label: "Antihero Protagonist" }, { value: "311", label: "Appearance Changes" }, { value: "312", label: "Appearance Different from Actual Age" },
|
||||||
|
{ value: "313", label: "Dao Comprehension" }, { value: "314", label: "Apathetic Protagonist" }, { value: "315", label: "Reincarnated in Another World" },
|
||||||
|
{ value: "316", label: "Twins" }, { value: "317", label: "Heavenly Tribulation" }, { value: "318", label: "Interconnected Storylines" },
|
||||||
|
{ value: "319", label: "World Travel" }, { value: "320", label: "Loner Protagonist" }, { value: "321", label: "Time Manipulation" },
|
||||||
|
{ value: "322", label: "Fellatio" }, { value: "323", label: "First-time Intercourse" }, { value: "324", label: "Abandoned Children" },
|
||||||
|
{ value: "325", label: "Strong to Stronger" }, { value: "326", label: "R-18" }, { value: "327", label: "Male MC" },
|
||||||
|
{ value: "328", label: "Hidden Gem" }, { value: "329", label: "Western Fantasy" }, { value: "330", label: "Protagonist Strong from the Start" },
|
||||||
|
{ value: "331", label: "Easy Going Life" }, { value: "332", label: "Lazy Protagonist" }, { value: "333", label: "Family Conflict" },
|
||||||
|
{ value: "334", label: "Poor Protagonist" }, { value: "335", label: "Adultery" }, { value: "336", label: "Investigations" },
|
||||||
|
{ value: "337", label: "Gangs" }, { value: "338", label: "Racism" }, { value: "339", label: "Single Parent" },
|
||||||
|
{ value: "340", label: "Inscriptions" }, { value: "341", label: "Student-Teacher Relationship" }, { value: "342", label: "Clever Protagonist Cultivation" },
|
||||||
|
{ value: "343", label: "Returning from Another World" }, { value: "344", label: "Bullying" }, { value: "345", label: "Urban" },
|
||||||
|
{ value: "346", label: "Honest Protagonist" }, { value: "347", label: "See edit history" }, { value: "348", label: "Death of Loved Ones" },
|
||||||
|
{ value: "349", label: "Evil Gods" }, { value: "350", label: "Friends Become Enemies" }, { value: "351", label: "Outdoor Intercourse" },
|
||||||
|
{ value: "352", label: "Perverted Protagonist" }, { value: "353", label: "Zombies" }, { value: "354", label: "Empires" },
|
||||||
|
{ value: "355", label: "Pill Based Cultivation" }, { value: "356", label: "Economics" }, { value: "357", label: "Gate to Another World" },
|
||||||
|
{ value: "358", label: "Herbalist" }, { value: "359", label: "Insects" }, { value: "360", label: "Low-key Protagonist" },
|
||||||
|
{ value: "361", label: "Medical Knowledge" }, { value: "362", label: "Modern World" }, { value: "363", label: "Organized Crime" },
|
||||||
|
{ value: "364", label: "Absent Parents" }, { value: "365", label: "Cute Protagonist" }, { value: "366", label: "Genies" },
|
||||||
|
{ value: "367", label: "Martial arts" }, { value: "368", label: "Mythical Beasts" }, { value: "369", label: "Spear Wielder" },
|
||||||
|
{ value: "370", label: "Dao Companion" }, { value: "371", label: "Demi-Humans" }, { value: "372", label: "World Tree" },
|
||||||
|
{ value: "373", label: "Adapted to Manga" }, { value: "374", label: "Contracts" }, { value: "375", label: "Human Experimentation" },
|
||||||
|
{ value: "376", label: "Humanoid Protagonist" }, { value: "377", label: "Pharmacist" }, { value: "378", label: "Sealed Power" },
|
||||||
|
{ value: "379", label: "Spirit Advisor" }, { value: "380", label: "Thieves" }, { value: "381", label: "Ugly to Beautiful" },
|
||||||
|
{ value: "382", label: "Phoenixes" }, { value: "383", label: "Sister Complex" }, { value: "384", label: "Summoning Magic" },
|
||||||
|
{ value: "385", label: "Hackers" }, { value: "386", label: "Programmer" }, { value: "387", label: "Stubborn Protagonist" },
|
||||||
|
{ value: "388", label: "Drugs" }, { value: "389", label: "Human-Nonhuman Relationship" }, { value: "390", label: "Hypnotism" },
|
||||||
|
{ value: "391", label: "Quirky Characters" }, { value: "392", label: "Torture" }, { value: "393", label: "Chefs" },
|
||||||
|
{ value: "394", label: "Cooking" }, { value: "395", label: "Restaurant" }, { value: "396", label: "System Administrator" },
|
||||||
|
{ value: "397", label: "Unique Weapon User" }, { value: "398", label: "Mutations" }, { value: "399", label: "Post-apocalyptic" },
|
||||||
|
{ value: "400", label: "Clan Building" }, { value: "401", label: "Legends" }, { value: "402", label: "Possession" },
|
||||||
|
{ value: "403", label: "Righteous Protagonist" }, { value: "404", label: "Healers" }, { value: "405", label: "Necromancer" },
|
||||||
|
{ value: "406", label: "Parallel Worlds" }, { value: "407", label: "Charismatic Protagonist" }, { value: "408", label: "Playful Protagonist" },
|
||||||
|
{ value: "409", label: "Shounen-Ai Subplot" }, { value: "410", label: "Harem-seeking Protagonist" }, { value: "411", label: "Hot-blooded Protagonist" },
|
||||||
|
{ value: "412", label: "Imperial Harem" }, { value: "413", label: "Masturbation" }, { value: "414", label: "Naive Protagonist" },
|
||||||
|
{ value: "415", label: "Reverse Rape" }, { value: "416", label: "Books" }, { value: "417", label: "Lottery" },
|
||||||
|
{ value: "418", label: "Poetry" }, { value: "419", label: "Writers" }, { value: "420", label: "Elemental Magic" },
|
||||||
|
{ value: "421", label: "Kingdoms" }, { value: "422", label: "Affair" }, { value: "423", label: "Charming Protagonist" },
|
||||||
|
{ value: "424", label: "Scientists" }, { value: "425", label: "Singers" }, { value: "426", label: "Childhood Friends" },
|
||||||
|
{ value: "427", label: "Childhood Promise" }, { value: "428", label: "Forced Marriage" }, { value: "429", label: "Doting Older Siblings" },
|
||||||
|
{ value: "430", label: "BDSM" }, { value: "431", label: "Masochistic Characters" }, { value: "432", label: "Teamwork" },
|
||||||
|
{ value: "433", label: "Threesome" }, { value: "434", label: "Cowardly Protagonist" }, { value: "435", label: "Unlucky Protagonist" },
|
||||||
|
{ value: "436", label: "Harsh Training" }, { value: "437", label: "Psychic Powers" }, { value: "438", label: "Druids" },
|
||||||
|
{ value: "439", label: "Enemies Become Lovers" }, { value: "440", label: "Maids" }, { value: "441", label: "Master-Servant Relationship" },
|
||||||
|
{ value: "442", label: "Cannibalism" }, { value: "443", label: "Corruption" }, { value: "444", label: "Farming" },
|
||||||
|
{ value: "445", label: "Generals" }, { value: "446", label: "Mind Control" }, { value: "447", label: "Monster Tamer" },
|
||||||
|
{ value: "448", label: "Prostitutes" }, { value: "449", label: "Sex Slaves" }, { value: "450", label: "Survival" },
|
||||||
|
{ value: "451", label: "Living Alone" }, { value: "452", label: "System" }, { value: "453", label: "Ancient Times" },
|
||||||
|
{ value: "454", label: "Fallen Nobility" }, { value: "455", label: "Past Plays a Big Role" }, { value: "456", label: "Past Trauma" },
|
||||||
|
{ value: "457", label: "Detectives" }, { value: "458", label: "Sickly Characters" }, { value: "459", label: "Fairies" },
|
||||||
|
{ value: "460", label: "Goblins" }, { value: "461", label: "Golems" }, { value: "462", label: "Slave Harem" },
|
||||||
|
{ value: "463", label: "Wizards" }, { value: "464", label: "Ability Steal" }, { value: "465", label: "Music" },
|
||||||
|
{ value: "466", label: "Mythology" }, { value: "467", label: "Trickster" }, { value: "468", label: "Anal" },
|
||||||
|
{ value: "469", label: "Clingy Lover" }, { value: "470", label: "Cousins" }, { value: "471", label: "Handjob" },
|
||||||
|
{ value: "472", label: "Photography" }, { value: "473", label: "Siblings" }, { value: "474", label: "Siblings Not Related by Blood" },
|
||||||
|
{ value: "475", label: "Yandere" }, { value: "476", label: "Blood Manipulation" }, { value: "477", label: "Tribal Society" },
|
||||||
|
{ value: "478", label: "Adopted Protagonist" }, { value: "479", label: "Incest" }, { value: "480", label: "Unrequited Love" },
|
||||||
|
{ value: "481", label: "Fallen Angels" }, { value: "482", label: "Goddesses" }, { value: "483", label: "Succubus" },
|
||||||
|
{ value: "484", label: "Terrorists" }, { value: "485", label: "Dreams" }, { value: "486", label: "World Hopping" },
|
||||||
|
{ value: "487", label: "Mysterious Past" }, { value: "488", label: "Terminal Illness" }, { value: "489", label: "Transported Modern Structure" },
|
||||||
|
{ value: "490", label: "Brotherhood" }, { value: "491", label: "Douluo Dalu" }, { value: "492", label: "European Ambience" },
|
||||||
|
{ value: "493", label: "Evil Religions" }, { value: "494", label: "Familiars" }, { value: "495", label: "Fated Lovers" },
|
||||||
|
{ value: "496", label: "Transformation Ability" }, { value: "497", label: "Fan-fiction" }, { value: "498", label: "Servants" },
|
||||||
|
{ value: "499", label: "Stockholm Syndrome" }, { value: "500", label: "Fantasy Magic" }, { value: "501", label: "Slow Growth at Start" },
|
||||||
|
{ value: "502", label: "Blind Protagonist" }, { value: "503", label: "Mpreg" }, { value: "504", label: "Familial Love" },
|
||||||
|
{ value: "505", label: "Multiple Reincarnated Individuals" }, { value: "506", label: "Curses" }, { value: "507", label: "Age Regression" },
|
||||||
|
{ value: "508", label: "Polite Protagonist" }, { value: "509", label: "Androgynous Characters" }, { value: "510", label: "Depression" },
|
||||||
|
{ value: "511", label: "Lawyers" }, { value: "512", label: "Seven Deadly Sins" }, { value: "513", label: "Twisted Personality" },
|
||||||
|
{ value: "514", label: "Polyandry" }, { value: "515", label: "Werebeasts" }, { value: "516", label: "Overprotective Siblings" },
|
||||||
|
{ value: "517", label: "Kidnappings" }, { value: "518", label: "Doting Parents" }, { value: "519", label: "Family" },
|
||||||
|
{ value: "520", label: "Models" }, { value: "521", label: "Clumsy Love Interests" }, { value: "522", label: "Love Triangles" },
|
||||||
|
{ value: "523", label: "Persistent Love Interests" }, { value: "524", label: "Sentient Objects" }, { value: "525", label: "Unique Weapons" },
|
||||||
|
{ value: "526", label: "Multiple Transported Individuals" }, { value: "527", label: "Obsessive Love" }, { value: "528", label: "Xuanhuan" },
|
||||||
|
{ value: "529", label: "Fantasy Creatures" }, { value: "530", label: "God-human Relationship" }, { value: "531", label: "Lost Civilizations" },
|
||||||
|
{ value: "532", label: "Campus Love" }, { value: "533", label: "Naruto" }, { value: "534", label: "Artists" },
|
||||||
|
{ value: "535", label: "Fearless Protagonist" }, { value: "536", label: "Skill Creation" }, { value: "537", label: "First Love" },
|
||||||
|
{ value: "538", label: "Lovers Reunited" }, { value: "539", label: "Soldiers" }, { value: "540", label: "Unconditional Love" },
|
||||||
|
{ value: "541", label: "Saving the World" }, { value: "542", label: "Superpowers" }, { value: "543", label: "Wuxia" },
|
||||||
|
{ value: "544", label: "Dolls/Puppets" }, { value: "545", label: "Football" }, { value: "546", label: "Villainess Noble Girls" },
|
||||||
|
{ value: "547", label: "Bodyguards" }, { value: "548", label: "Netori" }, { value: "549", label: "Nudity" },
|
||||||
|
{ value: "550", label: "Paizuri" }, { value: "551", label: "Family Business" }, { value: "552", label: "Pirates" },
|
||||||
|
{ value: "553", label: "Chuunibyou" }, { value: "554", label: "Lack of Common Sense" }, { value: "555", label: "Seduction" },
|
||||||
|
{ value: "556", label: "beautiful heroine" }, { value: "557", label: "Time Travel" }, { value: "558", label: "Heartwarming" },
|
||||||
|
{ value: "559", label: "Interdimensional Travel" }, { value: "560", label: "Parent Complex" }, { value: "561", label: "Precognition" },
|
||||||
|
{ value: "562", label: "Time Paradox" }, { value: "563", label: "Gambling" }, { value: "564", label: "Orcs" },
|
||||||
|
{ value: "565", label: "Summoned Hero" }, { value: "566", label: "Manly Gay Couple" }, { value: "567", label: "Power Struggle" },
|
||||||
|
{ value: "568", label: "Cute Story" }, { value: "569", label: "Dungeon Master" }, { value: "570", label: "Brother Complex" },
|
||||||
|
{ value: "571", label: "Cryostasis" }, { value: "572", label: "Different Social Status" }, { value: "573", label: "Student Council" },
|
||||||
|
{ value: "574", label: "Autism" }, { value: "575", label: "Childish Protagonist" }, { value: "576", label: "Genderless Protagonist" },
|
||||||
|
{ value: "577", label: "Half-human Protagonist" }, { value: "578", label: "Summoner" }, { value: "579", label: "Online Romance" },
|
||||||
|
{ value: "580", label: "Younger Love Interests" }, { value: "581", label: "Thriller" }, { value: "582", label: "Conditional Power" },
|
||||||
|
{ value: "583", label: "Slave Protagonist" }, { value: "584", label: "Mystery Solving" }, { value: "585", label: "Episodic" },
|
||||||
|
{ value: "586", label: "Fanaticism" }, { value: "587", label: "Omegaverse" }, { value: "588", label: "Priests" },
|
||||||
|
{ value: "589", label: "Stalkers" }, { value: "590", label: "Multiple Personalities" }, { value: "591", label: "Multiple Timelines" },
|
||||||
|
{ value: "592", label: "Crime" }, { value: "593", label: "Angels" }, { value: "594", label: "Awkward Protagonist" },
|
||||||
|
{ value: "595", label: "Destiny" }, { value: "596", label: "Disabilities" }, { value: "597", label: "Dragon Riders" },
|
||||||
|
{ value: "598", label: "Glasses-wearing Protagonist" }, { value: "599", label: "Helpful Protagonist" }, { value: "600", label: "Animal Characteristics" },
|
||||||
|
{ value: "601", label: "Shapeshifters" }, { value: "602", label: "Love Rivals" }, { value: "603", label: "Straight Seme" },
|
||||||
|
{ value: "604", label: "Straight Uke" }, { value: "605", label: "Chat Rooms" }, { value: "606", label: "Divination" },
|
||||||
|
{ value: "607", label: "Famous Parents" }, { value: "608", label: "Playboys" }, { value: "609", label: "Wars Weak to Strong" },
|
||||||
|
{ value: "610", label: "Cunnilingus" }, { value: "611", label: "Marriage of Convenience" }, { value: "612", label: "Police" },
|
||||||
|
{ value: "613", label: "Scheming" }, { value: "614", label: "Clones" }, { value: "615", label: "Heroes" },
|
||||||
|
{ value: "616", label: "Loli" }, { value: "617", label: "Reincarnated into Another World" }, { value: "618", label: "R-15" },
|
||||||
|
{ value: "619", label: "Butlers" }, { value: "620", label: "Reincarnated as a Monster" }, { value: "621", label: "Seven Virtues" },
|
||||||
|
{ value: "622", label: "Unreliable Narrator" }, { value: "623", label: "Witches" }, { value: "624", label: "Psychopaths" },
|
||||||
|
{ value: "625", label: "Neet" }, { value: "626", label: "S*x Friends" }, { value: "627", label: "Introverted Protagonist" },
|
||||||
|
{ value: "628", label: "Multiple Protagonists" }, { value: "629", label: "Non-linear Storytelling" }, { value: "630", label: "Resurrection" },
|
||||||
|
{ value: "631", label: "Folklore" }, { value: "632", label: "Puppeteers" }, { value: "633", label: "Romance" },
|
||||||
|
{ value: "634", label: "Modern" }, { value: "635", label: "Antagonist" }, { value: "636", label: "love" },
|
||||||
|
{ value: "637", label: "Drama" }, { value: "638", label: "Female Lead" }, { value: "639", label: "no-misunderstanding" },
|
||||||
|
{ value: "640", label: "contemporary romance" }, { value: "641", label: "backstabbing" }, { value: "642", label: "Complete" },
|
||||||
|
{ value: "643", label: "Child Protagonist" }, { value: "644", label: "Daoism" }, { value: "645", label: "S*aves" },
|
||||||
|
{ value: "646", label: "First-time Interc**rse" }, { value: "647", label: "Comedy" }, { value: "648", label: "Mature" },
|
||||||
|
{ value: "649", label: "Mystery" }, { value: "650", label: "Adapted to Manhwa" }, { value: "651", label: "Conspiracies" },
|
||||||
|
{ value: "652", label: "Bookworm" }, { value: "653", label: "Mysterious Illness" }, { value: "654", label: "Antique Shop" },
|
||||||
|
{ value: "655", label: "Indecisive Protagonist" }, { value: "656", label: "Seeing Things Other Humans Can't" }, { value: "657", label: "Doting male lead" },
|
||||||
|
{ value: "658", label: "Saints" }, { value: "659", label: "Female Master" }, { value: "660", label: "Adapted to Game" },
|
||||||
|
{ value: "661", label: "Monster Girls" }, { value: "662", label: "Adapted to Movie" }, { value: "663", label: "Eunuch" },
|
||||||
|
{ value: "664", label: "Otome Game" }, { value: "665", label: "Adapted to Visual Novel" }, { value: "666", label: "Evil MC" },
|
||||||
|
{ value: "667", label: "Shameless MC" }, { value: "668", label: "SwordAndMagic" }, { value: "669", label: "Debts" },
|
||||||
|
{ value: "670", label: "Valkyries" }, { value: "671", label: "Dishonest Protagonist" }, { value: "672", label: "Brainwashing" },
|
||||||
|
{ value: "673", label: "Curious Protagonist" }, { value: "674", label: "Prostit**es" }, { value: "675", label: "Blackmail" },
|
||||||
|
{ value: "676", label: "Criminals" }, { value: "677", label: "Disfigurement" }, { value: "678", label: "Male Lead" },
|
||||||
|
{ value: "679", label: "Historical" }, { value: "680", label: "Rivalry" }, { value: "681", label: "Earth Invasion" },
|
||||||
|
{ value: "682", label: "Divine Protection" }, { value: "683", label: "Exorcism" }, { value: "684", label: "Grave Keepers" },
|
||||||
|
{ value: "685", label: "Hell" }, { value: "686", label: "Animal Rearing" }, { value: "687", label: "Heaven" },
|
||||||
|
{ value: "688", label: "Adventure" }, { value: "689", label: "Sci-Fi" }, { value: "690", label: "Science Fiction" },
|
||||||
|
{ value: "691", label: "Mechs" }, { value: "692", label: "Mech Designer" }, { value: "693", label: "Space Opera" },
|
||||||
|
{ value: "694", label: "Starships" }, { value: "695", label: "RPG System" }, { value: "696", label: "Sudden Wealth" },
|
||||||
|
{ value: "697", label: "Tsundere Protagonist" }, { value: "698", label: "Priestesses" }, { value: "699", label: "Body Swap" },
|
||||||
|
{ value: "700", label: "ArrogantCharacters" }, { value: "701", label: "BeastCompanions" }, { value: "702", label: "CleverProtagonist" },
|
||||||
|
{ value: "703", label: "Spies" }, { value: "704", label: "Sculptors" }, { value: "705", label: "R*pe Victim Becomes Lover" },
|
||||||
|
{ value: "706", label: "Reverse R*pe" }, { value: "707", label: "F*llatio" }, { value: "708", label: "Parasites" },
|
||||||
|
{ value: "709", label: "R*pe" }, { value: "710", label: "Engagement" }, { value: "711", label: "Inferiority Complex" },
|
||||||
|
{ value: "712", label: "S*xual Abuse" }, { value: "713", label: "Human Weapon" }, { value: "714", label: "Ninjas" },
|
||||||
|
{ value: "715", label: "Emotionally Weak Protagonist" }, { value: "716", label: "Shota" }, { value: "717", label: "Shoujo-Ai Subplot" },
|
||||||
|
{ value: "718", label: "Dystopia" }, { value: "719", label: "Fat Protagonist" }, { value: "720", label: "Ugly Protagonist" },
|
||||||
|
{ value: "721", label: "An*l" }, { value: "722", label: "Mind Break" }, { value: "723", label: "Outdoor Interc**rse" },
|
||||||
|
{ value: "724", label: "Salaryman" }, { value: "725", label: "Sibling's Care" }, { value: "726", label: "Prison" },
|
||||||
|
{ value: "727", label: "Sadistic Characters" }, { value: "728", label: "Fat to Fit" }, { value: "729", label: "Dead Protagonist" },
|
||||||
|
{ value: "730", label: "Body-double" }, { value: "731", label: "Cohabitation" }, { value: "732", label: "Confinement" },
|
||||||
|
{ value: "733", label: "Conflicting Loyalties" }, { value: "734", label: "Social Outcasts" }, { value: "735", label: "Suicides" },
|
||||||
|
{ value: "736", label: "Schizophrenia" }, { value: "737", label: "FemaleProtagonist" }, { value: "738", label: "HandsomeMaleLead" },
|
||||||
|
{ value: "739", label: "PossessiveCharacters" }, { value: "740", label: "PowerCouple" }, { value: "741", label: "SlowRomance" },
|
||||||
|
{ value: "742", label: "Kind Love Interests" }, { value: "743", label: "Language Barrier" }, { value: "744", label: "Timid Protagonist" },
|
||||||
|
{ value: "745", label: "Anti-Magic" }, { value: "746", label: "Coming of Age" }, { value: "747", label: "Exhibitionism" },
|
||||||
|
{ value: "748", label: "Futanari" }, { value: "749", label: "Sleeping" }, { value: "750", label: "Spirit Users" },
|
||||||
|
{ value: "751", label: "Flashbacks" }, { value: "752", label: "Amusement Park" }, { value: "753", label: "Serial Killers" },
|
||||||
|
{ value: "754", label: "Former Hero" }, { value: "755", label: "Award-winning Work" }, { value: "756", label: "Child Abuse" },
|
||||||
|
{ value: "757", label: "Delinquents" }, { value: "758", label: "Entertainment" }, { value: "759", label: "Aggressive Characters" },
|
||||||
|
{ value: "760", label: "Anti-social Protagonist" }, { value: "761", label: "Court Official" }, { value: "762", label: "Loneliness" },
|
||||||
|
{ value: "763", label: "Toys" }, { value: "764", label: "Nightmares" }, { value: "765", label: "Androids" },
|
||||||
|
{ value: "766", label: "Future Civilization" }, { value: "767", label: "Childhood Sweethearts" }, { value: "768", label: "Villain" },
|
||||||
|
{ value: "769", label: "Character Development" }, { value: "770", label: "Quick Transmigration" }, { value: "771", label: "Dream" },
|
||||||
|
{ value: "772", label: "Pilots" }, { value: "773", label: "Male to Female" }, { value: "774", label: "Onmyouji" },
|
||||||
|
{ value: "775", label: "Shikigami" }, { value: "776", label: "Tomboyish Female Lead" }, { value: "777", label: "Female Master Friendship" },
|
||||||
|
{ value: "778", label: "Romantic Subplot Ruthless Protagonist" }, { value: "779", label: "Saint" }, { value: "780", label: "Rebellion" },
|
||||||
|
{ value: "781", label: "Netorare" }, { value: "782", label: "Distrustful Protagonist" }, { value: "783", label: "Voice Actors" },
|
||||||
|
{ value: "784", label: "Pretend Lovers" }, { value: "785", label: "Hospital" }, { value: "786", label: "Part-Time Job" },
|
||||||
|
{ value: "787", label: "Breast Fetish" }, { value: "788", label: "Co-Workers" }, { value: "789", label: "Office Romance" },
|
||||||
|
{ value: "790", label: "Gladiators" }, { value: "791", label: "Online Game" }, { value: "792", label: "Incubus" },
|
||||||
|
{ value: "793", label: "Divination Enlightenment" }, { value: "794", label: "eastern fantasy" }, { value: "795", label: "Knights Level System" },
|
||||||
|
{ value: "796", label: "Roommates" }, { value: "797", label: "Pacifist Protagonist" }, { value: "798", label: "Adapted from Manga" },
|
||||||
|
{ value: "799", label: "Coma" }, { value: "800", label: "Lolicon" }, { value: "801", label: "Matriarchy" },
|
||||||
|
{ value: "802", label: "Time Loop" }, { value: "803", label: "Dancers" }, { value: "804", label: "Feng Shui" },
|
||||||
|
{ value: "805", label: "Younger Brothers" }, { value: "806", label: "Special Abilitie" }, { value: "807", label: "Otaku" },
|
||||||
|
{ value: "808", label: "Samurai" }, { value: "809", label: "Crossover" }, { value: "810", label: "Youkai" },
|
||||||
|
{ value: "811", label: "Outcasts" }, { value: "812", label: "Xianxia" }, { value: "813", label: "Harem-seeking Protagonist Harsh Training" },
|
||||||
|
{ value: "814", label: "Fujoshi" }, { value: "815", label: "Waiters" }, { value: "816", label: "Short Story" },
|
||||||
|
{ value: "817", label: "Magical Girls" }, { value: "818", label: "Senpai-Kouhai Relationship" }, { value: "819", label: "Quiet Characters" },
|
||||||
|
{ value: "820", label: "Selfless Protagonist" }, { value: "821", label: "Imaginary Friend" }, { value: "822", label: "Mythical" },
|
||||||
|
{ value: "823", label: "War Records" }, { value: "824", label: "Artifacts Body Tempering" }, { value: "825", label: "One Piece" },
|
||||||
|
{ value: "826", label: "Apartment Life" }, { value: "827", label: "Based on a Song" }, { value: "828", label: "Vocaloid" },
|
||||||
|
{ value: "829", label: "Glasses-wearing Love Interests" }, { value: "830", label: "Reincarnated as an Object" }, { value: "831", label: "Long-distance Relationship" },
|
||||||
|
{ value: "832", label: "Cosplay" }, { value: "833", label: "Female to Male" }, { value: "834", label: "Mangaka" },
|
||||||
|
{ value: "835", label: "Misandry" }, { value: "836", label: "Harry Potter" }, { value: "837", label: "Jiangshi" },
|
||||||
|
{ value: "838", label: "No Harem" }, { value: "839", label: "Genius Female Lead" }, { value: "840", label: "Steamy" },
|
||||||
|
{ value: "841", label: "Smart female lead" }, { value: "842", label: "Hacker" }, { value: "843", label: "Forced Living Arrangements" },
|
||||||
|
{ value: "844", label: "Hentai" }, { value: "845", label: "NonHuman Protagonist" }, { value: "846", label: "Video-Game Elements" },
|
||||||
|
{ value: "847", label: "Dungeon" }, { value: "848", label: "Snow Girl" }, { value: "849", label: "Sentimental Protagonist" },
|
||||||
|
{ value: "850", label: "Identity Crisis" }, { value: "851", label: "Mismatched Couple" }, { value: "852", label: "Bisexual Protagonist" },
|
||||||
|
{ value: "853", label: "Rich to Poor" }, { value: "854", label: "Reluctant Protagonist" }, { value: "855", label: "Tentacles" },
|
||||||
|
{ value: "856", label: "Homunculus" }, { value: "857", label: "Mute Character" }, { value: "858", label: "Sharing A Body" },
|
||||||
|
{ value: "859", label: "Netorase" }, { value: "860", label: "Wishes" }, { value: "861", label: "Overpowered" },
|
||||||
|
{ value: "862", label: "Original" }, { value: "863", label: "Cute" }, { value: "864", label: "Desperate" },
|
||||||
|
{ value: "865", label: "Multiple Bodies" }, { value: "866", label: "Shield User" }, { value: "867", label: "spirit realm" },
|
||||||
|
{ value: "868", label: "All-Girls School" }, { value: "869", label: "Galge" }, { value: "870", label: "Forgetful Protagonist" },
|
||||||
|
{ value: "871", label: "Reversible Couple" }, { value: "872", label: "Bands" }, { value: "873", label: "LitRPG" },
|
||||||
|
{ value: "874", label: "Hikikomori" }, { value: "875", label: "Jobless Class" }, { value: "876", label: "Sign Language" },
|
||||||
|
{ value: "877", label: "Artifacts Cultivation" }, { value: "878", label: "Nurses" }, { value: "879", label: "Pill Concoting" },
|
||||||
|
{ value: "880", label: "Monster Catching" }, { value: "881", label: "Gaming/E-Sport" }, { value: "882", label: "Based on a Movie" },
|
||||||
|
{ value: "883", label: "Marvel" }, { value: "884", label: "Marvel Universe" }, { value: "885", label: "War" },
|
||||||
|
{ value: "886", label: "Smart mc" }, { value: "887", label: "Multiverse" }, { value: "888", label: "Shotacon" },
|
||||||
|
{ value: "889", label: "Yaoi" }, { value: "890", label: "Bestiality" }, { value: "891", label: "face slapping" },
|
||||||
|
{ value: "892", label: "Non-human Protagonist" }, { value: "893", label: "Delusions" }, { value: "894", label: "Based on an Anime" },
|
||||||
|
{ value: "895", label: "Oneshot" }, { value: "896", label: "Political Intrigue" }, { value: "897", label: "Reporters" },
|
||||||
|
{ value: "898", label: "Astrologers" }, { value: "899", label: "Unlimited Flow" }, { value: "900", label: "Martialarts" },
|
||||||
|
{ value: "901", label: "Hokage" }, { value: "902", label: "Kakashi" }, { value: "903", label: "Talent Prophecies" },
|
||||||
|
{ value: "904", label: "Sign-in" }, { value: "905", label: "Pokemon" }, { value: "906", label: "School Life" },
|
||||||
|
{ value: "907", label: "Decisive Mc" }, { value: "908", label: "Knight" }, { value: "909", label: "Mystical" },
|
||||||
|
{ value: "910", label: "OP MC" }, { value: "911", label: "Orgy" }, { value: "912", label: "Slave" },
|
||||||
|
{ value: "913", label: "Game" }, { value: "914", label: "Non-Human MC" }, { value: "915", label: "Undead" },
|
||||||
|
{ value: "916", label: "Warcraft" }, { value: "917", label: "Over-Powered Protagonist" }, { value: "918", label: "Europe" },
|
||||||
|
{ value: "919", label: "Basketball" }, { value: "920", label: "Dynasty" }, { value: "921", label: "travel" },
|
||||||
|
{ value: "922", label: "Rebirthed Protagonist" }, { value: "923", label: "Sign-In/Check-In" }, { value: "924", label: "Super Technology" },
|
||||||
|
{ value: "925", label: "long lived main character" }, { value: "926", label: "Pampering Romance" }, { value: "927", label: "Murder" },
|
||||||
|
{ value: "928", label: "Phobias" }, { value: "929", label: "Demons Familiars" }, { value: "930", label: "Based on a TV Show" },
|
||||||
|
{ value: "931", label: "Adult" }, { value: "932", label: "Anti-MC" }, { value: "933", label: "Douluo" },
|
||||||
|
{ value: "934", label: "Crossing" }, { value: "935", label: "Fight for hegemony" }, { value: "936", label: "Operation" },
|
||||||
|
{ value: "937", label: "Formation" }, { value: "938", label: "Luck" }, { value: "939", label: "Male Protaganist" },
|
||||||
|
{ value: "940", label: "WW2" }, { value: "941", label: "Civilization" }, { value: "942", label: "Creation" },
|
||||||
|
{ value: "943", label: "Epic Fantasy" }, { value: "944", label: "Voyeurism" }, { value: "945", label: "Isekai" },
|
||||||
|
{ value: "946", label: "Multiple CP" }, { value: "947", label: "Danmei" }, { value: "948", label: "Livestreaming" },
|
||||||
|
{ value: "949", label: "Found Family" }, { value: "950", label: "Living Abroad" }, { value: "951", label: "Ancient ChinaFanfiction" },
|
||||||
|
{ value: "952", label: "Entertainment Industry" }, { value: "953", label: "Blind Dates" }, { value: "954", label: "Guideverse" },
|
||||||
|
{ value: "955", label: "Based on a Video Game" }, { value: "956", label: "Invisibility" }, { value: "957", label: "Biotechnology" },
|
||||||
|
{ value: "958", label: "Interstellar" }, { value: "959", label: "No Cp" }, { value: "960", label: "Science" },
|
||||||
|
{ value: "961", label: "Technology" }, { value: "962", label: "Transmigrate" }, { value: "963", label: "Uplifting Civilization" },
|
||||||
|
{ value: "964", label: "Zerg" }, { value: "965", label: "Titans" }, { value: "966", label: "Classic" },
|
||||||
|
{ value: "967", label: "Soccer" }, { value: "968", label: "Eastern Setting" }, { value: "969", label: "Gamer" },
|
||||||
|
{ value: "970", label: "Beautiful Couple" }, { value: "971", label: "Modern Fantasy" }, { value: "972", label: "Sex Friends" },
|
||||||
|
{ value: "973", label: "Akame Ga Kill" }, { value: "974", label: "My Hero Academia" }, { value: "975", label: "X-men" },
|
||||||
|
{ value: "976", label: "DC Universe" }, { value: "977", label: "Super Heroes" }, { value: "978", label: "Rank System" },
|
||||||
|
{ value: "979", label: "Solo Leveling" }, { value: "980", label: "Portal Fantasy" }, { value: "981", label: "Progression" },
|
||||||
|
{ value: "982", label: "High Fantasy" }, { value: "983", label: "Grimdark" }, { value: "984", label: "Avatar: The Last Airbender" },
|
||||||
|
{ value: "985", label: "Protagonist Loyal to Love Interest" }, { value: "986", label: "Not Harem" }, { value: "987", label: "No Cheats" },
|
||||||
|
{ value: "988", label: "Highschool of the Dead" }, { value: "989", label: "Protagonist NPC" }, { value: "990", label: "Mistaken Identity" },
|
||||||
|
{ value: "991", label: "Dragon" }, { value: "992", label: "High School DxD" }, { value: "993", label: "Charlotte (anime)" },
|
||||||
|
{ value: "994", label: "Ruling Class" }, { value: "995", label: "Strong Lead" }, { value: "996", label: "Slow Cultivation" },
|
||||||
|
{ value: "997", label: "Webnovel Spirity Awards" }, { value: "998", label: "Kanojo Okarishimasu" }, { value: "999", label: "My Wife Is A Beautiful CEO" },
|
||||||
|
{ value: "1000", label: "Return of the Dragon King" }, { value: "1001", label: "Shinmai Mao no Tastement" }, { value: "1002", label: "Highschool DxD" },
|
||||||
|
{ value: "1003", label: "Bleach" }, { value: "1004", label: "Hunter X Hunter" }, { value: "1005", label: "Attack on Titan" },
|
||||||
|
{ value: "1006", label: "Devils" }, { value: "1007", label: "Fairy Tail" }, { value: "1008", label: "College or University" },
|
||||||
|
{ value: "1009", label: "Sects" }, { value: "1010", label: "Not Netorare" }, { value: "1011", label: "Jujutsu Kaisen" },
|
||||||
|
{ value: "1012", label: "Noble" }, { value: "1013", label: "No Romance" }, { value: "1014", label: "Strategy" },
|
||||||
|
{ value: "1015", label: "Psychological" }, { value: "1016", label: "Tokyo Ghoul" }, { value: "1017", label: "Minecraft" },
|
||||||
|
{ value: "1018", label: "Gintama" }, { value: "1019", label: "Dolls or Puppets" }, { value: "1020", label: "Fate/Grand Order" },
|
||||||
|
{ value: "1021", label: "Campione!" }, { value: "1022", label: "Skyrim" }, { value: "1023", label: "RWBY" },
|
||||||
|
{ value: "1024", label: "Cyberpunk" }, { value: "1025", label: "Modern Time" }, { value: "1026", label: "Type-Moon" },
|
||||||
|
{ value: "1027", label: "One-Punch Man" }, { value: "1028", label: "Satire" }, { value: "1029", label: "Food Shopkeeper" },
|
||||||
|
{ value: "1030", label: "Lord of Mysteries" }, { value: "1031", label: "Doulou Dalu" }, { value: "1032", label: "CEO" },
|
||||||
|
{ value: "1033", label: "Not Yaoi" }, { value: "1034", label: "Gothic" }, { value: "1035", label: "Kenichi: The Mightiest Disciple" },
|
||||||
|
{ value: "1036", label: "Enemies" }, { value: "1037", label: "Steampunk" }, { value: "1038", label: "Low Fantasy" },
|
||||||
|
{ value: "1039", label: "Crazy Protagonist" }, { value: "1040", label: "RPG" }, { value: "1041", label: "Resolute Protagonist" },
|
||||||
|
{ value: "1042", label: "Record of Ragnarok" }, { value: "1043", label: "Space" }, { value: "1044", label: "Amorality Protagonist" },
|
||||||
|
{ value: "1045", label: "Battle Through the Heavens" }, { value: "1046", label: "Apocalyptic" }, { value: "1047", label: "Soft Sci-fi" },
|
||||||
|
{ value: "1048", label: "Karma" }, { value: "1049", label: "Diplomacy" }, { value: "1050", label: "Gender Bender" },
|
||||||
|
{ value: "1051", label: "Sports" }, { value: "1052", label: "World Invasion" }, { value: "1053", label: "Vampire" },
|
||||||
|
{ value: "1054", label: "Game Element" }, { value: "1055", label: "Angst" }, { value: "1056", label: "The Asterisk War" },
|
||||||
|
{ value: "1057", label: "sciencefiction" }, { value: "1058", label: "Game of Thrones" }, { value: "1059", label: "ASOIAF" },
|
||||||
|
{ value: "1060", label: "Mythos" }, { value: "1061", label: "Spec" }, { value: "1062", label: "Smut" },
|
||||||
|
{ value: "1063", label: "Hiding Identity" }, { value: "1064", label: "Goddess" }, { value: "1065", label: "Urban Fantasy" },
|
||||||
|
{ value: "1066", label: "Warcaft" }, { value: "1067", label: "Little Romance" }, { value: "1068", label: "Overlord" },
|
||||||
|
{ value: "1069", label: "Genshin Impact" }, { value: "1070", label: "Girl's Love Subplot" }, { value: "1071", label: "Dwarves" },
|
||||||
|
{ value: "1072", label: "Warhammer" }, { value: "1073", label: "The Gamer" }, { value: "1074", label: "Rich Protagonist" },
|
||||||
|
{ value: "1075", label: "LGBTQA" }, { value: "1076", label: "Chatgroup" }, { value: "1077", label: "Week to Strong" },
|
||||||
|
{ value: "1078", label: "GameLit" }, { value: "1079", label: "Warhammer 40K" }, { value: "1080", label: "Healing" },
|
||||||
|
{ value: "1081", label: "Godzilla" }, { value: "1082", label: "Dark Souls" }, { value: "1083", label: "Subtle Romance" },
|
||||||
|
{ value: "1084", label: "Mage" }, { value: "1085", label: "Resident Evil" }, { value: "1086", label: "supernatural" },
|
||||||
|
{ value: "1087", label: "Tales of Demons and Gods" }, { value: "1088", label: "SCP" }, { value: "1089", label: "Not Netori" },
|
||||||
|
{ value: "1090", label: "Star Wars" }, { value: "1091", label: "Multiple Lead Characters" }, { value: "1092", label: "Adapted from Manhua" },
|
||||||
|
{ value: "1093", label: "Trolls" }, { value: "1094", label: "Fallout: New Vegas" }, { value: "1095", label: "Sci-Fantasy" },
|
||||||
|
{ value: "1096", label: "Planets" }, { value: "1097", label: "Evil Organization" }, { value: "1098", label: "Secret Organization" },
|
||||||
|
{ value: "1099", label: "Detective Conan" }, { value: "1100", label: "Non-Human lead" }, { value: "1101", label: "DanMachi" },
|
||||||
|
{ value: "1102", label: "Alchemist" }, { value: "1103", label: "From the first POV" }, { value: "1104", label: "Fourth Wall" },
|
||||||
|
{ value: "1105", label: "Reader Interactive" }, { value: "1106", label: "DEVIL" }, { value: "1107", label: "Fate/stay night" },
|
||||||
|
{ value: "1108", label: "Hollywood" }, { value: "1109", label: "Fallout" }, { value: "1110", label: "R18" },
|
||||||
|
{ value: "1111", label: "Weaktostrong" }, { value: "1112", label: "Myth" }, { value: "1113", label: "Antihero" },
|
||||||
|
{ value: "1114", label: "Blodpumping" }, { value: "1115", label: "Culinary" }, { value: "1116", label: "Food" },
|
||||||
|
{ value: "1117", label: "gourmet" }, { value: "1118", label: "Mysterious Organization" }, { value: "1119", label: "Scary" },
|
||||||
|
{ value: "1120", label: "The Truth" }, { value: "1121", label: "Decryption" }, { value: "1122", label: "China's Qinling Mountains" },
|
||||||
|
{ value: "1123", label: "The Desert" }, { value: "1124", label: "Stimulus" }, { value: "1125", label: "Terrori" },
|
||||||
|
{ value: "1126", label: "UFO" }, { value: "1127", label: "The Aliens" }, { value: "1128", label: "The Prehistoric Civilization" },
|
||||||
|
{ value: "1129", label: "Parallel Universe" }, { value: "1130", label: "Quantum Mechanics" }, { value: "1131", label: "Multidimensional Space" },
|
||||||
|
{ value: "1132", label: "Semiotics" }, { value: "1133", label: "Secretive" }, { value: "1134", label: "Super Ability" },
|
||||||
|
{ value: "1135", label: "A Dead Body" }, { value: "1136", label: "Evil Spirit Ghost" }, { value: "1137", label: "Catch a Ghost" },
|
||||||
|
{ value: "1138", label: "Popular" }, { value: "1139", label: "Pretty Girl" }, { value: "1140", label: "The Main Character Charm" },
|
||||||
|
{ value: "1141", label: "The Devil" }, { value: "1142", label: "Businesswoman" }, { value: "1143", label: "Secret Love" },
|
||||||
|
{ value: "1144", label: "Excellent Skills" }, { value: "1145", label: "A Psychic Detective" }, { value: "1146", label: "Ghost Event" },
|
||||||
|
{ value: "1147", label: "The Paranormal" }, { value: "1148", label: "Beasttaming" }, { value: "1149", label: "Slice of Life" },
|
||||||
|
{ value: "1150", label: "Faceslapping" }, { value: "1151", label: "Genus" }, { value: "1152", label: "Levelup" },
|
||||||
|
{ value: "1153", label: "Highiq" }, { value: "1154", label: "First Contact" }, { value: "1155", label: "Hard sci-fi" },
|
||||||
|
{ value: "1156", label: "Collection of short stories" }, { value: "1157", label: "Infinite flow" }, { value: "1158", label: "Races" },
|
||||||
|
{ value: "1159", label: "Attractive Lead" }, { value: "1160", label: "Egoist Protagonist" }, { value: "1161", label: "Genius" },
|
||||||
|
{ value: "1162", label: "Videogame" }, { value: "1163", label: "Tragedy" }, { value: "1164", label: "Percy jackson" },
|
||||||
|
{ value: "1165", label: "Kingdombuilding" }, { value: "1166", label: "Conquer" }, { value: "1167", label: "Sliceoflife" },
|
||||||
|
{ value: "1168", label: "Killer" }, { value: "1169", label: "Advancedtechnology" }, { value: "1170", label: "Dc" },
|
||||||
|
{ value: "1171", label: "Yuri" }, { value: "1172", label: "Possessive" }, { value: "1173", label: "Fastpaced" },
|
||||||
|
{ value: "1174", label: "Warm" }, { value: "1175", label: "Adapted Manhwa" }, { value: "1176", label: "Sports Basketball" },
|
||||||
|
{ value: "1177", label: "Tree Protagonist" }, { value: "1178", label: "World treeAdapted to Manhua" }, { value: "1179", label: "system owner" },
|
||||||
|
{ value: "1180", label: "Superheroes" }, { value: "1181", label: "Pansexual protagonist" }, { value: "1182", label: "Reincarnated into a game world" },
|
||||||
|
{ value: "1183", label: "Cheating" }, { value: "1184", label: "Special forces" }, { value: "1185", label: "Transgender" },
|
||||||
|
{ value: "1186", label: "Onepiece" }, { value: "1187", label: "Anime" }, { value: "1188", label: "Immortal" },
|
||||||
|
{ value: "1189", label: "Vampire diaries" }, { value: "1190", label: "Demon slayer" }, { value: "1191", label: "Avatar" },
|
||||||
|
{ value: "1192", label: "Full Metal Alchemist" }, { value: "1193", label: "Dragon Ball" }, { value: "1194", label: "Powerfulcouple" },
|
||||||
|
{ value: "1195", label: "Thestrongactingweak" }, { value: "1196", label: "Ecchi" }, { value: "1197", label: "Fate" },
|
||||||
|
{ value: "1198", label: "Fate stay night" }, { value: "1199", label: "Unprincipled" }, { value: "1200", label: "Egoist" },
|
||||||
|
{ value: "1201", label: "Beast taming" }, { value: "1202", label: "Angel" }, { value: "1203", label: "Positive" },
|
||||||
|
{ value: "1204", label: "Yu-gi-oh" }, { value: "1205", label: "Nonhuman" }, { value: "1206", label: "Black clover" },
|
||||||
|
{ value: "1207", label: "Summons" }, { value: "1208", label: "Bloodpumping" }, { value: "1209", label: "Simulation" },
|
||||||
|
{ value: "1210", label: "PUBG" }, { value: "1211", label: "Call of Cthulhu" }, { value: "1212", label: "Urban Life" },
|
||||||
|
{ value: "1213", label: "Star Trek" }, { value: "1214", label: "The Wizard of Oz" }, { value: "1215", label: "Maleficent" },
|
||||||
|
{ value: "1216", label: "Sleeping Beauty" }, { value: "1217", label: "Aladdin" }, { value: "1218", label: "Addpoint" },
|
||||||
|
{ value: "1219", label: "Cheat" }, { value: "1220", label: "Suivivalgame" }, { value: "1221", label: "VoyeurismAbsent Parents" },
|
||||||
|
{ value: "1222", label: "Human-nonhuman relationshipAdapted to Manhua" }, { value: "1223", label: "Forced living arrangementsApocalypse" }, { value: "1224", label: "Npc" },
|
||||||
|
{ value: "1225", label: "Wild peopleAntihero Protagonist" }, { value: "1226", label: "Transported into a game worldAcademy" }, { value: "1227", label: "LivestreamingFemale Protagonist" },
|
||||||
|
{ value: "1228", label: "Hidden abilitiesCultivation" }, { value: "1229", label: "Seeing things other humans can'tCalm Protagonist" }, { value: "1230", label: "Vtuber" },
|
||||||
|
{ value: "1231", label: "Dark themes" }, { value: "1232", label: "Curious protagonistAntihero Protagonist" }, { value: "1233", label: "Villainess noble girlsAdapted to Manhwa" },
|
||||||
|
{ value: "1234", label: "Awakening" }, { value: "1235", label: "ThrillerAdapted to Manhwa" }, { value: "1236", label: "Josei" },
|
||||||
|
{ value: "1237", label: "Female protagonistAbsent Parents" }, { value: "1238", label: "Strong female leadAdapted to Manhwa" }, { value: "1239", label: "Marriage of convenienceComedic Undertone" },
|
||||||
|
{ value: "1240", label: "Dragon slayersAcademy" }, { value: "1241", label: "InsectsAdapted to Manhwa" }, { value: "1242", label: "WitchesAcademy" },
|
||||||
|
{ value: "1243", label: "Child growth" }, { value: "1244", label: "Family drama" }, { value: "1245", label: "Cute female lead" },
|
||||||
|
{ value: "1246", label: "Strong female leadAdopted Protagonist" }, { value: "1247", label: "Carefree protagonistCarefree Protagonist" }, { value: "1248", label: "Transported into a game worldAristocracy" },
|
||||||
|
{ value: "1249", label: "Hot-blooded protagonistBeautiful Female Lead" }, { value: "1250", label: "Immortal cultivation" }, { value: "1251", label: "Hot-blooded protagonistArmy Building" },
|
||||||
|
{ value: "1252", label: "Determined protagoni" }, { value: "1253", label: "Demonic cultivation techniqueAccelerated Growth" }, { value: "1254", label: "WitchesAntihero Protagonist" },
|
||||||
|
{ value: "1255", label: "R-15Absent Parents" }, { value: "1256", label: "Shounen ai" }, { value: "1257", label: "SleepingAbandoned Children" },
|
||||||
|
{ value: "1258", label: "Adapted to drama cdAdapted to Drama CD" }, { value: "1259", label: "Single parentActing" }, { value: "1260", label: "Reincarnated in a game worldAbandoned Children" },
|
||||||
|
{ value: "1261", label: "Half-human protagonistAbility Steal" }, { value: "1262", label: "Beautiful female leadAlternate World" }, { value: "1263", label: "Reborn" },
|
||||||
|
{ value: "1264", label: "Absent parentsAntihero Protagonist" }, { value: "1265", label: "Time paradoxAdapted to Drama CD" }, { value: "1266", label: "PriestessesAdapted to Manhua" },
|
||||||
|
{ value: "1267", label: "Pro gamer" }, { value: "1268", label: "Virtual world" }, { value: "1269", label: "Online gaming" },
|
||||||
|
{ value: "1270", label: "Non-protagonist pov" }, { value: "1271", label: "Transformation" }, { value: "1272", label: "Psychological struggle" },
|
||||||
|
{ value: "1273", label: "Envy" }, { value: "1274", label: "Esports" }, { value: "1275", label: "Streamer" },
|
||||||
|
{ value: "1276", label: "Time loopAcademy" }, { value: "1277", label: "WitchesAbusive Characters" }, { value: "1278", label: "Inferiority complexAbsent Parents" },
|
||||||
|
{ value: "1279", label: "WishesCalm Protagonist" }, { value: "1280", label: "SuicidesAdapted to Drama CD" }, { value: "1281", label: "LawyersAcademy" },
|
||||||
|
{ value: "1282", label: "Lack of common senseAdventurers" }, { value: "1283", label: "Cold love interestsCo-Workers" }, { value: "1284", label: "Forgetful protagonistArrogant Characters" },
|
||||||
|
{ value: "1285", label: "Hiding true abilitiesAcademy" }, { value: "1286", label: "Fleet battlesAristocracy" }, { value: "1287", label: "Dragon hunting" },
|
||||||
|
{ value: "1288", label: "Strong female leadArranged Marriage" }, { value: "1289", label: "Protagonist falls in love firstBoss-Subordinate Relationship" }, { value: "1290", label: "Female protagonistFemale Protagonist" },
|
||||||
|
{ value: "1291", label: "Doting older siblingsAverage-looking Protagonist" }, { value: "1292", label: "SiblingsAmnesia" }, { value: "1293", label: "DystopiaAcademy" },
|
||||||
|
{ value: "1294", label: "Modern romance" }, { value: "1295", label: "Hero party" }, { value: "1296", label: "Early romanceArrogant Characters" },
|
||||||
|
{ value: "1297", label: "Complex family relationshipsAbusive Characters" }, { value: "1298", label: "Time paradoxAdopted Protagonist" }, { value: "1299", label: "Transported into a game worldAward-winning Work" },
|
||||||
|
{ value: "1300", label: "Determined protagonistCharacter Growth" }, { value: "1301", label: "Beautiful female leadBeautiful Female Lead" }, { value: "1302", label: "Scholar's life" },
|
||||||
|
{ value: "1303", label: "Seme protagonistAncient China" }, { value: "1304", label: "Possessive character" }, { value: "1305", label: "OrphansAbandoned Children" },
|
||||||
|
{ value: "1306", label: "Easy going lifeAncient China" }, { value: "1307", label: "Beautiful female lea" }, { value: "1308", label: "Manipulative charact" },
|
||||||
|
{ value: "1309", label: "Sword and magicAristocracy" }, { value: "1310", label: "Slow growth at startAccelerated Growth" }, { value: "1311", label: "Villainess noble gir" },
|
||||||
|
{ value: "1312", label: "Political dramaAristocracy" }, { value: "1313", label: "Lack of common senseAcademy" }, { value: "1314", label: "Ancient chinaAncient China" },
|
||||||
|
{ value: "1315", label: "Alternative history" }, { value: "1316", label: "Quick romance" }, { value: "1317", label: "Reverse domination" },
|
||||||
|
{ value: "1318", label: "Woman domination" }, { value: "1319", label: "Alternative world" }, { value: "1320", label: "Female dominant" },
|
||||||
|
{ value: "1321", label: "Prince" }, { value: "1322", label: "Black belly gongChildcare" }, { value: "1323", label: "Reverse haremBetrayal" },
|
||||||
|
{ value: "1324", label: "CursesAbandoned Children" }, { value: "1325", label: "Beautiful female leadAncient China" }, { value: "1326", label: "Carefree protagonistAngels" },
|
||||||
|
{ value: "1327", label: "Terminal illnessAncient Times" }, { value: "1328", label: "Parenting" }, { value: "1329", label: "CursesCurses" },
|
||||||
|
{ value: "1330", label: "Adapted to manhwaAdapted to Manhwa" }, { value: "1331", label: "Overprotective siblingsAbandoned Children" }, { value: "1332", label: "Childhood promiseAristocracy" },
|
||||||
|
{ value: "1333", label: "SpiesAdapted to Drama CD" }, { value: "1334", label: "ArmyAncient China" }, { value: "1335", label: "Shounen-ai subplotAcademy" },
|
||||||
|
{ value: "1336", label: "Regression" }, { value: "1337", label: "Hidden ability" }, { value: "1338", label: "Human x human relationship" },
|
||||||
|
{ value: "1339", label: "Imperial haremAncient China" }, { value: "1340", label: "Modern fantasyBrotherhood" }, { value: "1341", label: "Villainess noble girlsAdapted to Drama" },
|
||||||
|
{ value: "1342", label: "Determined protagonistAcademy" }, { value: "1343", label: "Fox girl" }, { value: "1344", label: "Human x non-human relationshipAnti-social Protagonist" },
|
||||||
|
{ value: "1345", label: "Returning from another worldActing" }, { value: "1346", label: "Mistaken identityBlind Dates" }, { value: "1347", label: "Devoted love interes" },
|
||||||
|
{ value: "1348", label: "Love interest falls" }, { value: "1349", label: "Wealthy charactersArtists" }, { value: "1350", label: "ComaAdapted to Manhwa" },
|
||||||
|
{ value: "1351", label: "Pretend loversArranged Marriage" }, { value: "1352", label: "Cd" }, { value: "1353", label: "Villainess noble girlsMaids" },
|
||||||
|
{ value: "1354", label: "Early romanceAbandoned Children" }, { value: "1355", label: "WerebeastsAge Regression" }, { value: "1356", label: "AffairAffair" },
|
||||||
|
{ value: "1357", label: "World hoppingCheats" }, { value: "1358", label: "Abandoned childrenAbandoned Children" }, { value: "1359", label: "Transported modern structureAndroids" },
|
||||||
|
{ value: "1360", label: "Non-humanoid protagonistAristocracy" }, { value: "1361", label: "Shield userAcademy" }, { value: "1362", label: "StalkersAdventurers" },
|
||||||
|
{ value: "1363", label: "Returning from another worldAcademy" }, { value: "1364", label: "Determined protagonistBusiness Management" }, { value: "1365", label: "Determined protagonistAristocracy" },
|
||||||
|
{ value: "1366", label: "Overpowered protagonistBetrayal" }, { value: "1367", label: "Proactive protagonistComedic Undertone" }, { value: "1368", label: "Past plays a big roleActing" },
|
||||||
|
{ value: "1369", label: "LivestreamingAlternate World" }, { value: "1370", label: "Beautiful female leadActing" }, { value: "1371", label: "Previous life talentArtists" },
|
||||||
|
{ value: "1372", label: "TeachersAdapted to Drama CD" }, { value: "1373", label: "Spirit usersAdventurers" }, { value: "1374", label: "InvisibilityAlternate World" },
|
||||||
|
{ value: "1375", label: "Returning from another worldAdapted to Manhwa" }, { value: "1376", label: "BeastkinAcademy" }, { value: "1377", label: "Reincarnated in another worldConfident Protagonist" },
|
||||||
|
{ value: "1378", label: "AdventurersAncient China" }, { value: "1379", label: "Emotionally weak protagonistAdventurers" }, { value: "1380", label: "Transported to another worldAbility Steal" },
|
||||||
|
{ value: "1381", label: "Martial artsAncient Times" }, { value: "1382", label: "Sect developmentMale Protagonist" }, { value: "1383", label: "Prodigy protagonist" },
|
||||||
|
{ value: "1384", label: "Innovative cultivation techniquesAdopted Protagonist" }, { value: "1385", label: "Shoujo-ai subplotAll-Girls School" }, { value: "1386", label: "RegressionHunters" },
|
||||||
|
{ value: "1387", label: "Emotionally weak protagonistAcademy" }, { value: "1388", label: "Powerful protagonist" }, { value: "1389", label: "MisunderstandingsArtifacts" },
|
||||||
|
{ value: "1390", label: "Adapted to manhwaAbility Steal" }, { value: "1391", label: "MunchkinAbility Steal" }, { value: "1392", label: "Politic" },
|
||||||
|
{ value: "1393", label: "Shounen aiModern Day" }, { value: "1394", label: "StrategistAdapted to Drama" }, { value: "1395", label: "Reincarnated as a monsterAncient Times" },
|
||||||
|
{ value: "1396", label: "Human-nonhuman relationshipAccelerated Growth" }, { value: "1397", label: "Weak protagonistBeautiful Female Lead" }, { value: "1398", label: "HuntersAcademy" },
|
||||||
|
{ value: "1399", label: "Fantasy worldCheats" }, { value: "1400", label: "Martial artsCultivation" }, { value: "1401", label: "Adoptedson" },
|
||||||
|
{ value: "1402", label: "Hunter" }, { value: "1403", label: "RegressionAcademy" }, { value: "1404", label: "Hiding true abilitiesDemon Lord" },
|
||||||
|
{ value: "1405", label: "Carefree protagonistAdapted to Manga" }, { value: "1406", label: "HypnosisAcademy" }, { value: "1407", label: "FairiesCheats" },
|
||||||
|
{ value: "1408", label: "Cute childrenCunning Protagonist" }, { value: "1409", label: "HuntersAlternate World" }, { value: "1410", label: "SingersCelebrities" },
|
||||||
|
{ value: "1411", label: "Complex family relationshipsAbsent Parents" }, { value: "1412", label: "CelebritiesActing" }, { value: "1413", label: "MerchantsBusiness Management" },
|
||||||
|
{ value: "1414", label: "Soul powerAccelerated Growth" }, { value: "1415", label: "R-15Adapted to Anime" }, { value: "1416", label: "Confident protagonis" },
|
||||||
|
{ value: "1417", label: "Strong love interestsAdapted to Anime" }, { value: "1418", label: "Reversible coupleAdapted to Anime" }, { value: "1419", label: "TeamworkAbsent Parents" },
|
||||||
|
{ value: "1420", label: "Sickly charactersAdapted to Drama CD" }, { value: "1421", label: "Witchcraft" }, { value: "1422", label: "Multiple love interests" },
|
||||||
|
{ value: "1423", label: "Beautiful female leads" }, { value: "1424", label: "Refiner" }, { value: "1425", label: "Souls merge" },
|
||||||
|
{ value: "1426", label: "Boy's Love Subplot" }, { value: "1427", label: "Historical fantasy" }, { value: "1428", label: "Myths" },
|
||||||
|
{ value: "1429", label: "Cold and calculating mc" }, { value: "1430", label: "Forbidden techniques" }, { value: "1431", label: "Overpowered main character" },
|
||||||
|
{ value: "1432", label: "Villain mc" }, { value: "1433", label: "Op" }, { value: "1434", label: "Side character" },
|
||||||
|
{ value: "1435", label: "Multiple destined ones" }, { value: "1436", label: "Bloodlnes" }, { value: "1437", label: "Overpowered system" },
|
||||||
|
{ value: "1438", label: "Assassin protagonist" }, { value: "1439", label: "Secret societies" }, { value: "1440", label: "Revenge & vendetta" },
|
||||||
|
{ value: "1441", label: "Twisted alliances" }, { value: "1442", label: "Bestkin" }, { value: "1443", label: "MagesBattle Academy" },
|
||||||
|
{ value: "1444", label: "Strong love interest" }, { value: "1445", label: "Mysterious family" }, { value: "1446", label: "Myrids worlds" },
|
||||||
|
{ value: "1447", label: "Portals" }, { value: "1448", label: "Forbidden love" }, { value: "1449", label: "Student-teacher romance" },
|
||||||
|
{ value: "1450", label: "Slow burn" }, { value: "1451", label: "Emotional angst" }, { value: "1452", label: "Obsession & desire" },
|
||||||
|
{ value: "1453", label: "Dream world" }, { value: "1454", label: "Overpowered mc" }, { value: "1455", label: "Physical refinement" },
|
||||||
|
{ value: "1456", label: "Mechas" }, { value: "1457", label: "Mystical powers" }, { value: "1458", label: "In love with the boss" },
|
||||||
|
{ value: "1459", label: "forbidden romance" }, { value: "1460", label: "Adventurer" }, { value: "1461", label: "Fantasy creaturesAdapted to Manhua" },
|
||||||
|
{ value: "1462", label: "Weak to strongCultivation" }, { value: "1463", label: "Power struggleMale Protagonist" }, { value: "1464", label: "Secret" },
|
||||||
|
{ value: "1465", label: "Relationsh" }, { value: "1466", label: "Prisons" }, { value: "1467", label: "Average" },
|
||||||
|
{ value: "1468", label: "Devoted love" }, { value: "1469", label: "Artifacts refiner" }, { value: "1470", label: "DungeonsAlchemy" },
|
||||||
|
{ value: "1471", label: "AssassinsAssassins" }, { value: "1472", label: "Sentient objectsBeasts" }, { value: "1473", label: "Power struggleBattle Academy" },
|
||||||
|
{ value: "1474", label: "Hansome male lead" }, { value: "1475", label: "Type-moonFanfiction" }, { value: "1476", label: "Soul cultivation" },
|
||||||
|
{ value: "1477", label: "Reincanation" }, { value: "1478", label: "Eastern" }, { value: "1479", label: "Royalfamily" },
|
||||||
|
{ value: "1480", label: "Fatedlove" }, { value: "1481", label: "Werewolf" }, { value: "1482", label: "Alpha" },
|
||||||
|
{ value: "1483", label: "Princess" }, { value: "1484", label: "Seductive" }, { value: "1485", label: "Abusedfl" },
|
||||||
|
{ value: "1486", label: "Arrangedmarriage" }, { value: "1487", label: "Campus" }, { value: "1488", label: "Cosmic entities" },
|
||||||
|
{ value: "1489", label: "Indifferent hero" }, { value: "1490", label: "Anti-hero protagonist" }, { value: "1491", label: "Tribulations" },
|
||||||
|
{ value: "1492", label: "Body cultivation" }, { value: "1493", label: "Trasported into a game world" }, { value: "1494", label: "Temporal anomaly" },
|
||||||
|
{ value: "1495", label: "Quantum resonance" }, { value: "1496", label: "Chrono singularity" }, { value: "1497", label: "Void beasts" },
|
||||||
|
{ value: "1498", label: "Fractured timeline" }, { value: "1499", label: "Echo technology" }, { value: "1500", label: "Temporal core" },
|
||||||
|
{ value: "1501", label: "Anomalous evolution" }, { value: "1502", label: "Reality containment" }, { value: "1503", label: "Conscious emergence" },
|
||||||
|
{ value: "1504", label: "Synthetic emotion" }, { value: "1505", label: "Reckless compassion" }, { value: "1506", label: "Hope persistence" },
|
||||||
|
{ value: "1507", label: "Silent reverence" }, { value: "1508", label: "Becoming human" }, { value: "1509", label: "Relational intimacy" },
|
||||||
|
{ value: "1510", label: "Sacred stillness" }, { value: "1511", label: "Identity disintegration" }, { value: "1512", label: "Existential memory" },
|
||||||
|
{ value: "1513", label: "Sovereign without a throne" }, { value: "1514", label: "Machine with a soul" }, { value: "1515", label: "Sacrifice & resurrection" },
|
||||||
|
{ value: "1516", label: "Blue sanctuary" }, { value: "1517", label: "Post-hierarchical society" }, { value: "1518", label: "Dystopian" },
|
||||||
|
{ value: "1519", label: "Assassin/hitman" }, { value: "1520", label: "Shapeshifting" }, { value: "1521", label: "Guilds & organizations" },
|
||||||
|
{ value: "1522", label: "Training & growth" }, { value: "1523", label: "Revenge & justice" }, { value: "1524", label: "Anti-heroine" },
|
||||||
|
{ value: "1525", label: "Hidden identity" }, { value: "1526", label: "Special bloodline/race" }, { value: "1527", label: "Legendary weapons" },
|
||||||
|
{ value: "1528", label: "Magic system & ranks" }, { value: "1529", label: "Stealth & illusions" }, { value: "1530", label: "Dark organizations" },
|
||||||
|
{ value: "1531", label: "Strongest" }, { value: "1532", label: "Demon king" }, { value: "1533", label: "Ruthless" },
|
||||||
|
{ value: "1534", label: "Cruel" }, { value: "1535", label: "Weird" }, { value: "1536", label: "Yin job" },
|
||||||
|
{ value: "1537", label: "Demon" }, { value: "1538", label: "Portal" }, { value: "1539", label: "Plottwist" },
|
||||||
|
{ value: "1540", label: "Masterpiece" }, { value: "1541", label: "Brahmyodha" }, { value: "1542", label: "The destroyer" },
|
||||||
|
{ value: "1543", label: "Based on a Visual Novel" }, { value: "1544", label: "Cold but kind protagonist" }, { value: "1545", label: "Family feud" },
|
||||||
|
{ value: "1546", label: "Ruthless enemies" }, { value: "1547", label: "Rise to power" }, { value: "1548", label: "Street smart" },
|
||||||
|
{ value: "1549", label: "Business genius" }, { value: "1550", label: "Strong male lead" }, { value: "1551", label: "Hidden heir" },
|
||||||
|
{ value: "1552", label: "Backstab" }, { value: "1553", label: "The divine scion" }, { value: "1554", label: "Donghua" },
|
||||||
|
{ value: "1555", label: "Funny" }, { value: "1556", label: "Sigma" }, { value: "1557", label: "Soul land" },
|
||||||
|
{ value: "1558", label: "Doulu dalu" }, { value: "1559", label: "Experiments" }, { value: "1560", label: "Business" },
|
||||||
|
{ value: "1561", label: "Political Systems" }, { value: "1562", label: "Abused Characters" }, { value: "1563", label: "Counter-Strike" },
|
||||||
|
{ value: "1564", label: "Anti-Hero Lead" }, { value: "1565", label: "Magic beast" }, { value: "1566", label: "Demon hunter" },
|
||||||
|
{ value: "1567", label: "Wizard" }, { value: "1568", label: "Witcher" }, { value: "1569", label: "Monster" },
|
||||||
|
{ value: "1570", label: "Other world" }, { value: "1571", label: "Redemption" }, { value: "1572", label: "Guns" },
|
||||||
|
{ value: "1573", label: "Editors" }, { value: "1574", label: "Technology Gap" }, { value: "1575", label: "Tutorial" },
|
||||||
|
{ value: "1576", label: "Frieren" }, { value: "1577", label: "Clans" }, { value: "1578", label: "Dieselpunk" },
|
||||||
|
{ value: "1579", label: "FriendshipMale Protagonist" }, { value: "1580", label: "Mecha" }, { value: "1581", label: "Scheming Protagonist" },
|
||||||
|
{ value: "1582", label: "The Witcher" }, { value: "1583", label: "Adaptable Protagonist" }, { value: "1584", label: "Ambitious Protagonist" },
|
||||||
|
{ value: "1585", label: "Devouring" }, { value: "1586", label: "Based on History" }, { value: "1587", label: "World Building" },
|
||||||
|
{ value: "1588", label: "Single Female Lead" }, { value: "1589", label: "Wrongly Accused" }, { value: "1590", label: "Magic Academy" },
|
||||||
|
{ value: "1591", label: "Legacies" }, { value: "1592", label: "Age" }, { value: "1593", label: "VampiresAdapted to Manhua" },
|
||||||
|
{ value: "1594", label: "Faith Dependent Deities" }, { value: "1595", label: "Gao Wu" }, { value: "1596", label: "Dark Fantasy" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch(
|
async search({ query, filters }) {
|
||||||
`${this.baseUrl}/ajax/searchLive?inputContent=${encodeURIComponent(query)}`,
|
// ------------------------------
|
||||||
{ headers: { "accept": "application/json" } }
|
// Lógica de Selección de Modo
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
// Si hay FILTROS activos (distintos a los default), usamos búsqueda AVANZADA (HTML)
|
||||||
|
const hasFilters = filters && Object.keys(filters).length > 0 && (
|
||||||
|
filters.sort !== 'bookmark' ||
|
||||||
|
filters.status !== '-1' ||
|
||||||
|
filters.chapters !== '0' ||
|
||||||
|
(filters.genres && filters.genres.length > 0) ||
|
||||||
|
(filters.tags && filters.tags.length > 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (hasFilters) {
|
||||||
|
return this.searchAdvanced(query, filters);
|
||||||
|
} else {
|
||||||
|
// Si es búsqueda simple por texto sin filtros especiales, usamos la API AJAX (Más rápida)
|
||||||
|
return this.searchSimple(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Modo Simple (AJAX) ---
|
||||||
|
async searchSimple(query) {
|
||||||
|
const url = `${this.baseUrl}/ajax/searchLive?inputContent=${encodeURIComponent(query || "")}`;
|
||||||
|
const res = await fetch(url, { headers: { "accept": "application/json" } });
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (!data.data) return [];
|
if (!data.data) return [];
|
||||||
|
|
||||||
return data.data.map(item => ({
|
return data.data.map(item => ({
|
||||||
@@ -22,10 +667,94 @@ class NovelFire {
|
|||||||
title: item.title,
|
title: item.title,
|
||||||
image: `https://novelfire.net/${item.image}`,
|
image: `https://novelfire.net/${item.image}`,
|
||||||
rating: item.rank ?? null,
|
rating: item.rank ?? null,
|
||||||
type: "book"
|
type: "book",
|
||||||
|
// Construir la URL completa es buena práctica
|
||||||
|
url: `${this.baseUrl}/book/${item.slug}`
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Modo Avanzado (HTML Scraping) ---
|
||||||
|
async searchAdvanced(query, filters) {
|
||||||
|
const url = new URL(`${this.baseUrl}/search-adv`);
|
||||||
|
|
||||||
|
// Parámetros por defecto para emular la request "all"
|
||||||
|
// NovelFire usa array params como country_id[]=1&country_id[]=2...
|
||||||
|
// Aquí hardcodeamos los orígenes "todos" para que no filtre por país salvo que quieras
|
||||||
|
[1, 2, 3, 4].forEach(id => url.searchParams.append('country_id[]', id));
|
||||||
|
|
||||||
|
url.searchParams.set('ctgcon', 'and'); // Categorías AND
|
||||||
|
url.searchParams.set('tagcon', 'and'); // Tags AND
|
||||||
|
|
||||||
|
// --- Filtro de Géneros (Categories) ---
|
||||||
|
if (filters && filters.genres) {
|
||||||
|
// El frontend manda string separado por comas "10,12,15"
|
||||||
|
const genreIds = String(filters.genres).split(',');
|
||||||
|
genreIds.forEach(id => {
|
||||||
|
if(id.trim()) url.searchParams.append('categories[]', id.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Filtro de Etiquetas (Tags) ---
|
||||||
|
if (filters && filters.tags) {
|
||||||
|
const tagIds = String(filters.tags).split(',');
|
||||||
|
tagIds.forEach(id => {
|
||||||
|
if(id.trim()) url.searchParams.append('tags[]', id.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Filtros Simples ---
|
||||||
|
if (filters) {
|
||||||
|
if (filters.status) url.searchParams.set('status', filters.status);
|
||||||
|
if (filters.sort) url.searchParams.set('sort', filters.sort);
|
||||||
|
if (filters.chapters && filters.chapters !== '0') {
|
||||||
|
url.searchParams.set('totalchapter', filters.chapters);
|
||||||
|
}
|
||||||
|
if (filters.rating) {
|
||||||
|
// Asumo que si el usuario filtra por rating, quiere rating alto (min 4 o 5)
|
||||||
|
// Aquí podrías hacerlo más complejo si el filtro rating tuviera opciones
|
||||||
|
// url.searchParams.set('ratcon', 'min');
|
||||||
|
// url.searchParams.set('rating', filters.rating);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Fetch HTML ---
|
||||||
|
// console.log("NovelFire Adv Search URL:", url.toString());
|
||||||
|
const res = await fetch(url.toString());
|
||||||
|
const html = await res.text();
|
||||||
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
$('.novel-item').each((_, el) => {
|
||||||
|
const $el = $(el);
|
||||||
|
|
||||||
|
const titleEl = $el.find('.novel-title a');
|
||||||
|
const title = titleEl.text().trim();
|
||||||
|
const href = titleEl.attr('href');
|
||||||
|
|
||||||
|
if (!href) return;
|
||||||
|
|
||||||
|
// Extraer slug del href "/book/slug"
|
||||||
|
const id = href.replace('/book/', '');
|
||||||
|
|
||||||
|
const img = $el.find('.novel-cover img').attr('data-src') ||
|
||||||
|
$el.find('.novel-cover img').attr('src');
|
||||||
|
|
||||||
|
const rating = $el.find('.info-rating').attr('data-rating');
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
image: img ? (img.startsWith('http') ? img : `${this.baseUrl}${img}`) : "",
|
||||||
|
rating: rating ? Number(rating) : null,
|
||||||
|
type: "book",
|
||||||
|
url: `${this.baseUrl}${href}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
async getMetadata(id) {
|
async getMetadata(id) {
|
||||||
const url = `https://novelfire.net/book/${id}`;
|
const url = `https://novelfire.net/book/${id}`;
|
||||||
const html = await (await fetch(url)).text();
|
const html = await (await fetch(url)).text();
|
||||||
@@ -95,6 +824,7 @@ class NovelFire {
|
|||||||
"columns[0][orderable]": "false",
|
"columns[0][orderable]": "false",
|
||||||
"columns[1][data]": "created_at",
|
"columns[1][data]": "created_at",
|
||||||
"columns[1][orderable]": "true",
|
"columns[1][orderable]": "true",
|
||||||
|
"columns[1][orderable]": "true",
|
||||||
"order[0][column]": 1,
|
"order[0][column]": 1,
|
||||||
"order[0][dir]": "asc",
|
"order[0][dir]": "asc",
|
||||||
start: 0,
|
start: 0,
|
||||||
|
|||||||
308
book/wattpad.js
308
book/wattpad.js
@@ -1,143 +1,267 @@
|
|||||||
class wattpad {
|
class Wattpad {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.baseUrl = "https://wattpad.com";
|
this.baseUrl = "https://www.wattpad.com";
|
||||||
|
this.apiUrl = "https://www.wattpad.com/v4";
|
||||||
this.type = "book-board";
|
this.type = "book-board";
|
||||||
this.mediaType = "ln";
|
this.mediaType = "ln"; // Light Novel
|
||||||
this.version = "1.0"
|
this.version = "1.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(queryObj) {
|
getFilters() {
|
||||||
const query = queryObj.query?.trim() || "";
|
return {
|
||||||
|
sort: {
|
||||||
|
label: "Sort By (Only for Explore)",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "hot", label: "Hot (Trending)" },
|
||||||
|
{ value: "new", label: "New (Latest)" },
|
||||||
|
{ value: "paid", label: "Paid Stories" }
|
||||||
|
],
|
||||||
|
default: "hot"
|
||||||
|
},
|
||||||
|
updated: {
|
||||||
|
label: "Last Updated (Search Only)",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "", label: "Any time" },
|
||||||
|
{ value: "24", label: "Today" },
|
||||||
|
{ value: "168", label: "This Week" },
|
||||||
|
{ value: "720", label: "This Month" },
|
||||||
|
{ value: "8760", label: "This Year" }
|
||||||
|
],
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
label: "Content Filters",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "completed", label: "Completed stories only" },
|
||||||
|
{ value: "paid", label: "Paid stories only (Hide free)" },
|
||||||
|
{ value: "free", label: "Free stories only (Hide Paid)" },
|
||||||
|
{ value: "mature", label: "Include mature content" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
label: "Tags (comma separated)",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "e.g. romance, vampire, magic"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
"Accept-Language": "en-US,en;q=0.9",
|
||||||
|
"Referer": "https://www.wattpad.com/"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async search({ query, page = 1, filters }) {
|
||||||
const limit = 15;
|
const limit = 15;
|
||||||
const offset = 0;
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
const url =
|
// 1. Preparar la Query (Texto + Etiquetas)
|
||||||
`${this.baseUrl}/v4/search/stories?` +
|
let finalQuery = (query || "").trim();
|
||||||
`query=${encodeURIComponent(query)}` +
|
if (filters?.tags) {
|
||||||
`&limit=${limit}&offset=${offset}&mature=false`;
|
const tags = String(filters.tags).split(",");
|
||||||
|
tags.forEach(t => {
|
||||||
|
const tag = t.trim();
|
||||||
|
if (tag) {
|
||||||
|
// Wattpad requiere que los tags en la búsqueda lleven #
|
||||||
|
finalQuery += ` #${tag.replace(/^#/, '')}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finalQuery = finalQuery.trim();
|
||||||
|
|
||||||
const json = await fetch(url).then(r => r.json());
|
let url;
|
||||||
|
|
||||||
return json.stories.map(n => ({
|
// 2. DECISIÓN: ¿Búsqueda o Exploración?
|
||||||
id: n.id,
|
if (finalQuery) {
|
||||||
title: n.title,
|
// CASO A: HAY BÚSQUEDA (Texto o Tags) -> Usar API de Search
|
||||||
image: n.cover,
|
url = new URL(`${this.apiUrl}/search/stories/`);
|
||||||
sampleImageUrl: n.cover,
|
url.searchParams.set("query", finalQuery);
|
||||||
tags: n.tags,
|
|
||||||
type: "book"
|
// Filtros exclusivos de búsqueda
|
||||||
}));
|
if (filters?.updated) {
|
||||||
|
url.searchParams.set("updateYoungerThan", filters.updated);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// CASO B: NO HAY BÚSQUEDA (Default) -> Usar API de Stories (Explorar)
|
||||||
|
// Esto cargará "Tendencias" o "Nuevos" cuando entres sin escribir nada.
|
||||||
|
url = new URL(`${this.apiUrl}/stories/`);
|
||||||
|
|
||||||
|
// Mapear el filtro 'sort' al parámetro 'filter' de la API
|
||||||
|
// Por defecto usamos 'hot' (Tendencias)
|
||||||
|
const filterVal = filters?.sort || "hot";
|
||||||
|
url.searchParams.set("filter", filterVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Filtros Comunes (Content)
|
||||||
|
let isMature = false;
|
||||||
|
if (filters?.content) {
|
||||||
|
const contentOpts = Array.isArray(filters.content)
|
||||||
|
? filters.content
|
||||||
|
: String(filters.content).split(',');
|
||||||
|
|
||||||
|
if (contentOpts.includes("completed")) url.searchParams.set("completed", "1");
|
||||||
|
if (contentOpts.includes("paid")) url.searchParams.set("paid", "1");
|
||||||
|
if (contentOpts.includes("free")) url.searchParams.set("paid", "0");
|
||||||
|
if (contentOpts.includes("mature")) isMature = true;
|
||||||
|
}
|
||||||
|
// Wattpad requiere mature=1 explícito para mostrar contenido adulto
|
||||||
|
url.searchParams.set("mature", isMature ? "1" : "0");
|
||||||
|
|
||||||
|
// 4. Parámetros Técnicos
|
||||||
|
url.searchParams.set("limit", limit.toString());
|
||||||
|
url.searchParams.set("offset", offset.toString());
|
||||||
|
|
||||||
|
// Solicitar campos específicos para obtener portadas, autor y estado
|
||||||
|
const fields = "stories(id,title,voteCount,readCount,commentCount,description,mature,completed,cover,url,numParts,isPaywalled,paidModel,length,language(id),user(name),lastPublishedPart(createDate),promoted,sponsor(name,avatar),tags),total,nextUrl";
|
||||||
|
url.searchParams.set("fields", fields);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url.toString(), { headers: this.headers });
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error(`Wattpad API Error: ${res.status}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
if (!json.stories) return [];
|
||||||
|
|
||||||
|
return json.stories.map(story => ({
|
||||||
|
id: story.id,
|
||||||
|
title: story.title,
|
||||||
|
image: story.cover,
|
||||||
|
type: "book",
|
||||||
|
// Datos extra para la UI
|
||||||
|
author: story.user?.name,
|
||||||
|
status: story.completed ? "Completed" : "Ongoing",
|
||||||
|
chapters: story.numParts,
|
||||||
|
rating: story.voteCount // Opcional: usar votos como rating
|
||||||
|
}));
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Wattpad search failed:", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMetadata(id) {
|
async getMetadata(id) {
|
||||||
const html = await fetch(`${this.baseUrl}/story/${id}`).then(r => r.text());
|
// Obtenemos metadatos básicos de la web para no depender solo de la API
|
||||||
|
const res = await fetch(`${this.baseUrl}/story/${id}`, { headers: this.headers });
|
||||||
|
const html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
|
// Intentar extraer datos del script de hidratación (más fiable)
|
||||||
const script = $('script')
|
const script = $('script')
|
||||||
.map((_, el) => $(el).html())
|
.map((_, el) => $(el).html())
|
||||||
.get()
|
.get()
|
||||||
.find(t => t?.includes('window.__remixContext'));
|
.find(t => t?.includes('window.__remixContext'));
|
||||||
|
|
||||||
if (!script) return null;
|
if (script) {
|
||||||
|
const jsonText = script.match(/window\.__remixContext\s*=\s*({[\s\S]*?});/)?.[1];
|
||||||
|
if (jsonText) {
|
||||||
|
try {
|
||||||
|
const ctx = JSON.parse(jsonText);
|
||||||
|
const route = ctx?.state?.loaderData?.["routes/story.$storyid"];
|
||||||
|
const story = route?.story;
|
||||||
|
|
||||||
const jsonText = script.match(/window\.__remixContext\s*=\s*({[\s\S]*?});/)?.[1];
|
if (story) {
|
||||||
if (!jsonText) return null;
|
return {
|
||||||
|
id: story.id,
|
||||||
let ctx;
|
title: story.title,
|
||||||
try {
|
format: "Novel",
|
||||||
ctx = JSON.parse(jsonText);
|
score: story.voteCount ?? 0,
|
||||||
} catch {
|
genres: story.tags || [],
|
||||||
return null;
|
status: story.completed ? "Completed" : "Ongoing",
|
||||||
|
published: story.createDate ? story.createDate.split("T")[0] : "???",
|
||||||
|
summary: story.description || "",
|
||||||
|
chapters: story.numParts || 0,
|
||||||
|
image: story.cover || "",
|
||||||
|
author: story.user?.name || ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = ctx?.state?.loaderData?.["routes/story.$storyid"];
|
// Fallback clásico
|
||||||
const story = route?.story;
|
const title = $('h1').first().text().trim();
|
||||||
const meta = route?.meta;
|
const image = $('.story-cover img').attr('src');
|
||||||
|
const summary = $('.description').text().trim();
|
||||||
if (!story) return null;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: story.id,
|
id,
|
||||||
title: story.title,
|
title: title || "Unknown",
|
||||||
format: "Novel",
|
format: "Novel",
|
||||||
score: story.voteCount ?? null,
|
image: image || "",
|
||||||
genres: story.tags || [],
|
summary: summary || "",
|
||||||
status: story.completed ? "Completed" : "Ongoing",
|
chapters: 0
|
||||||
published: story.createDate?.split("T")[0] || "???",
|
|
||||||
summary: story.description || meta?.description || "",
|
|
||||||
chapters: story.numParts || story.parts?.length || 1,
|
|
||||||
image: story.cover || meta?.image || "",
|
|
||||||
language: story.language?.name?.toLowerCase() || "unknown",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async findChapters(bookId) {
|
async findChapters(bookId) {
|
||||||
const html = await fetch(`${this.baseUrl}/story/${bookId}`).then(r => r.text());
|
const res = await fetch(`${this.baseUrl}/story/${bookId}`, { headers: this.headers });
|
||||||
const $ = this.cheerio.load(html);
|
const html = await res.text();
|
||||||
|
|
||||||
const script = $('script')
|
// Extraer estructura de capítulos del JSON
|
||||||
.map((_, el) => $(el).html())
|
const match = html.match(/window\.__remixContext\s*=\s*({[\s\S]*?});/);
|
||||||
.get()
|
if (!match?.[1]) return [];
|
||||||
.find(t => t?.includes('window.__remixContext'));
|
|
||||||
|
|
||||||
if (!script) return [];
|
|
||||||
|
|
||||||
const jsonText = script.match(/window\.__remixContext\s*=\s*({[\s\S]*?});/)?.[1];
|
|
||||||
if (!jsonText) return [];
|
|
||||||
|
|
||||||
let ctx;
|
|
||||||
try {
|
try {
|
||||||
ctx = JSON.parse(jsonText);
|
const ctx = JSON.parse(match[1]);
|
||||||
|
const story = ctx?.state?.loaderData?.["routes/story.$storyid"]?.story;
|
||||||
|
|
||||||
|
if (!story?.parts) return [];
|
||||||
|
|
||||||
|
return story.parts.map((part, i) => ({
|
||||||
|
id: String(part.id),
|
||||||
|
title: part.title || `Chapter ${i + 1}`,
|
||||||
|
number: i + 1,
|
||||||
|
index: i,
|
||||||
|
url: part.url
|
||||||
|
}));
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const story = ctx?.state?.loaderData?.["routes/story.$storyid"]?.story;
|
|
||||||
if (!story?.parts) return [];
|
|
||||||
|
|
||||||
return story.parts.map((p, i) => ({
|
|
||||||
id: String(p.id),
|
|
||||||
title: p.title || `Chapter ${i + 1}`,
|
|
||||||
number: i + 1,
|
|
||||||
language: story.language?.name?.toLowerCase() || "en",
|
|
||||||
index: i
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findChapterPages(chapterId) {
|
async findChapterPages(chapterId) {
|
||||||
const html = await fetch(`https://www.wattpad.com/amp/${chapterId}`).then(r => r.text());
|
// Usamos la versión AMP para obtener el contenido limpio en una sola petición
|
||||||
|
const url = `${this.baseUrl}/amp/${chapterId}`;
|
||||||
|
const res = await fetch(url, { headers: this.headers });
|
||||||
|
const html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const title = $('h2').first().text().trim();
|
const title = $('h2').first().text().trim();
|
||||||
|
|
||||||
const container = $('.story-body-type');
|
const container = $('.story-body-type');
|
||||||
if (!container.length) return "";
|
|
||||||
|
|
||||||
|
if (!container.length) return "Content not available or paid story.";
|
||||||
|
|
||||||
|
// Limpieza de elementos basura
|
||||||
container.find('[data-media-type="image"]').remove();
|
container.find('[data-media-type="image"]').remove();
|
||||||
|
|
||||||
const parts = [];
|
let content = "";
|
||||||
|
|
||||||
container.find('p').each((_, el) => {
|
if (title) content += `<h1>${title}</h1><br>`;
|
||||||
const text = $(el)
|
|
||||||
.html()
|
|
||||||
.replace(/\u00A0/g, " ")
|
|
||||||
.replace(/[ \t]+/g, " ")
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
if (text) parts.push(`<p>${text}</p>`);
|
container.contents().each((_, el) => {
|
||||||
|
if (el.tagName === 'p') {
|
||||||
|
const text = $(el).text().trim();
|
||||||
|
if (text) content += `<p>${text}</p>`;
|
||||||
|
} else if (el.tagName === 'amp-img') {
|
||||||
|
const src = $(el).attr('src');
|
||||||
|
if (src) content += `<img src="${src}" style="max-width:100%; display:block; margin: 10px auto;">`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
container.find('amp-img').each((_, el) => {
|
return content;
|
||||||
const src = $(el).attr('src');
|
|
||||||
const w = $(el).attr('width');
|
|
||||||
const h = $(el).attr('height');
|
|
||||||
if (src) parts.push(`<img src="${src}" width="${w}" height="${h}">`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
(title ? `<h1>${title}</h1>\n\n` : "") +
|
|
||||||
parts.join("\n\n")
|
|
||||||
).trim();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = wattpad;
|
module.exports = Wattpad;
|
||||||
@@ -2,65 +2,185 @@ class WeebCentral {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.baseUrl = "https://weebcentral.com";
|
this.baseUrl = "https://weebcentral.com";
|
||||||
this.type = "book-board";
|
this.type = "book-board";
|
||||||
this.version = "1.0";
|
this.version = "1.1";
|
||||||
this.mediaType = "manga";
|
this.mediaType = "manga";
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(url, options = {}) {
|
getFilters() {
|
||||||
return fetch(url, {
|
return {
|
||||||
...options,
|
sort: {
|
||||||
headers: {
|
label: "Sort By",
|
||||||
"User-Agent": "Mozilla/5.0",
|
type: "select",
|
||||||
...(options.headers || {}),
|
options: [
|
||||||
|
{ value: "Best Match", label: "Best Match" },
|
||||||
|
{ value: "Alphabet", label: "Alphabet" },
|
||||||
|
{ value: "Popularity", label: "Popularity" },
|
||||||
|
{ value: "Subscribers", label: "Subscribers" },
|
||||||
|
{ value: "Recently Added", label: "Recently Added" },
|
||||||
|
{ value: "Latest Updates", label: "Latest Updates" }
|
||||||
|
],
|
||||||
|
default: "Popularity"
|
||||||
},
|
},
|
||||||
});
|
order: {
|
||||||
|
label: "Sort Order",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "Descending", label: "Descending" },
|
||||||
|
{ value: "Ascending", label: "Ascending" }
|
||||||
|
],
|
||||||
|
default: "Descending"
|
||||||
|
},
|
||||||
|
official: {
|
||||||
|
label: "Official Translation",
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
{ value: "Any", label: "Any" },
|
||||||
|
{ value: "True", label: "True" },
|
||||||
|
{ value: "False", label: "False" }
|
||||||
|
],
|
||||||
|
default: "Any"
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: "Status",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "Ongoing", label: "Ongoing" },
|
||||||
|
{ value: "Complete", label: "Complete" },
|
||||||
|
{ value: "Hiatus", label: "Hiatus" },
|
||||||
|
{ value: "Canceled", label: "Canceled" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
label: "Type",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "Manga", label: "Manga" },
|
||||||
|
{ value: "Manhwa", label: "Manhwa" },
|
||||||
|
{ value: "Manhua", label: "Manhua" },
|
||||||
|
{ value: "OEL", label: "OEL" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
label: "Tags",
|
||||||
|
type: "multiselect",
|
||||||
|
options: [
|
||||||
|
{ value: "Action", label: "Action" },
|
||||||
|
{ value: "Adult", label: "Adult" },
|
||||||
|
{ value: "Adventure", label: "Adventure" },
|
||||||
|
{ value: "Comedy", label: "Comedy" },
|
||||||
|
{ value: "Doujinshi", label: "Doujinshi" },
|
||||||
|
{ value: "Drama", label: "Drama" },
|
||||||
|
{ value: "Ecchi", label: "Ecchi" },
|
||||||
|
{ value: "Fantasy", label: "Fantasy" },
|
||||||
|
{ value: "Gender Bender", label: "Gender Bender" },
|
||||||
|
{ value: "Harem", label: "Harem" },
|
||||||
|
{ value: "Hentai", label: "Hentai" },
|
||||||
|
{ value: "Historical", label: "Historical" },
|
||||||
|
{ value: "Horror", label: "Horror" },
|
||||||
|
{ value: "Isekai", label: "Isekai" },
|
||||||
|
{ value: "Josei", label: "Josei" },
|
||||||
|
{ value: "Lolicon", label: "Lolicon" },
|
||||||
|
{ value: "Martial Arts", label: "Martial Arts" },
|
||||||
|
{ value: "Mature", label: "Mature" },
|
||||||
|
{ value: "Mecha", label: "Mecha" },
|
||||||
|
{ value: "Mystery", label: "Mystery" },
|
||||||
|
{ value: "Psychological", label: "Psychological" },
|
||||||
|
{ value: "Romance", label: "Romance" },
|
||||||
|
{ value: "School Life", label: "School Life" },
|
||||||
|
{ value: "Sci-fi", label: "Sci-fi" },
|
||||||
|
{ value: "Seinen", label: "Seinen" },
|
||||||
|
{ value: "Shotacon", label: "Shotacon" },
|
||||||
|
{ 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: "Smut", label: "Smut" },
|
||||||
|
{ value: "Sports", label: "Sports" },
|
||||||
|
{ value: "Supernatural", label: "Supernatural" },
|
||||||
|
{ value: "Tragedy", label: "Tragedy" },
|
||||||
|
{ value: "Yaoi", label: "Yaoi" },
|
||||||
|
{ value: "Yuri", label: "Yuri" },
|
||||||
|
{ value: "Other", label: "Other" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(queryObj) {
|
get headers() {
|
||||||
const query = queryObj.query || "";
|
return {
|
||||||
const form = new URLSearchParams();
|
"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",
|
||||||
form.set("text", query);
|
"Referer": this.baseUrl + "/"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const res = await this.fetch(
|
async search({ query, page = 1, filters }) {
|
||||||
`${this.baseUrl}/search/simple?location=main`,
|
const limit = 32;
|
||||||
{
|
const offset = (page - 1) * limit;
|
||||||
method: "POST",
|
|
||||||
headers: {
|
const url = new URL(`${this.baseUrl}/search/data`);
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
"HX-Request": "true",
|
// Parámetros básicos
|
||||||
"HX-Trigger": "quick-search-input",
|
url.searchParams.set("limit", limit.toString());
|
||||||
"HX-Target": "quick-search-result",
|
url.searchParams.set("offset", offset.toString());
|
||||||
"HX-Current-URL": `${this.baseUrl}/`,
|
url.searchParams.set("display_mode", "Full Display");
|
||||||
},
|
|
||||||
body: form.toString(),
|
// Texto de búsqueda
|
||||||
|
if (query && query.trim().length > 0) {
|
||||||
|
url.searchParams.set("text", query.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtros
|
||||||
|
if (filters) {
|
||||||
|
if (filters.sort) url.searchParams.set("sort", filters.sort);
|
||||||
|
if (filters.order) url.searchParams.set("order", filters.order);
|
||||||
|
if (filters.official) url.searchParams.set("official", filters.official);
|
||||||
|
|
||||||
|
// Multiselects
|
||||||
|
if (filters.status) {
|
||||||
|
const vals = String(filters.status).split(',');
|
||||||
|
vals.forEach(v => { if(v.trim()) url.searchParams.append("included_status", v.trim()); });
|
||||||
}
|
}
|
||||||
);
|
if (filters.type) {
|
||||||
|
const vals = String(filters.type).split(',');
|
||||||
|
vals.forEach(v => { if(v.trim()) url.searchParams.append("included_type", v.trim()); });
|
||||||
|
}
|
||||||
|
if (filters.tags) {
|
||||||
|
const vals = String(filters.tags).split(',');
|
||||||
|
vals.forEach(v => { if(v.trim()) url.searchParams.append("included_tag", v.trim()); });
|
||||||
|
}
|
||||||
|
} else if (!query) {
|
||||||
|
// Default para "Latest" si no hay query ni filtros
|
||||||
|
url.searchParams.set("sort", "Popularity");
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(url.toString(), { headers: this.headers });
|
||||||
|
if (!res.ok) return [];
|
||||||
|
|
||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
|
|
||||||
$("#quick-search-result > div > a").each((_, el) => {
|
$("article > section > a").each((_, el) => {
|
||||||
const link = $(el).attr("href");
|
const href = $(el).attr("href");
|
||||||
if (!link) return;
|
if (!href) return;
|
||||||
|
|
||||||
const idMatch = link.match(/\/series\/([^/]+)/);
|
const idMatch = href.match(/\/series\/([^/]+)/);
|
||||||
if (!idMatch) return;
|
if (!idMatch) return;
|
||||||
|
|
||||||
const title = $(el).find(".flex-1").text().trim();
|
// Título es el último div sin clase
|
||||||
|
const title = $(el).find("div:not([class]):last-child").text().trim();
|
||||||
|
|
||||||
let image =
|
// Imagen
|
||||||
$(el).find("source").attr("srcset") ||
|
let image = $(el).find("source").attr("srcset") || $(el).find("img").attr("src");
|
||||||
$(el).find("img").attr("src") ||
|
if (image) image = image.replace("small", "normal");
|
||||||
null;
|
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
id: idMatch[1],
|
id: idMatch[1],
|
||||||
title,
|
title,
|
||||||
image,
|
image: image || "",
|
||||||
rating: null,
|
type: "book"
|
||||||
type: "book",
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,83 +188,76 @@ class WeebCentral {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getMetadata(id) {
|
async getMetadata(id) {
|
||||||
const res = await this.fetch(`${this.baseUrl}/series/${id}`, {
|
const res = await fetch(`${this.baseUrl}/series/${id}`, { headers: this.headers });
|
||||||
headers: { Referer: `${this.baseUrl}/series/${id}` },
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error("Metadata failed");
|
if (!res.ok) throw new Error("Metadata failed");
|
||||||
|
|
||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const title =
|
// Sección 1: Info (Imagen, Autor, Tags, Status)
|
||||||
$("section.md\\:w-8\\/12 h1").first().text().trim() || "";
|
const section1 = $("section[x-data] > section").eq(0);
|
||||||
|
|
||||||
|
let image = section1.find("source").attr("srcset") || section1.find("img").attr("src");
|
||||||
|
if(image) image = image.replace("small", "normal");
|
||||||
|
|
||||||
|
const authors = [];
|
||||||
|
section1.find("li:has(strong:contains('Author')) > span > a").each((_, el) => {
|
||||||
|
authors.push($(el).text().trim());
|
||||||
|
});
|
||||||
|
|
||||||
const genres = [];
|
const genres = [];
|
||||||
$("li strong")
|
section1.find("li:has(strong:contains('Tag'), strong:contains('Type')) a").each((_, el) => {
|
||||||
.filter((_, el) => $(el).text().includes("Tags"))
|
genres.push($(el).text().trim());
|
||||||
.parent()
|
});
|
||||||
.find("a")
|
|
||||||
.each((_, a) => {
|
const statusRaw = section1.find("li:has(strong:contains('Status')) > a").text().trim().toLowerCase();
|
||||||
genres.push($(a).text().trim());
|
let status = "unknown";
|
||||||
|
if (statusRaw.includes("ongoing")) status = "ongoing";
|
||||||
|
else if (statusRaw.includes("complete")) status = "completed";
|
||||||
|
else if (statusRaw.includes("hiatus")) status = "hiatus";
|
||||||
|
else if (statusRaw.includes("canceled")) status = "cancelled";
|
||||||
|
|
||||||
|
// Sección 2: Título y Descripción
|
||||||
|
const section2 = $("section[x-data] > section").eq(1);
|
||||||
|
const title = section2.find("h1").first().text().trim();
|
||||||
|
|
||||||
|
let descText = section2.find("li:has(strong:contains('Description')) > p").text().trim();
|
||||||
|
descText = descText.replace("NOTE:", "\n\nNOTE:");
|
||||||
|
|
||||||
|
// Añadir series relacionadas y nombres alternativos a la descripción (como en la app)
|
||||||
|
const related = section2.find("li:has(strong:contains('Related Series')) li");
|
||||||
|
if (related.length > 0) {
|
||||||
|
descText += "\n\nRelated Series:";
|
||||||
|
related.each((_, el) => {
|
||||||
|
descText += `\n• ${$(el).text().trim()}`;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const status =
|
const alts = section2.find("li:has(strong:contains('Associated Name')) li");
|
||||||
$("li strong")
|
if (alts.length > 0) {
|
||||||
.filter((_, el) => $(el).text().includes("Status"))
|
descText += "\n\nAssociated Names:";
|
||||||
.parent()
|
alts.each((_, el) => {
|
||||||
.find("a")
|
descText += `\n• ${$(el).text().trim()}`;
|
||||||
.first()
|
});
|
||||||
.text()
|
}
|
||||||
.trim() || "unknown";
|
|
||||||
|
|
||||||
const published =
|
|
||||||
$("li strong")
|
|
||||||
.filter((_, el) => $(el).text().includes("Released"))
|
|
||||||
.parent()
|
|
||||||
.find("span")
|
|
||||||
.first()
|
|
||||||
.text()
|
|
||||||
.trim() || "";
|
|
||||||
|
|
||||||
const summary =
|
|
||||||
$("li strong")
|
|
||||||
.filter((_, el) => $(el).text().includes("Description"))
|
|
||||||
.parent()
|
|
||||||
.find("p")
|
|
||||||
.text()
|
|
||||||
.trim() || "";
|
|
||||||
|
|
||||||
const image =
|
|
||||||
$("section.flex picture source").attr("srcset") ||
|
|
||||||
$("section.flex picture img").attr("src") ||
|
|
||||||
null;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
format: "MANGA",
|
format: "MANGA",
|
||||||
score: 0,
|
score: 0,
|
||||||
genres: genres.join(", "),
|
genres,
|
||||||
status,
|
status,
|
||||||
published,
|
summary: descText,
|
||||||
summary,
|
chapters: 0,
|
||||||
chapters: "???",
|
author: authors.join(", "),
|
||||||
image,
|
image: image || ""
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async findChapters(mangaId) {
|
async findChapters(mangaId) {
|
||||||
const res = await this.fetch(
|
const res = await fetch(`${this.baseUrl}/series/${mangaId}/full-chapter-list`, { headers: this.headers });
|
||||||
`${this.baseUrl}/series/${mangaId}/full-chapter-list`,
|
if (!res.ok) return [];
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"HX-Request": "true",
|
|
||||||
"HX-Target": "chapter-list",
|
|
||||||
"HX-Current-URL": `${this.baseUrl}/series/${mangaId}`,
|
|
||||||
Referer: `${this.baseUrl}/series/${mangaId}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
@@ -152,25 +265,29 @@ class WeebCentral {
|
|||||||
const chapters = [];
|
const chapters = [];
|
||||||
const numRegex = /(\d+(?:\.\d+)?)/;
|
const numRegex = /(\d+(?:\.\d+)?)/;
|
||||||
|
|
||||||
$("div.flex.items-center").each((_, el) => {
|
$("div[x-data] > a").each((_, el) => {
|
||||||
const a = $(el).find("a");
|
const href = $(el).attr("href");
|
||||||
if (!a.length) return;
|
|
||||||
|
|
||||||
const href = a.attr("href");
|
|
||||||
if (!href) return;
|
if (!href) return;
|
||||||
|
|
||||||
const idMatch = href.match(/\/chapters\/([^/]+)/);
|
const idMatch = href.match(/\/chapters\/([^/]+)/);
|
||||||
if (!idMatch) return;
|
if (!idMatch) return;
|
||||||
|
|
||||||
const title = a.find("span.grow > span").first().text().trim();
|
const titleElement = $(el).find("span.flex > span").first();
|
||||||
|
const title = titleElement.text().trim();
|
||||||
const numMatch = title.match(numRegex);
|
const numMatch = title.match(numRegex);
|
||||||
|
|
||||||
|
// Intentar detectar scanlator por el color del stroke del SVG (imitando la lógica de Kotlin)
|
||||||
|
let scanlator = null;
|
||||||
|
const svgStroke = $(el).find("svg").attr("stroke");
|
||||||
|
if (svgStroke === "#d8b4fe") scanlator = "Official"; // Color morado en WeebCentral indica oficial
|
||||||
|
|
||||||
chapters.push({
|
chapters.push({
|
||||||
id: idMatch[1],
|
id: idMatch[1],
|
||||||
title,
|
title,
|
||||||
number: numMatch ? Number(numMatch[1]) : 0,
|
number: numMatch ? Number(numMatch[1]) : 0,
|
||||||
releaseDate: null,
|
scanlator,
|
||||||
index: 0,
|
releaseDate: null, // La fecha requiere parseo complejo, lo omitimos por ahora
|
||||||
|
index: 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -180,23 +297,19 @@ class WeebCentral {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findChapterPages(chapterId) {
|
async findChapterPages(chapterId) {
|
||||||
const res = await this.fetch(
|
const res = await fetch(
|
||||||
`${this.baseUrl}/chapters/${chapterId}/images?is_prev=False&reading_style=long_strip`,
|
`${this.baseUrl}/chapters/${chapterId}/images?is_prev=False&reading_style=long_strip`,
|
||||||
{
|
{ headers: this.headers }
|
||||||
headers: {
|
|
||||||
"HX-Request": "true",
|
|
||||||
"HX-Current-URL": `${this.baseUrl}/chapters/${chapterId}`,
|
|
||||||
Referer: `${this.baseUrl}/chapters/${chapterId}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!res.ok) return [];
|
||||||
|
|
||||||
const html = await res.text();
|
const html = await res.text();
|
||||||
const $ = this.cheerio.load(html);
|
const $ = this.cheerio.load(html);
|
||||||
|
|
||||||
const pages = [];
|
const pages = [];
|
||||||
|
|
||||||
$("section.flex-1 img").each((i, el) => {
|
$("section[x-data~='scroll'] > img").each((i, el) => {
|
||||||
const src = $(el).attr("src");
|
const src = $(el).attr("src");
|
||||||
if (src) {
|
if (src) {
|
||||||
pages.push({
|
pages.push({
|
||||||
@@ -207,19 +320,6 @@ class WeebCentral {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pages.length === 0) {
|
|
||||||
$("img").each((i, el) => {
|
|
||||||
const src = $(el).attr("src");
|
|
||||||
if (src) {
|
|
||||||
pages.push({
|
|
||||||
url: src,
|
|
||||||
index: i,
|
|
||||||
headers: { Referer: this.baseUrl },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
135
marketplace.json
135
marketplace.json
@@ -6,7 +6,8 @@
|
|||||||
"description": "Anime streaming provider.",
|
"description": "Anime streaming provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/Anicrush.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/Anicrush.js",
|
||||||
"domain": "https://anicrush.to/"
|
"icon": "https://anicrush.to/favicon.ico",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"AniDream": {
|
"AniDream": {
|
||||||
"name": "AniDream",
|
"name": "AniDream",
|
||||||
@@ -14,7 +15,8 @@
|
|||||||
"description": "Anime streaming provider.",
|
"description": "Anime streaming provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/AniDream.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/AniDream.js",
|
||||||
"domain": "https://anidream.cc/"
|
"icon": "https://anidream.cc/favicon.ico",
|
||||||
|
"lang": "ita"
|
||||||
},
|
},
|
||||||
"Animekai": {
|
"Animekai": {
|
||||||
"name": "Animekai",
|
"name": "Animekai",
|
||||||
@@ -22,7 +24,8 @@
|
|||||||
"description": "Anime streaming provider.",
|
"description": "Anime streaming provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/Animekai.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/Animekai.js",
|
||||||
"domain": "https://animekai.to/"
|
"icon": "https://play-lh.googleusercontent.com/lwR3a_qUxIKBkNJdwZl5gqed9Tev9KNM7LEdcgYx5TjWpqq2EAt7ZUdd-iQgIq6kqb2n-9G7-uihwlGBkJ0THg=w240-h480-rw",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"AnimePahe": {
|
"AnimePahe": {
|
||||||
"name": "AnimePahe",
|
"name": "AnimePahe",
|
||||||
@@ -30,7 +33,8 @@
|
|||||||
"description": "Anime streaming provider.",
|
"description": "Anime streaming provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/AnimePahe.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/AnimePahe.js",
|
||||||
"domain": "https://animepahe.ru/"
|
"domain": "https://animepahe.si/favicon.ico",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"OppaiStream": {
|
"OppaiStream": {
|
||||||
"name": "OppaiStream",
|
"name": "OppaiStream",
|
||||||
@@ -38,7 +42,8 @@
|
|||||||
"description": "Anime streaming provider.",
|
"description": "Anime streaming provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/OppaiStream.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/OppaiStream.js",
|
||||||
"domain": "https://oppaistream.to/",
|
"icon": "https://oppai.stream/assets/logo/favicon.ico?v=1",
|
||||||
|
"lang": "en",
|
||||||
"nsfw": true
|
"nsfw": true
|
||||||
},
|
},
|
||||||
"AnimeAV1": {
|
"AnimeAV1": {
|
||||||
@@ -47,7 +52,48 @@
|
|||||||
"description": "Anime provider with dubs and hard subs in spanish.",
|
"description": "Anime provider with dubs and hard subs in spanish.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/AnimeAV1.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/AnimeAV1.js",
|
||||||
"domain": "https://animeav1.com/"
|
"icon": "https://animeav1.com/favicon.png",
|
||||||
|
"lang": "es"
|
||||||
|
},
|
||||||
|
"HentaiLA": {
|
||||||
|
"name": "HentaiLA",
|
||||||
|
"type": "anime-board",
|
||||||
|
"description": "Anime nsfw provider with hard subs in spanish.",
|
||||||
|
"author": "lenafx",
|
||||||
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/hentaila.js",
|
||||||
|
"icon": "https://hentaila.com/favicon.ico",
|
||||||
|
"nsfw": true,
|
||||||
|
"lang": "es"
|
||||||
|
},
|
||||||
|
"MissAV": {
|
||||||
|
"name": "MissAV",
|
||||||
|
"type": "anime-board",
|
||||||
|
"description": ".",
|
||||||
|
"author": "lenafx",
|
||||||
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/missav.js",
|
||||||
|
"icon": "https://missav.live/img/favicon.ico",
|
||||||
|
"nsfw": true,
|
||||||
|
"lang": "jp"
|
||||||
|
},
|
||||||
|
"Rouvideo": {
|
||||||
|
"name": "Rouvideo",
|
||||||
|
"type": "anime-board",
|
||||||
|
"description": ".",
|
||||||
|
"author": "lenafx",
|
||||||
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/rouvideo.js",
|
||||||
|
"icon": "https://rou.video/favicon-32x32.png",
|
||||||
|
"nsfw": true,
|
||||||
|
"lang": "jp"
|
||||||
|
},
|
||||||
|
"xvideos": {
|
||||||
|
"name": "xvideos",
|
||||||
|
"type": "anime-board",
|
||||||
|
"description": ".",
|
||||||
|
"author": "lenafx",
|
||||||
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/xvideos.js",
|
||||||
|
"icon": "https://static-cdn77.xvideos-cdn.com/v3/img/skins/default/logo/xv.white.180.png",
|
||||||
|
"nsfw": true,
|
||||||
|
"lang": "multi"
|
||||||
},
|
},
|
||||||
"Anizone": {
|
"Anizone": {
|
||||||
"name": "Anizone",
|
"name": "Anizone",
|
||||||
@@ -55,7 +101,8 @@
|
|||||||
"description": "Multi language anime provider, soft subs and dubs.",
|
"description": "Multi language anime provider, soft subs and dubs.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/AniZone.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/AniZone.js",
|
||||||
"domain": "https://anizone.to/"
|
"icon": "",
|
||||||
|
"lang": "multi"
|
||||||
},
|
},
|
||||||
"animepictures": {
|
"animepictures": {
|
||||||
"name": "Anime Pictures",
|
"name": "Anime Pictures",
|
||||||
@@ -63,18 +110,7 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/animepictures.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/animepictures.js",
|
||||||
"domain": "https://anime-pictures.net/",
|
"icon": "https://anime-pictures.net/favicon.ico"
|
||||||
"nsfw": true
|
|
||||||
},
|
|
||||||
"asmhentai": {
|
|
||||||
"name": "ASM Hentai",
|
|
||||||
"type": "book-board",
|
|
||||||
"description": "Adult manga provider.",
|
|
||||||
"author": "lenafx",
|
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/asmhentai.js",
|
|
||||||
"domain": "https://asmhentai.com/",
|
|
||||||
"nsfw": true,
|
|
||||||
"broken": true
|
|
||||||
},
|
},
|
||||||
"gelbooru": {
|
"gelbooru": {
|
||||||
"name": "Gelbooru",
|
"name": "Gelbooru",
|
||||||
@@ -82,7 +118,7 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/gelbooru.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/gelbooru.js",
|
||||||
"domain": "https://gelbooru.com/",
|
"icon": "https://gelbooru.com/favicon.png",
|
||||||
"nsfw": true
|
"nsfw": true
|
||||||
},
|
},
|
||||||
"giphy": {
|
"giphy": {
|
||||||
@@ -91,7 +127,7 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/giphy.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/giphy.js",
|
||||||
"domain": "https://giphy.com/"
|
"icon": "https://giphy.com/static/img/favicon.png"
|
||||||
},
|
},
|
||||||
"HiAnime": {
|
"HiAnime": {
|
||||||
"name": "HiAnime",
|
"name": "HiAnime",
|
||||||
@@ -99,16 +135,8 @@
|
|||||||
"description": "English anime provider with soft subs and dubs",
|
"description": "English anime provider with soft subs and dubs",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/HiAnime.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/anime/HiAnime.js",
|
||||||
"domain": "https://hianime.to/"
|
"icon": "https://hianime.to/favicon.ico",
|
||||||
},
|
"lang": "en"
|
||||||
"lightnovelworld": {
|
|
||||||
"name": "Lightnovelworld",
|
|
||||||
"type": "book-board",
|
|
||||||
"description": ".",
|
|
||||||
"author": "lenafx",
|
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/lightnovelworld.js",
|
|
||||||
"domain": "https://lightnovelworld.org/",
|
|
||||||
"broken": true
|
|
||||||
},
|
},
|
||||||
"mangadex": {
|
"mangadex": {
|
||||||
"name": "Mangadex",
|
"name": "Mangadex",
|
||||||
@@ -116,7 +144,8 @@
|
|||||||
"description": "English manga provider.",
|
"description": "English manga provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/mangadex.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/mangadex.js",
|
||||||
"domain": "https://mangadex.org/"
|
"icon": "https://mangadex.org/favicon.ico",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"Comix": {
|
"Comix": {
|
||||||
"name": "Comix",
|
"name": "Comix",
|
||||||
@@ -124,7 +153,8 @@
|
|||||||
"description": "English manga provider.",
|
"description": "English manga provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/comix.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/comix.js",
|
||||||
"domain": "https://comix.to/"
|
"icon": "",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"MangaPill": {
|
"MangaPill": {
|
||||||
"name": "MangaPill",
|
"name": "MangaPill",
|
||||||
@@ -132,7 +162,8 @@
|
|||||||
"description": "English manga provider.",
|
"description": "English manga provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/mangapill.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/mangapill.js",
|
||||||
"domain": "https://mangafire.to/"
|
"icon": "https://mangapill.com/static/favicon/favicon-32x32.png",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"MangaFire": {
|
"MangaFire": {
|
||||||
"name": "MangaFire",
|
"name": "MangaFire",
|
||||||
@@ -140,7 +171,8 @@
|
|||||||
"description": "Multi language manga provider",
|
"description": "Multi language manga provider",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/mangafire.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/mangafire.js",
|
||||||
"domain": "https://mangafire.to/"
|
"icon": "https://s.mfcdn.cc/assets/sites/mangafire/favicon.png?v4",
|
||||||
|
"lang": "multi"
|
||||||
},
|
},
|
||||||
"WeebCentral": {
|
"WeebCentral": {
|
||||||
"name": "WeebCentral",
|
"name": "WeebCentral",
|
||||||
@@ -148,15 +180,8 @@
|
|||||||
"description": "English manga provider.",
|
"description": "English manga provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/weebcentral.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/weebcentral.js",
|
||||||
"domain": "https://weebcentral.com/"
|
"icon": "https://weebcentral.com/favicon.ico",
|
||||||
},
|
"lang": "en"
|
||||||
"mangapark": {
|
|
||||||
"name": "Mangapark",
|
|
||||||
"type": "book-board",
|
|
||||||
"description": "English manga provider.",
|
|
||||||
"author": "lenafx",
|
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/mangapark.js",
|
|
||||||
"domain": "https://mangapark.io/"
|
|
||||||
},
|
},
|
||||||
"nhentai": {
|
"nhentai": {
|
||||||
"name": "nhentai",
|
"name": "nhentai",
|
||||||
@@ -164,7 +189,8 @@
|
|||||||
"description": "Adult manga provider.",
|
"description": "Adult manga provider.",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/nhentai.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/nhentai.js",
|
||||||
"domain": "https://nhentai.net/",
|
"icon": "https://nhentai.net/static/favicon.ico",
|
||||||
|
"lang": "multi",
|
||||||
"nsfw": true
|
"nsfw": true
|
||||||
},
|
},
|
||||||
"novelbin": {
|
"novelbin": {
|
||||||
@@ -173,7 +199,8 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/novelbin.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/novelbin.js",
|
||||||
"domain": "https://novelbin.me/"
|
"icon": "https://novelbin.me/img/favicon.ico?v=1.68",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"novelfire": {
|
"novelfire": {
|
||||||
"name": "NovelFire",
|
"name": "NovelFire",
|
||||||
@@ -181,7 +208,8 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/novelfire.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/novelfire.js",
|
||||||
"domain": "https://novelfire.net/"
|
"icon": "https://novelfire.net/logo.ico?v=1.9",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"realbooru": {
|
"realbooru": {
|
||||||
"name": "Realbooru",
|
"name": "Realbooru",
|
||||||
@@ -189,7 +217,7 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/realbooru.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/realbooru.js",
|
||||||
"domain": "https://realbooru.com/",
|
"icon": "https://realbooru.com/favicon-32x32.png?v=1",
|
||||||
"nsfw": true
|
"nsfw": true
|
||||||
},
|
},
|
||||||
"rule34": {
|
"rule34": {
|
||||||
@@ -198,7 +226,7 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/rule34.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/rule34.js",
|
||||||
"domain": "https://rule34.xxx/",
|
"icon": "https://rule34.xxx/favicon.ico?v=2",
|
||||||
"nsfw": true
|
"nsfw": true
|
||||||
},
|
},
|
||||||
"tenor": {
|
"tenor": {
|
||||||
@@ -207,7 +235,7 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/tenor.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/tenor.js",
|
||||||
"domain": "https://tenor.com/"
|
"icon": "https://tenor.com/assets/img/favicon/favicon-16x16.png"
|
||||||
},
|
},
|
||||||
"waifupics": {
|
"waifupics": {
|
||||||
"name": "Waifupics",
|
"name": "Waifupics",
|
||||||
@@ -215,7 +243,7 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/waifupics.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/waifupics.js",
|
||||||
"domain": "https://waifu.pics/"
|
"domain": "https://waifu.pics/favicon.ico"
|
||||||
},
|
},
|
||||||
"wattpad": {
|
"wattpad": {
|
||||||
"name": "Wattpad",
|
"name": "Wattpad",
|
||||||
@@ -223,7 +251,8 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/wattpad.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/book/wattpad.js",
|
||||||
"domain": "https://www.wattpad.com/"
|
"icon": "https://www.wattpad.com/wp-web-assets/images/wattpad-logo.svg",
|
||||||
|
"lang": "en"
|
||||||
},
|
},
|
||||||
"ZeroChan": {
|
"ZeroChan": {
|
||||||
"name": "ZeroChan",
|
"name": "ZeroChan",
|
||||||
@@ -231,7 +260,7 @@
|
|||||||
"description": ".",
|
"description": ".",
|
||||||
"author": "lenafx",
|
"author": "lenafx",
|
||||||
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/ZeroChan.js",
|
"entry": "https://git.waifuboard.app/ItsSkaiya/WaifuBoard-Extensions/raw/branch/main/image/ZeroChan.js",
|
||||||
"domain": "https://www.zerochan.net/"
|
"icon": "https://static.zerochan.net/favicon.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user