mirror of
https://github.com/shafat-96/anicrush-api.git
synced 2026-04-17 15:51:44 +00:00
fix
This commit is contained in:
685
mapper.js
685
mapper.js
@@ -12,28 +12,20 @@ const getCommonHeaders = () => ({
|
||||
'sec-fetch-dest': 'empty'
|
||||
});
|
||||
|
||||
// GraphQL query for AniList with synonyms
|
||||
// Minimal GraphQL query for AniList (title, format, year only)
|
||||
const ANILIST_QUERY = `
|
||||
query ($id: Int) {
|
||||
Media(id: $id, type: ANIME) {
|
||||
id
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
}
|
||||
synonyms
|
||||
episodes
|
||||
format
|
||||
status
|
||||
countryOfOrigin
|
||||
seasonYear
|
||||
description
|
||||
genres
|
||||
tags {
|
||||
name
|
||||
}
|
||||
Media(id: $id, type: ANIME) {
|
||||
id
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
}
|
||||
synonyms
|
||||
format
|
||||
seasonYear
|
||||
}
|
||||
}`;
|
||||
|
||||
// Function to calculate string similarity using Levenshtein distance
|
||||
@@ -43,376 +35,285 @@ function calculateLevenshteinSimilarity(str1, str2) {
|
||||
str2 = str2.toLowerCase();
|
||||
|
||||
const matrix = Array(str2.length + 1).fill(null)
|
||||
.map(() => Array(str1.length + 1).fill(null));
|
||||
|
||||
for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
|
||||
for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
|
||||
|
||||
for (let j = 1; j <= str2.length; j++) {
|
||||
for (let i = 1; i <= str1.length; i++) {
|
||||
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
||||
matrix[j][i] = Math.min(
|
||||
matrix[j][i - 1] + 1,
|
||||
matrix[j - 1][i] + 1,
|
||||
matrix[j - 1][i - 1] + indicator
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const maxLength = Math.max(str1.length, str2.length);
|
||||
if (maxLength === 0) return 100;
|
||||
return ((maxLength - matrix[str2.length][str1.length]) / maxLength) * 100;
|
||||
}
|
||||
|
||||
// Function to calculate word-based similarity
|
||||
function calculateWordSimilarity(str1, str2) {
|
||||
if (!str1 || !str2) return 0;
|
||||
|
||||
const words1 = str1.toLowerCase().split(/\s+/).filter(Boolean);
|
||||
const words2 = str2.toLowerCase().split(/\s+/).filter(Boolean);
|
||||
|
||||
const commonWords = words1.filter(word => words2.includes(word));
|
||||
const totalUniqueWords = new Set([...words1, ...words2]).size;
|
||||
|
||||
return (commonWords.length / totalUniqueWords) * 100;
|
||||
}
|
||||
|
||||
// Function to normalize title for comparison
|
||||
function normalizeTitle(title) {
|
||||
if (!title) return '';
|
||||
return title.toLowerCase()
|
||||
.replace(/[^a-z0-9\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\u4e00-\u9faf\uff00-\uff9f]/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Function to get anime details from AniList
|
||||
async function getAniListDetails(anilistId) {
|
||||
try {
|
||||
const response = await axios({
|
||||
url: 'https://graphql.anilist.co',
|
||||
method: 'POST',
|
||||
data: {
|
||||
query: ANILIST_QUERY,
|
||||
variables: {
|
||||
id: parseInt(anilistId)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.data?.data?.Media) {
|
||||
throw new Error('Anime not found on AniList');
|
||||
}
|
||||
|
||||
return response.data.data.Media;
|
||||
} catch (error) {
|
||||
console.error('Error fetching from AniList:', error.message);
|
||||
throw new Error('Failed to fetch anime details from AniList');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to search anime on anicrush
|
||||
async function searchAnicrush(title) {
|
||||
if (!title) {
|
||||
throw new Error('Search title is required');
|
||||
}
|
||||
|
||||
try {
|
||||
const headers = getCommonHeaders();
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: 'https://api.anicrush.to/shared/v2/movie/list',
|
||||
params: {
|
||||
keyword: title,
|
||||
page: 1,
|
||||
limit: 24
|
||||
},
|
||||
headers
|
||||
});
|
||||
|
||||
if (response.data?.status === false) {
|
||||
throw new Error(response.data.message || 'Search failed');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
console.error('Search API error:', error.response.data);
|
||||
throw new Error(error.response.data.message || 'Search request failed');
|
||||
} else if (error.request) {
|
||||
console.error('No response received:', error.request);
|
||||
throw new Error('No response from search API');
|
||||
} else {
|
||||
console.error('Search error:', error.message);
|
||||
throw new Error('Failed to search anime');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get episode list from anicrush
|
||||
async function getEpisodeList(movieId) {
|
||||
if (!movieId) {
|
||||
throw new Error('Movie ID is required');
|
||||
}
|
||||
|
||||
try {
|
||||
const headers = getCommonHeaders();
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: 'https://api.anicrush.to/shared/v2/episode/list',
|
||||
params: {
|
||||
_movieId: movieId
|
||||
},
|
||||
headers
|
||||
});
|
||||
|
||||
if (response.data?.status === false) {
|
||||
throw new Error(response.data.message || 'Failed to fetch episode list');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
console.error('Episode list API error:', error.response.data);
|
||||
throw new Error(error.response.data.message || 'Episode list request failed');
|
||||
} else if (error.request) {
|
||||
console.error('No response received:', error.request);
|
||||
throw new Error('No response from episode list API');
|
||||
} else {
|
||||
console.error('Episode list error:', error.message);
|
||||
throw new Error('Failed to fetch episode list');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to calculate overall similarity between titles
|
||||
function calculateTitleSimilarity(title1, title2) {
|
||||
const levenshteinSim = calculateLevenshteinSimilarity(title1, title2);
|
||||
const wordSim = calculateWordSimilarity(title1, title2);
|
||||
|
||||
// Weight the similarities (favoring word-based matching for titles)
|
||||
return (levenshteinSim * 0.4) + (wordSim * 0.6);
|
||||
}
|
||||
|
||||
// Function to find best match between AniList and anicrush results
|
||||
function findBestMatch(anilistData, anicrushResults) {
|
||||
if (!anicrushResults?.result?.movies || !anicrushResults.result.movies.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Prepare all possible titles from AniList
|
||||
const titles = [
|
||||
anilistData.title.romaji,
|
||||
anilistData.title.english,
|
||||
anilistData.title.native,
|
||||
...(anilistData.synonyms || [])
|
||||
].filter(Boolean);
|
||||
|
||||
let bestMatch = null;
|
||||
let highestSimilarity = 0;
|
||||
|
||||
// Map AniList format to Anicrush type
|
||||
const formatTypeMap = {
|
||||
'TV': 'TV',
|
||||
'TV_SHORT': 'TV',
|
||||
'MOVIE': 'MOVIE',
|
||||
'SPECIAL': 'SPECIAL',
|
||||
'OVA': 'OVA',
|
||||
'ONA': 'ONA',
|
||||
'MUSIC': 'MUSIC'
|
||||
};
|
||||
|
||||
const expectedType = formatTypeMap[anilistData.format] || null;
|
||||
|
||||
// Check each result from anicrush
|
||||
for (const result of anicrushResults.result.movies) {
|
||||
const resultTitles = [
|
||||
result.name,
|
||||
result.name_english
|
||||
].filter(Boolean);
|
||||
|
||||
for (const resultTitle of resultTitles) {
|
||||
// Try each possible title combination
|
||||
for (const title of titles) {
|
||||
// Remove common words that might cause false matches
|
||||
const cleanTitle1 = normalizeTitle(title).replace(/\b(season|2nd|3rd|4th|5th|6th|7th|8th|9th|10th|11th|12th|13th|14th|15th|16th|17th|18th|19th|20th|21st|22nd|23rd|24th|25th|26th|27th|28th|29th|30th|31st|32nd|33rd|34th|35th|36th|37th|38th|39th|40th|41st|42nd|43rd|44th|45th|46th|47th|48th|49th|50th)\b/gi, '');
|
||||
const cleanTitle2 = normalizeTitle(resultTitle).replace(/\b(season|2nd|3rd|4th|5th|6th|7th|8th|9th|10th|11th|12th|13th|14th|15th|16th|17th|18th|19th|20th|21st|22nd|23rd|24th|25th|26th|27th|28th|29th|30th|31st|32nd|33rd|34th|35th|36th|37th|38th|39th|40th|41st|42nd|43rd|44th|45th|46th|47th|48th|49th|50th)\b/gi, '');
|
||||
|
||||
const similarity = calculateTitleSimilarity(cleanTitle1, cleanTitle2);
|
||||
|
||||
// Add bonus for year match
|
||||
let currentSimilarity = similarity;
|
||||
if (anilistData.seasonYear && result.aired_from) {
|
||||
const yearMatch = result.aired_from.includes(anilistData.seasonYear.toString());
|
||||
if (yearMatch) currentSimilarity += 10; // Reduced from 15 to 10
|
||||
}
|
||||
|
||||
// Add bonus for type match
|
||||
if (expectedType && result.type) {
|
||||
if (expectedType === result.type) {
|
||||
currentSimilarity += 25; // Increased from 10 to 25
|
||||
} else {
|
||||
currentSimilarity -= 30; // Add significant penalty for type mismatch
|
||||
}
|
||||
}
|
||||
|
||||
// Add penalty for episode count mismatch (if available)
|
||||
if (anilistData.episodes && result.episodes_count) {
|
||||
const episodeDiff = Math.abs(anilistData.episodes - result.episodes_count);
|
||||
if (episodeDiff > 2) { // Allow small differences
|
||||
currentSimilarity -= (episodeDiff * 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Add penalty for length difference
|
||||
const lengthDiff = Math.abs(cleanTitle1.length - cleanTitle2.length);
|
||||
if (lengthDiff > 10) {
|
||||
currentSimilarity -= (lengthDiff * 0.5);
|
||||
}
|
||||
|
||||
// Add penalty for completely different first words
|
||||
const firstWord1 = cleanTitle1.split(' ')[0];
|
||||
const firstWord2 = cleanTitle2.split(' ')[0];
|
||||
if (firstWord1 && firstWord2 && firstWord1 !== firstWord2) {
|
||||
currentSimilarity -= 20;
|
||||
}
|
||||
|
||||
if (currentSimilarity > highestSimilarity) {
|
||||
highestSimilarity = currentSimilarity;
|
||||
bestMatch = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only return a match if similarity is above threshold
|
||||
console.log(`Best match found with similarity: ${highestSimilarity}%`);
|
||||
return highestSimilarity >= 70 ? bestMatch : null; // Increased threshold from 60 to 70
|
||||
}
|
||||
|
||||
// Function to parse episode list response
|
||||
function parseEpisodeList(episodeList) {
|
||||
if (!episodeList?.result) return [];
|
||||
|
||||
const episodes = [];
|
||||
for (const [key, value] of Object.entries(episodeList.result)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(ep => {
|
||||
episodes.push({
|
||||
number: ep.number,
|
||||
name: ep.name,
|
||||
name_english: ep.name_english,
|
||||
is_filler: ep.is_filler
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
return episodes.sort((a, b) => a.number - b.number);
|
||||
}
|
||||
|
||||
// Function to fetch ani.zip mappings
|
||||
async function getAniZipMappings(anilistId) {
|
||||
try {
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: `https://api.ani.zip/mappings?anilist_id=${anilistId}`,
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching ani.zip mappings:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Main mapper function
|
||||
async function mapAniListToAnicrush(anilistId) {
|
||||
try {
|
||||
// Get AniList details
|
||||
const anilistData = await getAniListDetails(anilistId);
|
||||
|
||||
// Get ani.zip mappings for episode images
|
||||
const aniZipData = await getAniZipMappings(anilistId);
|
||||
|
||||
// Try all possible titles for search
|
||||
const titlesToTry = [
|
||||
anilistData.title.romaji,
|
||||
anilistData.title.english,
|
||||
anilistData.title.native,
|
||||
...(anilistData.synonyms || [])
|
||||
].filter(Boolean);
|
||||
|
||||
let searchResults = null;
|
||||
let bestMatch = null;
|
||||
|
||||
// Try each title until we find a match
|
||||
for (const title of titlesToTry) {
|
||||
console.log(`Trying title: ${title}`);
|
||||
searchResults = await searchAnicrush(title);
|
||||
bestMatch = findBestMatch(anilistData, searchResults);
|
||||
if (bestMatch) break;
|
||||
}
|
||||
|
||||
if (!bestMatch) {
|
||||
throw new Error('No matching anime found on anicrush');
|
||||
}
|
||||
|
||||
// Get episode list
|
||||
const episodeList = await getEpisodeList(bestMatch.id);
|
||||
const parsedEpisodes = parseEpisodeList(episodeList);
|
||||
|
||||
// Create episode mapping with images from ani.zip
|
||||
const episodes = parsedEpisodes.map(ep => {
|
||||
const aniZipEpisode = aniZipData?.episodes?.[ep.number] || {};
|
||||
return {
|
||||
number: ep.number,
|
||||
name: ep.name,
|
||||
name_english: ep.name_english,
|
||||
is_filler: ep.is_filler,
|
||||
id: `${bestMatch.id}?episode=${ep.number}`,
|
||||
image: aniZipEpisode.image || null,
|
||||
overview: aniZipEpisode.overview || null,
|
||||
airDate: aniZipEpisode.airDate || null,
|
||||
runtime: aniZipEpisode.runtime || null
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
anilist_id: anilistId,
|
||||
anicrush_id: bestMatch.id,
|
||||
titles: {
|
||||
romaji: anilistData.title.romaji,
|
||||
english: anilistData.title.english,
|
||||
native: anilistData.title.native,
|
||||
synonyms: anilistData.synonyms,
|
||||
anicrush: bestMatch.name,
|
||||
anicrush_english: bestMatch.name_english,
|
||||
additional: aniZipData?.titles || {}
|
||||
},
|
||||
type: bestMatch.type,
|
||||
total_episodes: episodes.length,
|
||||
episodes: episodes,
|
||||
format: anilistData.format,
|
||||
status: anilistData.status,
|
||||
mal_score: bestMatch.mal_score,
|
||||
genres: bestMatch.genres,
|
||||
country_of_origin: anilistData.countryOfOrigin,
|
||||
year: anilistData.seasonYear,
|
||||
description: anilistData.description,
|
||||
images: aniZipData?.images || []
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Mapper error:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mapAniListToAnicrush,
|
||||
getCommonHeaders,
|
||||
getAniZipMappings
|
||||
};
|
||||
.map(() => Array(str1.length + 1).fill(null));
|
||||
|
||||
for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
|
||||
for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
|
||||
|
||||
for (let j = 1; j <= str2.length; j++) {
|
||||
for (let i = 1; i <= str1.length; i++) {
|
||||
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
||||
matrix[j][i] = Math.min(
|
||||
matrix[j][i - 1] + 1,
|
||||
matrix[j - 1][i] + 1,
|
||||
matrix[j - 1][i - 1] + indicator
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const maxLength = Math.max(str1.length, str2.length);
|
||||
if (maxLength === 0) return 100;
|
||||
return ((maxLength - matrix[str2.length][str1.length]) / maxLength) * 100;
|
||||
}
|
||||
|
||||
// Function to calculate word-based similarity
|
||||
function calculateWordSimilarity(str1, str2) {
|
||||
if (!str1 || !str2) return 0;
|
||||
|
||||
const words1 = str1.toLowerCase().split(/\s+/).filter(Boolean);
|
||||
const words2 = str2.toLowerCase().split(/\s+/).filter(Boolean);
|
||||
|
||||
const commonWords = words1.filter(word => words2.includes(word));
|
||||
const totalUniqueWords = new Set([...words1, ...words2]).size;
|
||||
|
||||
return (commonWords.length / totalUniqueWords) * 100;
|
||||
}
|
||||
|
||||
// Function to normalize title for comparison
|
||||
function normalizeTitle(title) {
|
||||
if (!title) return '';
|
||||
return title.toLowerCase()
|
||||
.replace(/[^a-z0-9\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\u4e00-\u9faf\uff00-\uff9f]/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Function to get anime details from AniList
|
||||
async function getAniListDetails(anilistId) {
|
||||
try {
|
||||
const response = await axios({
|
||||
url: 'https://graphql.anilist.co',
|
||||
method: 'POST',
|
||||
data: {
|
||||
query: ANILIST_QUERY,
|
||||
variables: {
|
||||
id: parseInt(anilistId)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.data?.data?.Media) {
|
||||
throw new Error('Anime not found on AniList');
|
||||
}
|
||||
|
||||
return response.data.data.Media;
|
||||
} catch (error) {
|
||||
console.error('Error fetching from AniList:', error.message);
|
||||
throw new Error('Failed to fetch anime details from AniList');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to search anime on anicrush
|
||||
async function searchAnicrush(title) {
|
||||
if (!title) {
|
||||
throw new Error('Search title is required');
|
||||
}
|
||||
|
||||
try {
|
||||
const headers = getCommonHeaders();
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: 'https://api.anicrush.to/shared/v2/movie/list',
|
||||
params: {
|
||||
keyword: title,
|
||||
page: 1,
|
||||
limit: 24
|
||||
},
|
||||
headers
|
||||
});
|
||||
|
||||
if (response.data?.status === false) {
|
||||
throw new Error(response.data.message || 'Search failed');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
console.error('Search API error:', error.response.data);
|
||||
throw new Error(error.response.data.message || 'Search request failed');
|
||||
} else if (error.request) {
|
||||
console.error('No response received:', error.request);
|
||||
throw new Error('No response from search API');
|
||||
} else {
|
||||
console.error('Search error:', error.message);
|
||||
throw new Error('Failed to search anime');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get episode list from anicrush
|
||||
async function getEpisodeList(movieId) {
|
||||
if (!movieId) {
|
||||
throw new Error('Movie ID is required');
|
||||
}
|
||||
|
||||
try {
|
||||
const headers = getCommonHeaders();
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: 'https://api.anicrush.to/shared/v2/episode/list',
|
||||
params: {
|
||||
_movieId: movieId
|
||||
},
|
||||
headers
|
||||
});
|
||||
|
||||
if (response.data?.status === false) {
|
||||
throw new Error(response.data.message || 'Failed to fetch episode list');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
console.error('Episode list API error:', error.response.data);
|
||||
throw new Error(error.response.data.message || 'Episode list request failed');
|
||||
} else if (error.request) {
|
||||
console.error('No response received:', error.request);
|
||||
throw new Error('No response from episode list API');
|
||||
} else {
|
||||
console.error('Episode list error:', error.message);
|
||||
throw new Error('Failed to fetch episode list');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to calculate overall similarity between titles
|
||||
function calculateTitleSimilarity(title1, title2) {
|
||||
const levenshteinSim = calculateLevenshteinSimilarity(title1, title2);
|
||||
const wordSim = calculateWordSimilarity(title1, title2);
|
||||
|
||||
// Weight the similarities (favoring word-based matching for titles)
|
||||
return (levenshteinSim * 0.4) + (wordSim * 0.6);
|
||||
}
|
||||
|
||||
// Function to find best match between AniList and anicrush results
|
||||
function findBestMatch(anilistData, anicrushResults) {
|
||||
if (!anicrushResults?.result?.movies?.length) return null;
|
||||
|
||||
const anilistTitles = [
|
||||
anilistData.title.romaji,
|
||||
anilistData.title.english,
|
||||
anilistData.title.native,
|
||||
...(anilistData.synonyms || [])
|
||||
].filter(Boolean).map(normalizeTitle);
|
||||
|
||||
let bestMatch = null;
|
||||
let highestScore = 0;
|
||||
|
||||
const formatTypeMap = {
|
||||
TV: 'TV',
|
||||
TV_SHORT: 'TV',
|
||||
MOVIE: 'MOVIE',
|
||||
SPECIAL: 'SPECIAL',
|
||||
OVA: 'OVA',
|
||||
ONA: 'ONA',
|
||||
MUSIC: 'MUSIC'
|
||||
};
|
||||
const expectedType = formatTypeMap[anilistData.format] || null;
|
||||
|
||||
for (const result of anicrushResults.result.movies) {
|
||||
let typePenalty = 0;
|
||||
if (expectedType && result.type && expectedType !== result.type) {
|
||||
typePenalty = 15; // small penalty instead of skip
|
||||
}
|
||||
|
||||
const resultTitles = [result.name, result.name_english].filter(Boolean).map(normalizeTitle);
|
||||
|
||||
for (const aTitle of anilistTitles) {
|
||||
for (const rTitle of resultTitles) {
|
||||
const score = calculateTitleSimilarity(aTitle, rTitle);
|
||||
if (score > highestScore) {
|
||||
highestScore = score;
|
||||
bestMatch = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return highestScore >= 25 ? bestMatch : null; // accept if 25%+ similarity
|
||||
}
|
||||
|
||||
// Alias for compatibility
|
||||
const findBestMatchFuzzy = findBestMatch;
|
||||
|
||||
// Function to parse episode list response
|
||||
function parseEpisodeList(episodeList) {
|
||||
if (!episodeList?.result) return [];
|
||||
|
||||
const episodes = [];
|
||||
for (const [key, value] of Object.entries(episodeList.result)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(ep => {
|
||||
episodes.push({
|
||||
number: ep.number,
|
||||
name: ep.name,
|
||||
name_english: ep.name_english,
|
||||
is_filler: ep.is_filler
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
return episodes.sort((a, b) => a.number - b.number);
|
||||
}
|
||||
|
||||
// Function to fetch ani.zip mappings
|
||||
async function getAniZipMappings(anilistId) {
|
||||
try {
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: `https://api.ani.zip/mappings?anilist_id=${anilistId}`,
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching ani.zip mappings:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Main mapper function
|
||||
async function mapAniListToAnicrush(anilistId) {
|
||||
try {
|
||||
const anilistData = await getAniListDetails(anilistId);
|
||||
|
||||
const titlesToTry = [
|
||||
anilistData.title.romaji,
|
||||
anilistData.title.english,
|
||||
anilistData.title.native
|
||||
].filter(Boolean);
|
||||
|
||||
let bestMatch = null;
|
||||
for (const title of titlesToTry) {
|
||||
const searchResults = await searchAnicrush(title);
|
||||
bestMatch = findBestMatchFuzzy(anilistData, searchResults);
|
||||
if (bestMatch) break;
|
||||
}
|
||||
|
||||
if (!bestMatch) throw new Error('No matching anime found on anicrush');
|
||||
|
||||
return {
|
||||
anilist_id: anilistId,
|
||||
anicrush_id: bestMatch.id,
|
||||
title: {
|
||||
romaji: anilistData.title.romaji,
|
||||
english: anilistData.title.english,
|
||||
native: anilistData.title.native,
|
||||
anicrush: bestMatch.name,
|
||||
anicrush_english: bestMatch.name_english
|
||||
},
|
||||
type: bestMatch.type,
|
||||
year: anilistData.seasonYear
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Mapper error:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mapAniListToAnicrush,
|
||||
getCommonHeaders
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user