fix adult search

This commit is contained in:
janisslsm committed 2026-10-27 13:18:54 +02:00
1 parent bee6625a9e
commit bb010e7062
3 files changed
+74 -47

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "Nuvio Providers", "name": "Nuvio Providers",
"version": "1.0.15", "version": "1.0.16",
"scrapers": [ "scrapers": [
{ {
"id": "bcine", "id": "bcine",
@@ -54,7 +54,7 @@
"id": "miruro", "id": "miruro",
"name": "Miruro", "name": "Miruro",
"description": "Anime streams from Miruro with subbed and dubbed sources.", "description": "Anime streams from Miruro with subbed and dubbed sources.",
"version": "1.0.7", "version": "1.0.8",
"author": "Jānis L", "author": "Jānis L",
"supportedTypes": [ "supportedTypes": [
"movie", "movie",
+29 -19
View File
@@ -714,12 +714,16 @@ function responseItems(response) {
function getExternalIds(anime, key) { function getExternalIds(anime, key) {
return asArray(anime && anime.external_ids ? anime.external_ids[key] : null).map((id) => asNumber(id)).filter((id) => id != null); return asArray(anime && anime.external_ids ? anime.external_ids[key] : null).map((id) => asNumber(id)).filter((id) => id != null);
} }
async function searchCatalogAnime(title) { async function searchCatalogAnime(title, includeAdult = false) {
const response = await apiGet("anime", { const query = {
q: title, q: title,
limit: SEARCH_LIMIT, limit: SEARCH_LIMIT,
sort: "-popularity" sort: "-popularity"
}); };
if (includeAdult) {
query.is_adult = true;
}
const response = await apiGet("anime", query);
return responseItems(response); return responseItems(response);
} }
function matchesAnyAniListId(anime, aniListIds) { function matchesAnyAniListId(anime, aniListIds) {
@@ -744,28 +748,34 @@ function buildSearchTitles(targetMeta, mapping) {
return title; return title;
}).slice(0, MAX_SEARCH_QUERIES); }).slice(0, MAX_SEARCH_QUERIES);
} }
async function findCatalogAnime(candidateIds, targetId, searchTitles) { function pickCatalogMatch(results, candidateIds, targetId) {
for (const title of searchTitles) {
let results;
try {
results = await searchCatalogAnime(title);
} catch (error) {
console.warn(`[Miruro] Catalog search "${title}" failed: ${error.message}`);
if (error.status === 403) {
break;
}
continue;
}
if (results.length === 0) { if (results.length === 0) {
continue; return null;
} }
const exact = results.find((anime) => getExternalIds(anime, "anilist").includes(Number(targetId))); const exact = results.find((anime) => getExternalIds(anime, "anilist").includes(Number(targetId)));
if (exact) { if (exact) {
return exact; return exact;
} }
const candidate = results.find((anime) => matchesAnyAniListId(anime, candidateIds)); return results.find((anime) => matchesAnyAniListId(anime, candidateIds)) || null;
if (candidate) { }
return candidate; async function findCatalogAnime(candidateIds, targetId, searchTitles) {
for (const includeAdult of [false, true]) {
const titles = includeAdult ? searchTitles.slice(0, 3) : searchTitles;
for (const title of titles) {
let results;
try {
results = await searchCatalogAnime(title, includeAdult);
} catch (error) {
console.warn(`[Miruro] Catalog search "${title}" failed: ${error.message}`);
if (error.status === 403) {
return null;
}
continue;
}
const match = pickCatalogMatch(results, candidateIds, targetId);
if (match) {
return match;
}
} }
} }
return null; return null;
+43 -26
View File
@@ -285,13 +285,20 @@ function getExternalIds(anime, key) {
.filter((id) => id != null); .filter((id) => id != null);
} }
async function searchCatalogAnime(title) { async function searchCatalogAnime(title, includeAdult = false) {
// Miruro rejects search limits above 15 with a generic 400. // Miruro rejects search limits above 15 with a generic 400. Its catalog
const response = await apiGet('anime', { // also hides adult entries unless is_adult=true is requested explicitly.
const query = {
q: title, q: title,
limit: SEARCH_LIMIT, limit: SEARCH_LIMIT,
sort: '-popularity' sort: '-popularity'
}); };
if (includeAdult) {
query.is_adult = true;
}
const response = await apiGet('anime', query);
return responseItems(response); return responseItems(response);
} }
@@ -327,26 +334,9 @@ function buildSearchTitles(targetMeta, mapping) {
.slice(0, MAX_SEARCH_QUERIES); .slice(0, MAX_SEARCH_QUERIES);
} }
async function findCatalogAnime(candidateIds, targetId, searchTitles) { function pickCatalogMatch(results, candidateIds, targetId) {
for (const title of searchTitles) {
let results;
try {
results = await searchCatalogAnime(title);
} catch (error) {
console.warn(`[Miruro] Catalog search "${title}" failed: ${error.message}`);
// A 403 means the catalog rejected the client itself (Cloudflare or
// an IP block); trying more title variants cannot help.
if (error.status === 403) {
break;
}
continue;
}
if (results.length === 0) { if (results.length === 0) {
continue; return null;
} }
const exact = results.find((anime) => getExternalIds(anime, 'anilist').includes(Number(targetId))); const exact = results.find((anime) => getExternalIds(anime, 'anilist').includes(Number(targetId)));
@@ -355,10 +345,37 @@ async function findCatalogAnime(candidateIds, targetId, searchTitles) {
return exact; return exact;
} }
const candidate = results.find((anime) => matchesAnyAniListId(anime, candidateIds)); return results.find((anime) => matchesAnyAniListId(anime, candidateIds)) || null;
}
if (candidate) { async function findCatalogAnime(candidateIds, targetId, searchTitles) {
return candidate; // The catalog excludes adult entries from a default search, so retry the
// same titles with is_adult=true when the normal pass found nothing.
for (const includeAdult of [false, true]) {
const titles = includeAdult ? searchTitles.slice(0, 3) : searchTitles;
for (const title of titles) {
let results;
try {
results = await searchCatalogAnime(title, includeAdult);
} catch (error) {
console.warn(`[Miruro] Catalog search "${title}" failed: ${error.message}`);
// A 403 means the catalog rejected the client itself (Cloudflare
// or an IP block); trying more title variants cannot help.
if (error.status === 403) {
return null;
}
continue;
}
const match = pickCatalogMatch(results, candidateIds, targetId);
if (match) {
return match;
}
} }
} }