mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
feat: v2.2.0 — Modularize codebase, unify mirror fallback, harden security
Major Changes: - Add shared parseListItem.helper.js for unified list parsing (DRY) - Migrate all 22 extractors to use fetchWithMirror() for consistent mirror fallback - Remove unused cors dependency (custom CORS middleware handles it) - Fix middleware ordering: timeout now registered before routes - Fix CORS to allow POST for /api/mirrors/reset endpoint - Remove redundant cache check in homeInfo extractor New Features: - Request ID tracking (X-Request-Id header) for debugging - Graceful shutdown handling (SIGTERM/SIGINT) with 10s timeout - CSP and HSTS security headers - Rate limiter cleanup interval (every 5 min) to prevent memory leak - Request body size limits (10kb) via express.json() Security: - Content-Security-Policy header for XSS protection - Strict-Transport-Security for HTTPS enforcement - X-Permitted-Cross-Domain-Policies: none - Body size limits to prevent large payload attacks Code Quality: - All files follow strict documentation style (box headers, section separators, feature markers, JSDoc params/returns, inline notes, module footers) - Eliminated ~200 lines of duplicated code across 8+ extractors - Updated CHANGELOG.md with comprehensive change history
This commit is contained in:
@@ -17,9 +17,9 @@
|
||||
*/
|
||||
|
||||
import * as cheerio from "cheerio";
|
||||
import axios from "axios";
|
||||
import { headers } from "../configs/header.config.js";
|
||||
import { BASE_URL } from "../configs/dataUrl.js";
|
||||
import { fetchWithMirror } from "../helper/mirror.helper.js";
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// STREAM INFO EXTRACTOR
|
||||
@@ -40,15 +40,11 @@ import { BASE_URL } from "../configs/dataUrl.js";
|
||||
*/
|
||||
const extractStreamInfo = async (linkId) => {
|
||||
try {
|
||||
const url = `${BASE_URL}/ajax/server?get=${linkId}`;
|
||||
const { data } = await axios.get(url, {
|
||||
headers: {
|
||||
...headers,
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
}
|
||||
const path = `/ajax/server?get=${linkId}`;
|
||||
const { data } = await fetchWithMirror(path, {
|
||||
headers: { "X-Requested-With": "XMLHttpRequest" }
|
||||
});
|
||||
|
||||
// NOTE: Response is JSON {status, result: {url, skip_data}}
|
||||
if (!data || !data.result) {
|
||||
return { linkId, url: null, skipData: null };
|
||||
}
|
||||
@@ -75,28 +71,23 @@ const extractStreamInfo = async (linkId) => {
|
||||
* Returns raw server data as provided by the source site.
|
||||
*
|
||||
* @param {string} episodeIds - Comma-separated or single episode ID string
|
||||
* @returns {Promise<Object>} Raw server list data from the AJAX response
|
||||
* @returns {Promise<Array>} Array of server objects with type, link_id, ep_id, etc.
|
||||
*
|
||||
* @example
|
||||
* const servers = await extractServerList("12345");
|
||||
* // servers contains the server configuration for that episode
|
||||
* console.log(servers[0].link_id); // first server's link ID
|
||||
*/
|
||||
const extractServerList = async (episodeIds) => {
|
||||
try {
|
||||
const url = `${BASE_URL}/ajax/server/list?servers=${episodeIds}`;
|
||||
const { data: raw } = await axios.get(url, {
|
||||
headers: {
|
||||
...headers,
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
}
|
||||
const path = `/ajax/server/list?servers=${episodeIds}`;
|
||||
const { data: raw } = await fetchWithMirror(path, {
|
||||
headers: { "X-Requested-With": "XMLHttpRequest" }
|
||||
});
|
||||
|
||||
// NOTE: Response is JSON {status, result} where result is HTML
|
||||
const html = typeof raw === "string" ? raw : (raw?.result || "");
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const servers = [];
|
||||
// NOTE: Servers are in .servers div, grouped by type (sub/dub)
|
||||
$(".servers .type").each((_, typeEl) => {
|
||||
const type = $(typeEl).attr("data-type") || "sub";
|
||||
$(typeEl).find("li[data-link-id]").each((__, li) => {
|
||||
@@ -139,18 +130,17 @@ const extractServerList = async (episodeIds) => {
|
||||
*/
|
||||
const extractMapperServers = async (malId, slug, timestamp) => {
|
||||
try {
|
||||
// Validate and sanitize input to prevent SSRF
|
||||
if (!Number.isFinite(Number(malId))) throw new Error("Invalid malId");
|
||||
if (!/^[a-zA-Z0-9-]+$/.test(slug)) throw new Error("Invalid slug");
|
||||
if (!Number.isFinite(Number(timestamp))) throw new Error("Invalid timestamp");
|
||||
const url = `https://mapper.nekostream.site/api/mal/${encodeURIComponent(malId)}/${encodeURIComponent(slug)}/${encodeURIComponent(timestamp)}`;
|
||||
const { data } = await axios.get(url, { headers });
|
||||
|
||||
const path = `/ajax/mapper/${encodeURIComponent(malId)}/${encodeURIComponent(slug)}/${encodeURIComponent(timestamp)}`;
|
||||
const { data } = await fetchWithMirror(path);
|
||||
|
||||
const servers = [];
|
||||
|
||||
if (data && typeof data === "object") {
|
||||
for (const [provider, sources] of Object.entries(data)) {
|
||||
// NOTE: Each provider may have sub, dub, or both source types
|
||||
if (sources && sources.sub) {
|
||||
servers.push({
|
||||
provider,
|
||||
@@ -172,7 +162,6 @@ const extractMapperServers = async (malId, slug, timestamp) => {
|
||||
|
||||
return servers;
|
||||
} catch (error) {
|
||||
// NOTE: Returns empty array on failure — mapper API may be unavailable
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user