feature, not a bug

This commit is contained in:
Tejas Panchal
2026-03-01 12:39:28 +05:30
commit a6184c63d4
91 changed files with 6797 additions and 0 deletions

View 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;
}
};

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;
}
}