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:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
115
embedHandler.js
115
embedHandler.js
@@ -1,107 +1,126 @@
|
|||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const crypto = require("crypto");
|
const cheerio = require("cheerio");
|
||||||
|
|
||||||
const MEGACLOUD_URL = 'https://megacloud.blog';
|
const MEGACLOUD_URL = "https://megacloud.blog";
|
||||||
const KEY_URL = "https://raw.githubusercontent.com/yogesh-hacker/MegacloudKeys/refs/heads/main/keys.json";
|
const KEY_URL = "https://raw.githubusercontent.com/yogesh-hacker/MegacloudKeys/refs/heads/main/keys.json";
|
||||||
const DECODE_URL = "https://script.google.com/macros/s/AKfycbx-yHTwupis_JD0lNzoOnxYcEYeXmJZrg7JeMxYnEZnLBy5V0--UxEvP-y9txHyy1TX9Q/exec";
|
const DECODE_URL = "https://script.google.com/macros/s/AKfycbx-yHTwupis_JD0lNzoOnxYcEYeXmJZrg7JeMxYnEZnLBy5V0--UxEvP-y9txHyy1TX9Q/exec";
|
||||||
const UA = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36";
|
const UA =
|
||||||
|
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36";
|
||||||
|
|
||||||
let cachedKey = null;
|
let cachedKey = null;
|
||||||
|
|
||||||
|
/** 🔹 Extract nonce from HTML */
|
||||||
function extractNonce(html) {
|
function extractNonce(html) {
|
||||||
const match1 = html.match(/\b[a-zA-Z0-9]{48}\b/);
|
const match48 = html.match(/\b[a-zA-Z0-9]{48}\b/);
|
||||||
if (match1) return match1[0];
|
if (match48) return match48[0];
|
||||||
|
|
||||||
const match2 = html.match(/\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b/);
|
const match3x16 = html.match(
|
||||||
if (match2) return match2.groupValues ? match2.groupValues[1] + match2.groupValues[2] + match2.groupValues[3] : match2[1] + match2[2] + match2[3];
|
/\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b/
|
||||||
|
);
|
||||||
|
if (match3x16) return match3x16.slice(1, 4).join("");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 🔹 Cache + fetch decryption key */
|
||||||
async function fetchKey() {
|
async function fetchKey() {
|
||||||
if (cachedKey) return cachedKey;
|
if (cachedKey) return cachedKey;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(KEY_URL, { headers: { "User-Agent": UA } });
|
const { data } = await axios.get(KEY_URL, { headers: { "User-Agent": UA } });
|
||||||
cachedKey = data?.mega;
|
cachedKey = data?.mega;
|
||||||
|
if (!cachedKey) throw new Error("Missing key in JSON");
|
||||||
return cachedKey;
|
return cachedKey;
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error("Failed to fetch key:", error.message);
|
console.error("Failed to fetch key:", err.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 🔹 Call remote Google Script to decrypt */
|
||||||
async function decryptWithGoogleScript(encryptedData, nonce, key) {
|
async function decryptWithGoogleScript(encryptedData, nonce, key) {
|
||||||
const fullUrl = `${DECODE_URL}?encrypted_data=${encodeURIComponent(encryptedData)}&nonce=${encodeURIComponent(nonce)}&secret=${encodeURIComponent(key)}`;
|
const fullUrl = `${DECODE_URL}?encrypted_data=${encodeURIComponent(
|
||||||
|
typeof encryptedData === "string" ? encryptedData : JSON.stringify(encryptedData)
|
||||||
|
)}&nonce=${encodeURIComponent(nonce)}&secret=${encodeURIComponent(key)}`;
|
||||||
|
|
||||||
const { data } = await axios.get(fullUrl);
|
const { data } = await axios.get(fullUrl);
|
||||||
const match = data.match(/"file":"(.*?)"/);
|
const match = data.match(/"file":"(.*?)"/);
|
||||||
if (!match) throw new Error("Video URL not found in decrypted response");
|
if (!match) throw new Error("Decrypted file not found");
|
||||||
|
return match[1].replace(/\\\//g, "/");
|
||||||
return match[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 🔹 Main extractor */
|
||||||
async function extract(embedUrl) {
|
async function extract(embedUrl) {
|
||||||
try {
|
try {
|
||||||
const headers = {
|
const headers = {
|
||||||
"Accept": "*/*",
|
Accept: "*/*",
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
"Referer": MEGACLOUD_URL,
|
// Use domain as referer for proper CORS
|
||||||
"User-Agent": UA
|
Referer: `${new URL(embedUrl).origin}/`,
|
||||||
|
"User-Agent": UA,
|
||||||
};
|
};
|
||||||
|
|
||||||
const id = embedUrl.split("/").pop().split("?")[0];
|
// Fetch embed HTML
|
||||||
|
|
||||||
const { data: html } = await axios.get(embedUrl, { headers });
|
const { data: html } = await axios.get(embedUrl, { headers });
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
// Extract file ID
|
||||||
|
const fileId = $("#megacloud-player").attr("data-id");
|
||||||
|
if (!fileId) throw new Error("data-id not found");
|
||||||
|
|
||||||
|
// Extract nonce
|
||||||
const nonce = extractNonce(html);
|
const nonce = extractNonce(html);
|
||||||
|
if (!nonce) throw new Error("Nonce not found");
|
||||||
if (!nonce) {
|
|
||||||
throw new Error("Could not extract nonce from embed page");
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiUrl = `${MEGACLOUD_URL}/embed-2/v3/e-1/getSources?id=${id}&_k=${nonce}`;
|
// Build API URL dynamically
|
||||||
|
const urlParts = new URL(embedUrl).pathname.split("/").filter(Boolean);
|
||||||
|
// Always use v3 endpoint as it returns correct JSON even when embed URL contains v2
|
||||||
|
const apiUrl = `${MEGACLOUD_URL}/embed-2/v3/e-1/getSources?id=${fileId}&_k=${nonce}`;
|
||||||
|
|
||||||
|
// Fetch JSON
|
||||||
const { data: response } = await axios.get(apiUrl, { headers });
|
const { data: response } = await axios.get(apiUrl, { headers });
|
||||||
|
if (!response || !response.sources)
|
||||||
if (!response || !response.sources) {
|
throw new Error("No sources in API response");
|
||||||
throw new Error("No sources found in API response");
|
|
||||||
}
|
|
||||||
|
|
||||||
const encoded = response.sources;
|
let sources = response.sources;
|
||||||
|
const tracks = response.tracks || [];
|
||||||
let m3u8Url;
|
let m3u8Url;
|
||||||
|
|
||||||
if (encoded.includes(".m3u8")) {
|
// 🔸 If sources is array and contains HLS directly
|
||||||
m3u8Url = encoded;
|
if (Array.isArray(sources) && sources.length && sources[0].file) {
|
||||||
} else {
|
m3u8Url = sources[0].file;
|
||||||
|
}
|
||||||
|
// 🔸 Encrypted string or empty array
|
||||||
|
else {
|
||||||
const key = await fetchKey();
|
const key = await fetchKey();
|
||||||
if (!key) {
|
if (!key) throw new Error("No key available");
|
||||||
throw new Error("Could not fetch decryption key");
|
|
||||||
}
|
|
||||||
|
|
||||||
m3u8Url = await decryptWithGoogleScript(encoded, nonce, key);
|
m3u8Url = await decryptWithGoogleScript(sources, nonce, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sources: [{ file: m3u8Url, type: "hls" }],
|
sources: [{ file: m3u8Url, type: "hls" }],
|
||||||
tracks: response.tracks || [],
|
tracks: tracks.filter((t) =>
|
||||||
|
["captions", "subtitles"].includes((t.kind || "").toLowerCase())
|
||||||
|
),
|
||||||
t: response.t || 0,
|
t: response.t || 0,
|
||||||
server: response.server || 0,
|
server: response.server || 0,
|
||||||
intro: response.intro || null,
|
intro: response.intro || null,
|
||||||
outro: response.outro || null
|
outro: response.outro || null,
|
||||||
};
|
};
|
||||||
|
} catch (err) {
|
||||||
} catch (error) {
|
console.error("❌ MegaCloud extraction failed:", err.message);
|
||||||
console.error("MegaCloud extraction failed:", error.message);
|
|
||||||
return {
|
return {
|
||||||
sources: [],
|
sources: [],
|
||||||
tracks: [],
|
tracks: [],
|
||||||
t: 0,
|
t: 0,
|
||||||
server: 0
|
server: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleEmbed(embedUrl, referrer) {
|
/** 🔹 Wrapper */
|
||||||
|
async function handleEmbed(embedUrl) {
|
||||||
return await extract(embedUrl);
|
return await extract(embedUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { extract, handleEmbed };
|
module.exports = { extract, handleEmbed };
|
||||||
|
|||||||
4
index.js
4
index.js
@@ -284,13 +284,13 @@ app.get('/api/anime/:anilistId/:episodeNum', async (req, res) => {
|
|||||||
|
|
||||||
// Find the specific episode data
|
// Find the specific episode data
|
||||||
const episodeNumber = parseInt(episodeNum) || 1;
|
const episodeNumber = parseInt(episodeNum) || 1;
|
||||||
const episodeData = mappedData.episodes.find(ep => ep.number === episodeNumber) || {};
|
const episodeData = (mappedData.episodes || []).find(ep => ep.number === episodeNumber) || {};
|
||||||
|
|
||||||
// Add metadata from the mapped data
|
// Add metadata from the mapped data
|
||||||
const response = {
|
const response = {
|
||||||
...hlsData,
|
...hlsData,
|
||||||
metadata: {
|
metadata: {
|
||||||
title: mappedData.titles?.english || mappedData.titles?.romaji,
|
title: mappedData.title?.english || mappedData.title?.romaji,
|
||||||
anilistId: parseInt(anilistId),
|
anilistId: parseInt(anilistId),
|
||||||
movieId: movieId,
|
movieId: movieId,
|
||||||
episode: episodeNumber,
|
episode: episodeNumber,
|
||||||
|
|||||||
685
mapper.js
685
mapper.js
@@ -12,28 +12,20 @@ const getCommonHeaders = () => ({
|
|||||||
'sec-fetch-dest': 'empty'
|
'sec-fetch-dest': 'empty'
|
||||||
});
|
});
|
||||||
|
|
||||||
// GraphQL query for AniList with synonyms
|
// Minimal GraphQL query for AniList (title, format, year only)
|
||||||
const ANILIST_QUERY = `
|
const ANILIST_QUERY = `
|
||||||
query ($id: Int) {
|
query ($id: Int) {
|
||||||
Media(id: $id, type: ANIME) {
|
Media(id: $id, type: ANIME) {
|
||||||
id
|
id
|
||||||
title {
|
title {
|
||||||
romaji
|
romaji
|
||||||
english
|
english
|
||||||
native
|
native
|
||||||
}
|
|
||||||
synonyms
|
|
||||||
episodes
|
|
||||||
format
|
|
||||||
status
|
|
||||||
countryOfOrigin
|
|
||||||
seasonYear
|
|
||||||
description
|
|
||||||
genres
|
|
||||||
tags {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
synonyms
|
||||||
|
format
|
||||||
|
seasonYear
|
||||||
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
// Function to calculate string similarity using Levenshtein distance
|
// Function to calculate string similarity using Levenshtein distance
|
||||||
@@ -43,376 +35,285 @@ function calculateLevenshteinSimilarity(str1, str2) {
|
|||||||
str2 = str2.toLowerCase();
|
str2 = str2.toLowerCase();
|
||||||
|
|
||||||
const matrix = Array(str2.length + 1).fill(null)
|
const matrix = Array(str2.length + 1).fill(null)
|
||||||
.map(() => Array(str1.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 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 = 0; j <= str2.length; j++) matrix[j][0] = j;
|
||||||
|
|
||||||
for (let j = 1; j <= str2.length; j++) {
|
for (let j = 1; j <= str2.length; j++) {
|
||||||
for (let i = 1; i <= str1.length; i++) {
|
for (let i = 1; i <= str1.length; i++) {
|
||||||
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
||||||
matrix[j][i] = Math.min(
|
matrix[j][i] = Math.min(
|
||||||
matrix[j][i - 1] + 1,
|
matrix[j][i - 1] + 1,
|
||||||
matrix[j - 1][i] + 1,
|
matrix[j - 1][i] + 1,
|
||||||
matrix[j - 1][i - 1] + indicator
|
matrix[j - 1][i - 1] + indicator
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxLength = Math.max(str1.length, str2.length);
|
const maxLength = Math.max(str1.length, str2.length);
|
||||||
if (maxLength === 0) return 100;
|
if (maxLength === 0) return 100;
|
||||||
return ((maxLength - matrix[str2.length][str1.length]) / maxLength) * 100;
|
return ((maxLength - matrix[str2.length][str1.length]) / maxLength) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to calculate word-based similarity
|
// Function to calculate word-based similarity
|
||||||
function calculateWordSimilarity(str1, str2) {
|
function calculateWordSimilarity(str1, str2) {
|
||||||
if (!str1 || !str2) return 0;
|
if (!str1 || !str2) return 0;
|
||||||
|
|
||||||
const words1 = str1.toLowerCase().split(/\s+/).filter(Boolean);
|
const words1 = str1.toLowerCase().split(/\s+/).filter(Boolean);
|
||||||
const words2 = str2.toLowerCase().split(/\s+/).filter(Boolean);
|
const words2 = str2.toLowerCase().split(/\s+/).filter(Boolean);
|
||||||
|
|
||||||
const commonWords = words1.filter(word => words2.includes(word));
|
const commonWords = words1.filter(word => words2.includes(word));
|
||||||
const totalUniqueWords = new Set([...words1, ...words2]).size;
|
const totalUniqueWords = new Set([...words1, ...words2]).size;
|
||||||
|
|
||||||
return (commonWords.length / totalUniqueWords) * 100;
|
return (commonWords.length / totalUniqueWords) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to normalize title for comparison
|
// Function to normalize title for comparison
|
||||||
function normalizeTitle(title) {
|
function normalizeTitle(title) {
|
||||||
if (!title) return '';
|
if (!title) return '';
|
||||||
return title.toLowerCase()
|
return title.toLowerCase()
|
||||||
.replace(/[^a-z0-9\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\u4e00-\u9faf\uff00-\uff9f]/g, ' ')
|
.replace(/[^a-z0-9\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\u4e00-\u9faf\uff00-\uff9f]/g, ' ')
|
||||||
.replace(/\s+/g, ' ')
|
.replace(/\s+/g, ' ')
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get anime details from AniList
|
// Function to get anime details from AniList
|
||||||
async function getAniListDetails(anilistId) {
|
async function getAniListDetails(anilistId) {
|
||||||
try {
|
try {
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
url: 'https://graphql.anilist.co',
|
url: 'https://graphql.anilist.co',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
query: ANILIST_QUERY,
|
query: ANILIST_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
id: parseInt(anilistId)
|
id: parseInt(anilistId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.data?.data?.Media) {
|
if (!response.data?.data?.Media) {
|
||||||
throw new Error('Anime not found on AniList');
|
throw new Error('Anime not found on AniList');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data.data.Media;
|
return response.data.data.Media;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching from AniList:', error.message);
|
console.error('Error fetching from AniList:', error.message);
|
||||||
throw new Error('Failed to fetch anime details from AniList');
|
throw new Error('Failed to fetch anime details from AniList');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to search anime on anicrush
|
// Function to search anime on anicrush
|
||||||
async function searchAnicrush(title) {
|
async function searchAnicrush(title) {
|
||||||
if (!title) {
|
if (!title) {
|
||||||
throw new Error('Search title is required');
|
throw new Error('Search title is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const headers = getCommonHeaders();
|
const headers = getCommonHeaders();
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: 'https://api.anicrush.to/shared/v2/movie/list',
|
url: 'https://api.anicrush.to/shared/v2/movie/list',
|
||||||
params: {
|
params: {
|
||||||
keyword: title,
|
keyword: title,
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 24
|
limit: 24
|
||||||
},
|
},
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data?.status === false) {
|
if (response.data?.status === false) {
|
||||||
throw new Error(response.data.message || 'Search failed');
|
throw new Error(response.data.message || 'Search failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
console.error('Search API error:', error.response.data);
|
console.error('Search API error:', error.response.data);
|
||||||
throw new Error(error.response.data.message || 'Search request failed');
|
throw new Error(error.response.data.message || 'Search request failed');
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
console.error('No response received:', error.request);
|
console.error('No response received:', error.request);
|
||||||
throw new Error('No response from search API');
|
throw new Error('No response from search API');
|
||||||
} else {
|
} else {
|
||||||
console.error('Search error:', error.message);
|
console.error('Search error:', error.message);
|
||||||
throw new Error('Failed to search anime');
|
throw new Error('Failed to search anime');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get episode list from anicrush
|
// Function to get episode list from anicrush
|
||||||
async function getEpisodeList(movieId) {
|
async function getEpisodeList(movieId) {
|
||||||
if (!movieId) {
|
if (!movieId) {
|
||||||
throw new Error('Movie ID is required');
|
throw new Error('Movie ID is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const headers = getCommonHeaders();
|
const headers = getCommonHeaders();
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: 'https://api.anicrush.to/shared/v2/episode/list',
|
url: 'https://api.anicrush.to/shared/v2/episode/list',
|
||||||
params: {
|
params: {
|
||||||
_movieId: movieId
|
_movieId: movieId
|
||||||
},
|
},
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data?.status === false) {
|
if (response.data?.status === false) {
|
||||||
throw new Error(response.data.message || 'Failed to fetch episode list');
|
throw new Error(response.data.message || 'Failed to fetch episode list');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
console.error('Episode list API error:', error.response.data);
|
console.error('Episode list API error:', error.response.data);
|
||||||
throw new Error(error.response.data.message || 'Episode list request failed');
|
throw new Error(error.response.data.message || 'Episode list request failed');
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
console.error('No response received:', error.request);
|
console.error('No response received:', error.request);
|
||||||
throw new Error('No response from episode list API');
|
throw new Error('No response from episode list API');
|
||||||
} else {
|
} else {
|
||||||
console.error('Episode list error:', error.message);
|
console.error('Episode list error:', error.message);
|
||||||
throw new Error('Failed to fetch episode list');
|
throw new Error('Failed to fetch episode list');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to calculate overall similarity between titles
|
// Function to calculate overall similarity between titles
|
||||||
function calculateTitleSimilarity(title1, title2) {
|
function calculateTitleSimilarity(title1, title2) {
|
||||||
const levenshteinSim = calculateLevenshteinSimilarity(title1, title2);
|
const levenshteinSim = calculateLevenshteinSimilarity(title1, title2);
|
||||||
const wordSim = calculateWordSimilarity(title1, title2);
|
const wordSim = calculateWordSimilarity(title1, title2);
|
||||||
|
|
||||||
// Weight the similarities (favoring word-based matching for titles)
|
// Weight the similarities (favoring word-based matching for titles)
|
||||||
return (levenshteinSim * 0.4) + (wordSim * 0.6);
|
return (levenshteinSim * 0.4) + (wordSim * 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to find best match between AniList and anicrush results
|
// Function to find best match between AniList and anicrush results
|
||||||
function findBestMatch(anilistData, anicrushResults) {
|
function findBestMatch(anilistData, anicrushResults) {
|
||||||
if (!anicrushResults?.result?.movies || !anicrushResults.result.movies.length) {
|
if (!anicrushResults?.result?.movies?.length) return null;
|
||||||
return null;
|
|
||||||
}
|
const anilistTitles = [
|
||||||
|
anilistData.title.romaji,
|
||||||
// Prepare all possible titles from AniList
|
anilistData.title.english,
|
||||||
const titles = [
|
anilistData.title.native,
|
||||||
anilistData.title.romaji,
|
...(anilistData.synonyms || [])
|
||||||
anilistData.title.english,
|
].filter(Boolean).map(normalizeTitle);
|
||||||
anilistData.title.native,
|
|
||||||
...(anilistData.synonyms || [])
|
let bestMatch = null;
|
||||||
].filter(Boolean);
|
let highestScore = 0;
|
||||||
|
|
||||||
let bestMatch = null;
|
const formatTypeMap = {
|
||||||
let highestSimilarity = 0;
|
TV: 'TV',
|
||||||
|
TV_SHORT: 'TV',
|
||||||
// Map AniList format to Anicrush type
|
MOVIE: 'MOVIE',
|
||||||
const formatTypeMap = {
|
SPECIAL: 'SPECIAL',
|
||||||
'TV': 'TV',
|
OVA: 'OVA',
|
||||||
'TV_SHORT': 'TV',
|
ONA: 'ONA',
|
||||||
'MOVIE': 'MOVIE',
|
MUSIC: 'MUSIC'
|
||||||
'SPECIAL': 'SPECIAL',
|
};
|
||||||
'OVA': 'OVA',
|
const expectedType = formatTypeMap[anilistData.format] || null;
|
||||||
'ONA': 'ONA',
|
|
||||||
'MUSIC': 'MUSIC'
|
for (const result of anicrushResults.result.movies) {
|
||||||
};
|
let typePenalty = 0;
|
||||||
|
if (expectedType && result.type && expectedType !== result.type) {
|
||||||
const expectedType = formatTypeMap[anilistData.format] || null;
|
typePenalty = 15; // small penalty instead of skip
|
||||||
|
}
|
||||||
// Check each result from anicrush
|
|
||||||
for (const result of anicrushResults.result.movies) {
|
const resultTitles = [result.name, result.name_english].filter(Boolean).map(normalizeTitle);
|
||||||
const resultTitles = [
|
|
||||||
result.name,
|
for (const aTitle of anilistTitles) {
|
||||||
result.name_english
|
for (const rTitle of resultTitles) {
|
||||||
].filter(Boolean);
|
const score = calculateTitleSimilarity(aTitle, rTitle);
|
||||||
|
if (score > highestScore) {
|
||||||
for (const resultTitle of resultTitles) {
|
highestScore = score;
|
||||||
// Try each possible title combination
|
bestMatch = result;
|
||||||
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);
|
return highestScore >= 25 ? bestMatch : null; // accept if 25%+ similarity
|
||||||
|
}
|
||||||
// Add bonus for year match
|
|
||||||
let currentSimilarity = similarity;
|
// Alias for compatibility
|
||||||
if (anilistData.seasonYear && result.aired_from) {
|
const findBestMatchFuzzy = findBestMatch;
|
||||||
const yearMatch = result.aired_from.includes(anilistData.seasonYear.toString());
|
|
||||||
if (yearMatch) currentSimilarity += 10; // Reduced from 15 to 10
|
// Function to parse episode list response
|
||||||
}
|
function parseEpisodeList(episodeList) {
|
||||||
|
if (!episodeList?.result) return [];
|
||||||
// Add bonus for type match
|
|
||||||
if (expectedType && result.type) {
|
const episodes = [];
|
||||||
if (expectedType === result.type) {
|
for (const [key, value] of Object.entries(episodeList.result)) {
|
||||||
currentSimilarity += 25; // Increased from 10 to 25
|
if (Array.isArray(value)) {
|
||||||
} else {
|
value.forEach(ep => {
|
||||||
currentSimilarity -= 30; // Add significant penalty for type mismatch
|
episodes.push({
|
||||||
}
|
number: ep.number,
|
||||||
}
|
name: ep.name,
|
||||||
|
name_english: ep.name_english,
|
||||||
// Add penalty for episode count mismatch (if available)
|
is_filler: ep.is_filler
|
||||||
if (anilistData.episodes && result.episodes_count) {
|
});
|
||||||
const episodeDiff = Math.abs(anilistData.episodes - result.episodes_count);
|
});
|
||||||
if (episodeDiff > 2) { // Allow small differences
|
}
|
||||||
currentSimilarity -= (episodeDiff * 2);
|
}
|
||||||
}
|
return episodes.sort((a, b) => a.number - b.number);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add penalty for length difference
|
// Function to fetch ani.zip mappings
|
||||||
const lengthDiff = Math.abs(cleanTitle1.length - cleanTitle2.length);
|
async function getAniZipMappings(anilistId) {
|
||||||
if (lengthDiff > 10) {
|
try {
|
||||||
currentSimilarity -= (lengthDiff * 0.5);
|
const response = await axios({
|
||||||
}
|
method: 'GET',
|
||||||
|
url: `https://api.ani.zip/mappings?anilist_id=${anilistId}`,
|
||||||
// Add penalty for completely different first words
|
headers: {
|
||||||
const firstWord1 = cleanTitle1.split(' ')[0];
|
'Accept': 'application/json'
|
||||||
const firstWord2 = cleanTitle2.split(' ')[0];
|
}
|
||||||
if (firstWord1 && firstWord2 && firstWord1 !== firstWord2) {
|
});
|
||||||
currentSimilarity -= 20;
|
|
||||||
}
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
if (currentSimilarity > highestSimilarity) {
|
console.error('Error fetching ani.zip mappings:', error.message);
|
||||||
highestSimilarity = currentSimilarity;
|
return null;
|
||||||
bestMatch = result;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
// Main mapper function
|
||||||
}
|
async function mapAniListToAnicrush(anilistId) {
|
||||||
|
try {
|
||||||
// Only return a match if similarity is above threshold
|
const anilistData = await getAniListDetails(anilistId);
|
||||||
console.log(`Best match found with similarity: ${highestSimilarity}%`);
|
|
||||||
return highestSimilarity >= 70 ? bestMatch : null; // Increased threshold from 60 to 70
|
const titlesToTry = [
|
||||||
}
|
anilistData.title.romaji,
|
||||||
|
anilistData.title.english,
|
||||||
// Function to parse episode list response
|
anilistData.title.native
|
||||||
function parseEpisodeList(episodeList) {
|
].filter(Boolean);
|
||||||
if (!episodeList?.result) return [];
|
|
||||||
|
let bestMatch = null;
|
||||||
const episodes = [];
|
for (const title of titlesToTry) {
|
||||||
for (const [key, value] of Object.entries(episodeList.result)) {
|
const searchResults = await searchAnicrush(title);
|
||||||
if (Array.isArray(value)) {
|
bestMatch = findBestMatchFuzzy(anilistData, searchResults);
|
||||||
value.forEach(ep => {
|
if (bestMatch) break;
|
||||||
episodes.push({
|
}
|
||||||
number: ep.number,
|
|
||||||
name: ep.name,
|
if (!bestMatch) throw new Error('No matching anime found on anicrush');
|
||||||
name_english: ep.name_english,
|
|
||||||
is_filler: ep.is_filler
|
return {
|
||||||
});
|
anilist_id: anilistId,
|
||||||
});
|
anicrush_id: bestMatch.id,
|
||||||
}
|
title: {
|
||||||
}
|
romaji: anilistData.title.romaji,
|
||||||
return episodes.sort((a, b) => a.number - b.number);
|
english: anilistData.title.english,
|
||||||
}
|
native: anilistData.title.native,
|
||||||
|
anicrush: bestMatch.name,
|
||||||
// Function to fetch ani.zip mappings
|
anicrush_english: bestMatch.name_english
|
||||||
async function getAniZipMappings(anilistId) {
|
},
|
||||||
try {
|
type: bestMatch.type,
|
||||||
const response = await axios({
|
year: anilistData.seasonYear
|
||||||
method: 'GET',
|
};
|
||||||
url: `https://api.ani.zip/mappings?anilist_id=${anilistId}`,
|
} catch (error) {
|
||||||
headers: {
|
console.error('Mapper error:', error.message);
|
||||||
'Accept': 'application/json'
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return response.data;
|
module.exports = {
|
||||||
} catch (error) {
|
mapAniListToAnicrush,
|
||||||
console.error('Error fetching ani.zip mappings:', error.message);
|
getCommonHeaders
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|||||||
303
package-lock.json
generated
303
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
|
"cheerio": "^1.1.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
@@ -115,6 +116,12 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
@@ -177,6 +184,48 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cheerio": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cheerio-select": "^2.1.0",
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.2.2",
|
||||||
|
"encoding-sniffer": "^0.2.1",
|
||||||
|
"htmlparser2": "^10.0.0",
|
||||||
|
"parse5": "^7.3.0",
|
||||||
|
"parse5-htmlparser2-tree-adapter": "^7.1.0",
|
||||||
|
"parse5-parser-stream": "^7.1.2",
|
||||||
|
"undici": "^7.12.0",
|
||||||
|
"whatwg-mimetype": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.18.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cheerio-select": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
@@ -277,6 +326,34 @@
|
|||||||
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
|
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
@@ -314,6 +391,61 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.7",
|
"version": "16.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||||
@@ -355,6 +487,43 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/encoding-sniffer": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"whatwg-encoding": "^3.1.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/encoding-sniffer/node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
@@ -680,6 +849,37 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmlparser2": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.2.1",
|
||||||
|
"entities": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/htmlparser2/node_modules/entities": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-errors": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
@@ -937,6 +1137,18 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -970,6 +1182,55 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"entities": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5-parser-stream": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5/node_modules/entities": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -1334,6 +1595,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/undici": {
|
||||||
|
"version": "7.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz",
|
||||||
|
"integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
@@ -1360,6 +1630,39 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-encoding": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-encoding/node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-mimetype": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
|
"cheerio": "^1.1.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user