updates and new extensions

This commit is contained in:
2026-01-13 17:26:06 +01:00
parent 83c51a82da
commit e8d64174fd
15 changed files with 3516 additions and 468 deletions

View File

@@ -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);