fix unmapped anime

This commit is contained in:
janisslsm committed 2026-10-27 13:12:57 +02:00
1 parent 4ae2142e8f
commit bee6625a9e
3 files changed
+123 -12

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "Nuvio Providers", "name": "Nuvio Providers",
"version": "1.0.14", "version": "1.0.15",
"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.6", "version": "1.0.7",
"author": "Jānis L", "author": "Jānis L",
"supportedTypes": [ "supportedTypes": [
"movie", "movie",
+52 -5
View File
@@ -520,6 +520,7 @@ async function apiGet(path, query = {}) {
// src/miruro/extractor.js // src/miruro/extractor.js
var ANIMAP_URL = "https://animap.id/api/map"; var ANIMAP_URL = "https://animap.id/api/map";
var ANIZIP_URL = "https://api.ani.zip/mappings";
var ANILIST_URL = "https://graphql.anilist.co"; var ANILIST_URL = "https://graphql.anilist.co";
var SEARCH_LIMIT = 15; var SEARCH_LIMIT = 15;
var MAX_SEARCH_QUERIES = 5; var MAX_SEARCH_QUERIES = 5;
@@ -600,6 +601,45 @@ function getMapping(records, identifier, mediaType) {
} }
return { ids, titles, synonyms }; return { ids, titles, synonyms };
} }
async function getAniZipMapping(identifier) {
const queryKey = identifier.service === "tmdb" ? "themoviedb_id" : "imdb_id";
try {
const response = await fetchJson(
`${ANIZIP_URL}?${queryKey}=${encodeURIComponent(identifier.value)}`,
{ headers: { "Accept": "application/json" } }
);
const entries = Array.isArray(response) ? response : [response];
let mapping = null;
let titleTable = {};
for (const entry of entries) {
const candidate = entry && entry.mappings ? entry.mappings : entry;
if (candidate && asNumber(candidate.anilist_id)) {
mapping = candidate;
titleTable = entry && entry.titles && typeof entry.titles === "object" ? entry.titles : {};
break;
}
}
if (!mapping) {
return null;
}
const titles = [];
const synonyms = [];
for (const key of ["x-jat", "en", "ja"]) {
if (titleTable[key]) {
titles.push(String(titleTable[key]));
}
}
for (const [key, value] of Object.entries(titleTable)) {
if (!["x-jat", "en", "ja"].includes(key) && value) {
synonyms.push(String(value));
}
}
return { ids: [asNumber(mapping.anilist_id)], titles, synonyms };
} catch (error) {
console.warn(`[Miruro] AnimeAPI mapping for ${identifier.value} failed: ${error.message}`);
return null;
}
}
async function getAniListMeta(anilistId) { async function getAniListMeta(anilistId) {
if (anilistCache.has(anilistId)) { if (anilistCache.has(anilistId)) {
return anilistCache.get(anilistId); return anilistCache.get(anilistId);
@@ -926,18 +966,25 @@ async function resolveAnime(mediaId, mediaType, season, episode) {
if (!identifier) { if (!identifier) {
return null; return null;
} }
let mappingRecords; let mapping;
try { try {
mappingRecords = await fetchJson( const mappingRecords = await fetchJson(
`${ANIMAP_URL}/${identifier.service}/${encodeURIComponent(identifier.value)}`, `${ANIMAP_URL}/${identifier.service}/${encodeURIComponent(identifier.value)}`,
{ headers: { "Accept": "application/json" } } { headers: { "Accept": "application/json" } }
); );
mapping = getMapping(mappingRecords, identifier, mediaType);
} catch (error) { } catch (error) {
console.warn(`[Miruro] No anime mapping for ${mediaId}: ${error.message}`); console.warn(`[Miruro] AniMap lookup for ${mediaId} failed: ${error.message}`);
return null; mapping = { ids: [], titles: [], synonyms: [] };
} }
const mapping = getMapping(mappingRecords, identifier, mediaType);
if (mapping.ids.length === 0) { if (mapping.ids.length === 0) {
const fallback = await getAniZipMapping(identifier);
if (fallback) {
mapping = fallback;
}
}
if (mapping.ids.length === 0) {
console.warn(`[Miruro] No anime mapping for ${mediaId}`);
return null; return null;
} }
const requestedSeason = asNumber(season) || 1; const requestedSeason = asNumber(season) || 1;
+69 -5
View File
@@ -15,6 +15,7 @@
import { HEADERS, apiGet, fetchJson } from './http.js'; import { HEADERS, apiGet, fetchJson } from './http.js';
const ANIMAP_URL = 'https://animap.id/api/map'; const ANIMAP_URL = 'https://animap.id/api/map';
const ANIZIP_URL = 'https://api.ani.zip/mappings';
const ANILIST_URL = 'https://graphql.anilist.co'; const ANILIST_URL = 'https://graphql.anilist.co';
const SEARCH_LIMIT = 15; const SEARCH_LIMIT = 15;
const MAX_SEARCH_QUERIES = 5; const MAX_SEARCH_QUERIES = 5;
@@ -131,6 +132,60 @@ function getMapping(records, identifier, mediaType) {
return { ids, titles, synonyms }; return { ids, titles, synonyms };
} }
/**
* AniMap's legacy TMDB/IMDb mapping is missing for newer shows. AnimeAPI
* (api.ani.zip) indexes the same services and fills those gaps.
*/
async function getAniZipMapping(identifier) {
const queryKey = identifier.service === 'tmdb' ? 'themoviedb_id' : 'imdb_id';
try {
const response = await fetchJson(
`${ANIZIP_URL}?${queryKey}=${encodeURIComponent(identifier.value)}`,
{ headers: { 'Accept': 'application/json' } }
);
const entries = Array.isArray(response) ? response : [response];
let mapping = null;
let titleTable = {};
for (const entry of entries) {
const candidate = entry && entry.mappings ? entry.mappings : entry;
if (candidate && asNumber(candidate.anilist_id)) {
mapping = candidate;
titleTable = entry && entry.titles && typeof entry.titles === 'object'
? entry.titles
: {};
break;
}
}
if (!mapping) {
return null;
}
const titles = [];
const synonyms = [];
for (const key of ['x-jat', 'en', 'ja']) {
if (titleTable[key]) {
titles.push(String(titleTable[key]));
}
}
for (const [key, value] of Object.entries(titleTable)) {
if (!['x-jat', 'en', 'ja'].includes(key) && value) {
synonyms.push(String(value));
}
}
return { ids: [asNumber(mapping.anilist_id)], titles, synonyms };
} catch (error) {
console.warn(`[Miruro] AnimeAPI mapping for ${identifier.value} failed: ${error.message}`);
return null;
}
}
async function getAniListMeta(anilistId) { async function getAniListMeta(anilistId) {
if (anilistCache.has(anilistId)) { if (anilistCache.has(anilistId)) {
return anilistCache.get(anilistId); return anilistCache.get(anilistId);
@@ -611,21 +666,30 @@ async function resolveAnime(mediaId, mediaType, season, episode) {
return null; return null;
} }
let mappingRecords; let mapping;
try { try {
mappingRecords = await fetchJson( const mappingRecords = await fetchJson(
`${ANIMAP_URL}/${identifier.service}/${encodeURIComponent(identifier.value)}`, `${ANIMAP_URL}/${identifier.service}/${encodeURIComponent(identifier.value)}`,
{ headers: { 'Accept': 'application/json' } } { headers: { 'Accept': 'application/json' } }
); );
mapping = getMapping(mappingRecords, identifier, mediaType);
} catch (error) { } catch (error) {
console.warn(`[Miruro] No anime mapping for ${mediaId}: ${error.message}`); console.warn(`[Miruro] AniMap lookup for ${mediaId} failed: ${error.message}`);
return null; mapping = { ids: [], titles: [], synonyms: [] };
} }
const mapping = getMapping(mappingRecords, identifier, mediaType); if (mapping.ids.length === 0) {
const fallback = await getAniZipMapping(identifier);
if (fallback) {
mapping = fallback;
}
}
if (mapping.ids.length === 0) { if (mapping.ids.length === 0) {
console.warn(`[Miruro] No anime mapping for ${mediaId}`);
return null; return null;
} }