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:
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* ======= • ======= • ======= • ======= • =======• =======
|
||||
* AniKotoAPI — parseListItem.helper.js
|
||||
* Repository: https://github.com/Shineii86/AniKotoAPI
|
||||
*
|
||||
* @description
|
||||
* Shared helper for parsing anime list items from Cheerio DOM.
|
||||
* Eliminates code duplication across multiple extractors by
|
||||
* providing a unified parsing function for standard list items.
|
||||
*
|
||||
* @exports
|
||||
* parseListItems, parseListItem
|
||||
*
|
||||
* @author Shinei Nouzen
|
||||
* @license MIT
|
||||
* ======= • ======= • ======= • ======= • =======• =======
|
||||
*/
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// SINGLE ITEM PARSER
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
// ---- FEATURE: Parse a single anime list item from a Cheerio element ----
|
||||
/**
|
||||
* Parses a single anime list item from a Cheerio element.
|
||||
* Handles multiple CSS selector patterns for different page layouts
|
||||
* by falling through comma-separated selectors until one matches.
|
||||
*
|
||||
* @param {CheerioAPI} $ - Cheerio instance with loaded HTML
|
||||
* @param {CheerioElement} el - DOM element to parse
|
||||
* @param {object} [options] - Parsing options
|
||||
* @param {boolean} [options.includeAnimeId=false] - Include animeId field from data-tip attribute
|
||||
* @param {boolean} [options.includeRating=true] - Include rating field from .rated span
|
||||
* @returns {object|null} Parsed anime object or null if slug is missing (invalid item)
|
||||
*
|
||||
* @example
|
||||
* const item = parseListItem($, el, { includeAnimeId: true });
|
||||
* if (item) results.push(item);
|
||||
*/
|
||||
const parseListItem = ($, el, options = {}) => {
|
||||
const { includeAnimeId = false, includeRating = true } = options;
|
||||
|
||||
// NOTE: slug is the primary key — skip items without a valid watch link
|
||||
const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
||||
if (!slug) return null;
|
||||
|
||||
// NOTE: Multiple selectors handle different page layouts (#list-items vs .film_list-wrap vs .item)
|
||||
const poster = $(el).find(".ani.poster.tip > a > img, img").attr("src") || "";
|
||||
const title = $(el).find(".info .b1 a.name.d-title, .film-name a, .name.d-title, a.name").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".info .b1 a.name.d-title, .name.d-title, a.name").attr("data-jp") || "";
|
||||
const sub = parseInt($(el).find(".ep-status.sub span").text().trim()) || 0;
|
||||
const dub = parseInt($(el).find(".ep-status.dub span").text().trim()) || 0;
|
||||
const total = parseInt($(el).find(".ep-status.total span").text().trim()) || 0;
|
||||
const type = $(el).find(".info .meta .m-item:nth-child(2) label, .meta .inner .right, .fdi-item:nth-child(2), .type").text().trim() || "";
|
||||
|
||||
const item = { slug, poster, title, japaneseTitle, sub, dub, total, type };
|
||||
|
||||
// NOTE: animeId comes from data-tip on .ani.poster.tip — only present on some page layouts
|
||||
if (includeAnimeId) {
|
||||
item.animeId = $(el).find(".ani.poster.tip").attr("data-tip") || "";
|
||||
}
|
||||
|
||||
// NOTE: Rating uses different selectors depending on whether it's a list page or detail page
|
||||
if (includeRating) {
|
||||
item.rating = $(el).find(".info .meta .m-item.rated span, .rating, .fdi-item:nth-child(3)").text().trim() || "";
|
||||
}
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// MULTI-ITEM PARSER
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
// ---- FEATURE: Parse multiple anime list items from a Cheerio instance ----
|
||||
/**
|
||||
* Parses multiple anime list items from a Cheerio instance.
|
||||
* Uses multiple CSS selectors to handle different page layouts,
|
||||
* returning only items with valid slugs.
|
||||
*
|
||||
* @param {CheerioAPI} $ - Cheerio instance with loaded HTML
|
||||
* @param {string} [selector] - CSS selector for list items (default: auto-detect all layouts)
|
||||
* @param {object} [options] - Parsing options (passed to parseListItem)
|
||||
* @returns {Array<object>} Array of parsed anime objects (empty if none found)
|
||||
*
|
||||
* @example
|
||||
* const $ = await extractPages("/most-popular");
|
||||
* const items = parseListItems($);
|
||||
* console.log(items.length); // Number of parsed items
|
||||
*/
|
||||
const parseListItems = ($, selector = "#list-items > .item, .film_list-wrap .flw-item, .film-detail, .item", options = {}) => {
|
||||
const results = [];
|
||||
|
||||
$(selector).each((i, el) => {
|
||||
const item = parseListItem($, el, options);
|
||||
if (item) results.push(item);
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
export { parseListItems, parseListItem };
|
||||
|
||||
// ══════════════════════════════════════════════════════════════ END: parseListItem.helper.js
|
||||
Reference in New Issue
Block a user