Files
anicrush-api/mapper.js
shafat-96 3a649d01df fix
2026-02-14 23:33:44 +06:00

217 lines
6.0 KiB
JavaScript

const axios = require('axios');
const stringSimilarity = require('string-similarity');
const PROXY_PRO = process.env.PROXY_PRO || "";
const PROXY_PRO2 = process.env.PROXY_PRO2;
// Common headers for API requests
const getCommonHeaders = () => ({
'Accept': 'application/json, text/plain, */*',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
'x-site': 'anicrush',
'Referer': 'https://anicrush.to/',
'Origin': 'https://anicrush.to',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty'
});
// Minimal GraphQL query for AniList
const ANILIST_QUERY = `
query ($id: Int) {
Media(id: $id, type: ANIME) {
id
title {
romaji
english
native
}
synonyms
format
seasonYear
}
}`;
// Normalize title
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();
}
// 🔥 UPDATED FUNCTION WITH PROPER LOGGING
async function getAniListDetails(anilistId) {
const targetUrl = 'https://graphql.anilist.co';
try {
console.log("⚡ Trying DIRECT AniList request...");
const response = await axios({
url: targetUrl,
method: 'POST',
data: {
query: ANILIST_QUERY,
variables: { id: parseInt(anilistId) }
},
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0'
}
});
if (!response.data?.data?.Media) {
throw new Error('Anime not found Direct');
}
console.log("✅ DIRECT REQUEST SUCCESS");
return response.data.data.Media;
} catch (error) {
console.error("❌ DIRECT REQUEST FAILED");
console.error("Status:", error.response?.status || "No response");
console.error("Reason:", error.response?.data || error.message);
// 🔁 Fallback to Proxy
try {
const proxyBase = PROXY_PRO2 || PROXY_PRO;
const sep = proxyBase.includes('?') ? '&' : '?';
const proxyUrl = `${proxyBase}${sep}url=${encodeURIComponent(targetUrl)}`;
console.log("🌐 Trying PROXY request...");
console.log("Proxy URL:", proxyUrl);
const response = await axios.post(proxyUrl, {
query: ANILIST_QUERY,
variables: { id: parseInt(anilistId) }
}, {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0'
},
timeout: 10000
});
if (!response.data?.data?.Media) {
throw new Error('Anime not found via Proxy');
}
console.log("✅ PROXY REQUEST SUCCESS");
return response.data.data.Media;
} catch (err2) {
console.error("❌ PROXY REQUEST ALSO FAILED");
console.error("Status:", err2.response?.status || "No response");
console.error("Reason:", err2.response?.data || err2.message);
throw new Error('Failed to fetch anime details from AniList');
}
}
}
// Search anime on anicrush
async function searchAnicrush(title) {
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
});
return response.data;
}
// Similarity
function calculateTitleSimilarity(title1, title2) {
if (!title1 || !title2) return 0;
return stringSimilarity.compareTwoStrings(
title1.toLowerCase(),
title2.toLowerCase()
) * 100;
}
function extractSeasonNumber(title) {
if (!title) return null;
const lower = title.toLowerCase();
let match = lower.match(/(?:season|cour|part)\s*(\d{1,2})/i);
if (match && match[1]) return parseInt(match[1], 10);
match = lower.match(/(\d{1,2})\s*$/);
if (match && match[1] && parseInt(match[1], 10) < 10)
return parseInt(match[1], 10);
return null;
}
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;
for (const result of anicrushResults.result.movies) {
for (const aTitle of anilistTitles) {
const similarity = calculateTitleSimilarity(aTitle, result.name);
if (similarity > highestScore) {
highestScore = similarity;
bestMatch = result;
}
}
}
return highestScore >= 60 ? bestMatch : null;
}
// Main mapper
async function mapAniListToAnicrush(anilistId) {
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 = findBestMatch(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: bestMatch.name,
type: bestMatch.type,
year: anilistData.seasonYear
};
}
module.exports = {
mapAniListToAnicrush,
getCommonHeaders
};