updates and new extensions
This commit is contained in:
@@ -3,7 +3,7 @@ class OppaiStream {
|
||||
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.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.ScoreWeight = {
|
||||
@@ -19,48 +19,178 @@ class OppaiStream {
|
||||
};
|
||||
}
|
||||
|
||||
async search(queryObj) {
|
||||
let tempquery = queryObj.query;
|
||||
getGenreOptions() {
|
||||
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 {
|
||||
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 $ = this.cheerio.load(html);
|
||||
|
||||
const movies = $("div.in-grid.episode-shown");
|
||||
|
||||
// 👉 si no hay resultados:
|
||||
if (movies.length <= 0) {
|
||||
// si hay filtros, no hacemos fallback por palabras
|
||||
if (hasFilters || !tempquery) return [];
|
||||
|
||||
// fallback normal cuando hay texto
|
||||
if (tempquery.includes(" ")) {
|
||||
tempquery = tempquery.split(/[\s:']+/).slice(0, -1).join(" ");
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
const movieList = [];
|
||||
const results = [];
|
||||
movies.each((_, el) => {
|
||||
const title = $(el).find(".title-ep").text().trim();
|
||||
const href = $(el).find("a").attr("href");
|
||||
const elObj = $(el);
|
||||
const title = elObj.find(".title-ep .title").text().trim();
|
||||
const href = elObj.find("a").attr("href");
|
||||
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) {
|
||||
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 [];
|
||||
|
||||
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",
|
||||
}];
|
||||
// 👉 si NO hay query, devolvemos todo (modo catálogo)
|
||||
return results;
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -74,6 +204,7 @@ class OppaiStream {
|
||||
try {
|
||||
// Decodificamos el ID para obtener la URL real de OppaiStream
|
||||
const decodedUrl = decodeURIComponent(id);
|
||||
console.log(decodedUrl)
|
||||
const html = await this.GETText(decodedUrl);
|
||||
const $ = this.cheerio.load(html);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user