diff --git a/mapper.js b/mapper.js index fb1d706..d3ed701 100644 --- a/mapper.js +++ b/mapper.js @@ -1,73 +1,33 @@ -const axios = require('axios'); - -// 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 (title, format, year only) -const ANILIST_QUERY = ` -query ($id: Int) { - Media(id: $id, type: ANIME) { - id - title { - romaji - english - native - } - synonyms - format - seasonYear - } -}`; - -// Function to calculate string similarity using Levenshtein distance -function calculateLevenshteinSimilarity(str1, str2) { - if (!str1 || !str2) return 0; - str1 = str1.toLowerCase(); - str2 = str2.toLowerCase(); - - const matrix = Array(str2.length + 1).fill(null) - .map(() => Array(str1.length + 1).fill(null)); +const axios = require('axios'); +const stringSimilarity = require('string-similarity'); - for (let i = 0; i <= str1.length; i++) matrix[0][i] = i; - for (let j = 0; j <= str2.length; j++) matrix[j][0] = j; +// 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' +}); - 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 - ); - } +// 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 } - - 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; -} + synonyms + format + seasonYear + } +}`; // Function to normalize title for comparison function normalizeTitle(title) { @@ -177,13 +137,13 @@ async function getEpisodeList(movieId) { } } -// Function to calculate overall similarity between titles +// Function to calculate similarity between titles using string-similarity library 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); + if (!title1 || !title2) return 0; + return stringSimilarity.compareTwoStrings( + title1.toLowerCase(), + title2.toLowerCase() + ) * 100; // Convert to percentage } // Function to find best match between AniList and anicrush results @@ -217,11 +177,13 @@ function findBestMatch(anilistData, anicrushResults) { typePenalty = 15; // small penalty instead of skip } - const resultTitles = [result.name, result.name_english].filter(Boolean).map(normalizeTitle); + const resultTitles = [result.name, result.name_english].filter(Boolean); for (const aTitle of anilistTitles) { for (const rTitle of resultTitles) { - const score = calculateTitleSimilarity(aTitle, rTitle); + const similarity = calculateTitleSimilarity(aTitle, rTitle); + const score = Math.max(0, similarity - typePenalty); + if (score > highestScore) { highestScore = score; bestMatch = result; @@ -230,7 +192,7 @@ function findBestMatch(anilistData, anicrushResults) { } } - return highestScore >= 25 ? bestMatch : null; // accept if 25%+ similarity + return highestScore >= 60 ? bestMatch : null; // Increased threshold to 60% for better accuracy } // Alias for compatibility diff --git a/package-lock.json b/package-lock.json index dffa399..fd03068 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "cors": "^2.8.5", "crypto": "^1.0.1", "dotenv": "^16.3.1", - "express": "^4.18.2" + "express": "^4.18.2", + "string-similarity": "^4.0.4" }, "devDependencies": { "nodemon": "^3.0.2" @@ -1530,6 +1531,13 @@ "node": ">= 0.8" } }, + "node_modules/string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "ISC" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index c5565c8..4fec820 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "cors": "^2.8.5", "crypto": "^1.0.1", "dotenv": "^16.3.1", - "express": "^4.18.2" + "express": "^4.18.2", + "string-similarity": "^4.0.4" }, "devDependencies": { "nodemon": "^3.0.2"