mirror of
https://github.com/JustAnimeCore/HiAnime-Api.git
synced 2026-04-17 22:01:44 +00:00
feature, not a bug
This commit is contained in:
34
src/helper/cache.helper.js
Normal file
34
src/helper/cache.helper.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import axios from "axios";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const CACHE_SERVER_URL = process.env.CACHE_URL || null;
|
||||
|
||||
export const getCachedData = async (key) => {
|
||||
try {
|
||||
if (!CACHE_SERVER_URL) {
|
||||
console.log(CACHE_SERVER_URL);
|
||||
return;
|
||||
}
|
||||
const response = await axios.get(`${CACHE_SERVER_URL}/${key}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const setCachedData = async (key, value) => {
|
||||
try {
|
||||
if (!CACHE_SERVER_URL) {
|
||||
return;
|
||||
}
|
||||
await axios.post(CACHE_SERVER_URL, { key, value });
|
||||
} catch (error) {
|
||||
console.error("Error setting cache data:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
24
src/helper/countPages.helper.js
Normal file
24
src/helper/countPages.helper.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import axios from "axios";
|
||||
import * as cheerio from "cheerio";
|
||||
import { DEFAULT_HEADERS } from "../configs/header.config.js";
|
||||
|
||||
const axiosInstance = axios.create({ headers: DEFAULT_HEADERS });
|
||||
|
||||
async function countPages(url) {
|
||||
try {
|
||||
const { data } = await axiosInstance.get(url);
|
||||
const $ = cheerio.load(data);
|
||||
const lastPageHref = $(
|
||||
".tab-content .pagination .page-item:last-child a"
|
||||
).attr("href");
|
||||
const lastPageNumber = lastPageHref
|
||||
? parseInt(lastPageHref.split("=").pop())
|
||||
: 1;
|
||||
return lastPageNumber;
|
||||
} catch (error) {
|
||||
console.error("Error counting pages:", error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export default countPages;
|
||||
92
src/helper/extractPages.helper.js
Normal file
92
src/helper/extractPages.helper.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import axios from "axios";
|
||||
import * as cheerio from "cheerio";
|
||||
import { v1_base_url } from "../utils/base_v1.js";
|
||||
import { DEFAULT_HEADERS } from "../configs/header.config.js";
|
||||
|
||||
const axiosInstance = axios.create({ headers: DEFAULT_HEADERS });
|
||||
|
||||
async function extractPage(page, params) {
|
||||
try {
|
||||
const resp = await axiosInstance.get(`https://${v1_base_url}/${params}?page=${page}`);
|
||||
const $ = cheerio.load(resp.data);
|
||||
const totalPages =
|
||||
Number(
|
||||
$('.pre-pagination nav .pagination > .page-item a[title="Last"]')
|
||||
?.attr("href")
|
||||
?.split("=")
|
||||
.pop() ??
|
||||
$('.pre-pagination nav .pagination > .page-item a[title="Next"]')
|
||||
?.attr("href")
|
||||
?.split("=")
|
||||
.pop() ??
|
||||
$(".pre-pagination nav .pagination > .page-item.active a")
|
||||
?.text()
|
||||
?.trim()
|
||||
) || 1;
|
||||
|
||||
const contentSelector = params.includes("az-list")
|
||||
? ".tab-content"
|
||||
: "#main-content";
|
||||
const data = await Promise.all(
|
||||
$(`${contentSelector} .film_list-wrap .flw-item`).map(
|
||||
async (index, element) => {
|
||||
const $fdiItems = $(".film-detail .fd-infor .fdi-item", element);
|
||||
const showType = $fdiItems
|
||||
.filter((_, item) => {
|
||||
const text = $(item).text().trim().toLowerCase();
|
||||
return ["tv", "ona", "movie", "ova", "special", "music"].some((type) =>
|
||||
text.includes(type)
|
||||
);
|
||||
})
|
||||
.first();
|
||||
const poster = $(".film-poster>img", element).attr("data-src");
|
||||
const title = $(".film-detail .film-name", element).text();
|
||||
const japanese_title = $(".film-detail>.film-name>a", element).attr(
|
||||
"data-jname"
|
||||
);
|
||||
const description = $(".film-detail .description", element)
|
||||
.text()
|
||||
.trim();
|
||||
const data_id = $(".film-poster>a", element).attr("data-id");
|
||||
const id = $(".film-poster>a", element).attr("href").split("/").pop();
|
||||
const tvInfo = {
|
||||
showType: showType ? showType.text().trim() : "Unknown",
|
||||
duration: $(".film-detail .fd-infor .fdi-duration", element)
|
||||
.text()
|
||||
.trim(),
|
||||
};
|
||||
let adultContent = false;
|
||||
const tickRateText = $(".film-poster>.tick-rate", element)
|
||||
.text()
|
||||
.trim();
|
||||
if (tickRateText.includes("18+")) {
|
||||
adultContent = true;
|
||||
}
|
||||
|
||||
["sub", "dub", "eps"].forEach((property) => {
|
||||
const value = $(`.tick .tick-${property}`, element).text().trim();
|
||||
if (value) {
|
||||
tvInfo[property] = value;
|
||||
}
|
||||
});
|
||||
return {
|
||||
id,
|
||||
data_id,
|
||||
poster,
|
||||
title,
|
||||
japanese_title,
|
||||
description,
|
||||
tvInfo,
|
||||
adultContent,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
return [data, parseInt(totalPages, 10)];
|
||||
} catch (error) {
|
||||
console.error(`Error extracting data from page ${page}:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export default extractPage;
|
||||
8
src/helper/fetchScript.helper.js
Normal file
8
src/helper/fetchScript.helper.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import axios from "axios";
|
||||
|
||||
async function fetchScript(url) {
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export default fetchScript;
|
||||
76
src/helper/foreignInput.helper.js
Normal file
76
src/helper/foreignInput.helper.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import axios from "axios";
|
||||
// import { getCachedData, setCachedData } from "./cache.helper";
|
||||
|
||||
async function getEnglishTitleFromAniList(userInput) {
|
||||
// const cacheKey = `translation:${userInput}`;
|
||||
|
||||
try {
|
||||
// Check cache
|
||||
// const cachedValue = await getCachedData(cacheKey);
|
||||
// if (cachedValue) {
|
||||
// console.log(`Cache Hit ${userInput} -> ${cachedValue}`)
|
||||
// }
|
||||
|
||||
const query = `
|
||||
query ($search: String) {
|
||||
Media (search: $search, type: ANIME) {
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await axios.post('https://graphql.anilist.co', {
|
||||
query,
|
||||
variables: { search: userInput }
|
||||
}, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
timeout: 3000 // 3 seconds
|
||||
});
|
||||
|
||||
const titles = response.data?.data?.Media?.title;
|
||||
|
||||
if (!titles) {
|
||||
console.log(`AniList no match found for: ${userInput}`);
|
||||
return userInput;
|
||||
}
|
||||
|
||||
const result = titles.english || titles.romaji || userInput;
|
||||
|
||||
// await setCachedData(cacheKey, result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("AniList API Error:", error.response?.data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function convertForeignLanguage(userInput) {
|
||||
try {
|
||||
if (!userInput) return '';
|
||||
|
||||
// If it's only Latin characters, return as-is
|
||||
if (/^[a-zA-Z\s]+$/.test(userInput)) {
|
||||
return userInput;
|
||||
}
|
||||
|
||||
// Detect if it is Japanese, Chinese or Korean
|
||||
const isForeign = /[\u3040-\u30ff\u3000-\u303f\u4e00-\u9faf\uac00-\ud7af]/.test(userInput);
|
||||
|
||||
if (isForeign) {
|
||||
const translated = await getEnglishTitleFromAniList(userInput);
|
||||
|
||||
return translated;
|
||||
}
|
||||
|
||||
return userInput;
|
||||
} catch (error) {
|
||||
console.error(`Error converting foreign input ${userInput}:`, error.message);
|
||||
return userInput;
|
||||
}
|
||||
}
|
||||
|
||||
export default convertForeignLanguage;
|
||||
9
src/helper/formatTitle.helper.js
Normal file
9
src/helper/formatTitle.helper.js
Normal file
@@ -0,0 +1,9 @@
|
||||
function formatTitle(title, data_id) {
|
||||
let formattedTitle = title.replace(/[^\w\s]/g, "");
|
||||
formattedTitle = formattedTitle.toLowerCase();
|
||||
formattedTitle = formattedTitle.replace(/\s+/g, "-");
|
||||
formattedTitle = `${formattedTitle}-${data_id}`;
|
||||
return formattedTitle;
|
||||
}
|
||||
|
||||
export default formatTitle;
|
||||
34
src/helper/getKey.helper.js
Normal file
34
src/helper/getKey.helper.js
Normal file
@@ -0,0 +1,34 @@
|
||||
class ErrorLoadingException extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "ErrorLoadingException";
|
||||
}
|
||||
}
|
||||
|
||||
function matchingKey(value, script) {
|
||||
const regex = new RegExp(`,${value}=((?:0x)?([0-9a-fA-F]+))`);
|
||||
const match = script.match(regex);
|
||||
if (match) {
|
||||
return match[2];
|
||||
} else {
|
||||
throw new ErrorLoadingException("Failed to match the key");
|
||||
}
|
||||
}
|
||||
|
||||
function getKeys(script) {
|
||||
const regex =
|
||||
/case\s*0x[0-9a-f]+:(?![^;]*=partKey)\s*\w+\s*=\s*(\w+)\s*,\s*\w+\s*=\s*(\w+);/g;
|
||||
const matches = script.matchAll(regex);
|
||||
|
||||
return Array.from(matches, (match) => {
|
||||
const matchKey1 = matchingKey(match[1], script);
|
||||
const matchKey2 = matchingKey(match[2], script);
|
||||
try {
|
||||
return [parseInt(matchKey1, 16), parseInt(matchKey2, 16)];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}).filter((pair) => pair.length > 0);
|
||||
}
|
||||
|
||||
export default getKeys;
|
||||
71
src/helper/token.helper.js
Normal file
71
src/helper/token.helper.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import axios from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { v1_base_url } from '../utils/base_v1.js';
|
||||
|
||||
export default async function extractToken(url) {
|
||||
try {
|
||||
const { data: html } = await axios.get(url, {
|
||||
headers: {
|
||||
Referer: `https://${v1_base_url}/`
|
||||
}
|
||||
});
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
const results = {};
|
||||
|
||||
// 1. Meta tag
|
||||
const meta = $('meta[name="_gg_fb"]').attr('content');
|
||||
if (meta) results.meta = meta;
|
||||
|
||||
// 2. Data attribute
|
||||
const dpi = $('[data-dpi]').attr('data-dpi');
|
||||
if (dpi) results.dataDpi = dpi;
|
||||
|
||||
// 3. Nonce from empty script
|
||||
const nonceScript = $('script[nonce]').filter((i, el) => {
|
||||
return $(el).text().includes('empty nonce script');
|
||||
}).attr('nonce');
|
||||
if (nonceScript) results.nonce = nonceScript;
|
||||
|
||||
// 4. JS string assignment: window.<key> = "value";
|
||||
const stringAssignRegex = /window\.(\w+)\s*=\s*["']([\w-]+)["']/g;
|
||||
const stringMatches = [...html.matchAll(stringAssignRegex)];
|
||||
for (const [_, key, value] of stringMatches) {
|
||||
results[`window.${key}`] = value;
|
||||
}
|
||||
|
||||
// 5. JS object assignment: window.<key> = { ... };
|
||||
const objectAssignRegex = /window\.(\w+)\s*=\s*(\{[\s\S]*?\});/g;
|
||||
const matches = [...html.matchAll(objectAssignRegex)];
|
||||
for (const [_, varName, rawObj] of matches) {
|
||||
try {
|
||||
const parsedObj = eval('(' + rawObj + ')');
|
||||
if (parsedObj && typeof parsedObj === 'object') {
|
||||
const stringValues = Object.values(parsedObj).filter(val => typeof val === 'string');
|
||||
const concatenated = stringValues.join('');
|
||||
if (concatenated.length >= 20) {
|
||||
results[`window.${varName}`] = concatenated;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip invalid object
|
||||
}
|
||||
}
|
||||
|
||||
// 6. HTML comment: <!-- _is_th:... -->
|
||||
$('*').contents().each(function () {
|
||||
if (this.type === 'comment') {
|
||||
const match = this.data.trim().match(/^_is_th:([\w-]+)$/);
|
||||
if (match) {
|
||||
results.commentToken = match[1].trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const token = Object.values(results)[0];
|
||||
return token || null;
|
||||
} catch (err) {
|
||||
console.error('Error:', err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user