mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 19:05:11 +00:00
feat: upgrade to v2.0.0 with multi-mirror fallback, new endpoints, and LRU cache
- Add multi-mirror fallback system with 5 mirror domains - Add LRU cache with configurable TTL per endpoint - Add response compression (gzip) - Add Seasons endpoint (/api/seasons/:id) - Add Watch Order endpoint (/api/watch-order/:id) - Add Latest Updated endpoint (/api/latest-updated) - Add Download endpoint (/api/download) - Add cache stats endpoint (/api/cache/stats) - Add mirror status/reset endpoints (/api/mirrors, /api/mirrors/reset) - Add configurable rate limiting with X-RateLimit headers - Add request timeouts and entity too large handling - Update documentation with all new endpoints - Update CHANGELOG.md with v2.0.0 entry - Update package.json version to 2.0.0 - Add 6 AI agent prompts for development
This commit is contained in:
@@ -4,8 +4,9 @@
|
||||
* Repository: https://github.com/Shineii86/AniKotoAPI
|
||||
*
|
||||
* @description
|
||||
* Extracts watch order information for a specific anime via API.
|
||||
* Returns recommended viewing order from the internal API endpoint.
|
||||
* Extracts watch order and related anime information from the
|
||||
* anikototv.to AJAX endpoint. Returns related anime with their
|
||||
* relationship types (sequel, prequel, summary, etc.).
|
||||
*
|
||||
* @exports
|
||||
* extractWatchOrder
|
||||
@@ -15,30 +16,90 @@
|
||||
* ======= • ======= • ======= • ======= • =======• =======
|
||||
*/
|
||||
|
||||
import axios from "axios";
|
||||
import { headers } from "../configs/header.config.js";
|
||||
import { BASE_URL } from "../configs/dataUrl.js";
|
||||
import * as cheerio from "cheerio";
|
||||
import { fetchWithMirror } from "../helper/mirror.helper.js";
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// WATCH ORDER API EXTRACTION
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
// ---- FEATURE: Extract watch order data from internal API ----
|
||||
/**
|
||||
* Fetches watch order information for a specific anime from the internal API.
|
||||
* Returns an array of watch order objects or empty array on error.
|
||||
* Fetches and parses watch order / related anime for an anime.
|
||||
* Scrapes the AJAX endpoint that returns related anime HTML.
|
||||
*
|
||||
* @param {string} animeId - The anime ID to fetch watch order for
|
||||
* @returns {Promise<Array<Object>>} Array of watch order objects or empty array
|
||||
* @param {string|number} animeId - The numeric anime ID
|
||||
* @returns {Promise<Object>} Object with animeId, totalRelated, related array
|
||||
*
|
||||
* @example
|
||||
* const watchOrder = await extractWatchOrder("12345");
|
||||
* console.log(watchOrder[0].title); // First anime in watch order
|
||||
* const data = await extractWatchOrder(7174);
|
||||
* console.log(data.totalRelated); // number of related anime
|
||||
* console.log(data.related[0].title); // first related anime
|
||||
*/
|
||||
const extractWatchOrder = async (animeId) => {
|
||||
throw new Error("Watch order endpoint not available - source site does not provide this data");
|
||||
try {
|
||||
const { data } = await fetchWithMirror(`/api/watch-order/${animeId}`, {
|
||||
headers: { "X-Requested-With": "XMLHttpRequest" }
|
||||
});
|
||||
|
||||
// Handle both string HTML and JSON responses
|
||||
let raw = "";
|
||||
if (typeof data === "string") {
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
raw = json.result || data;
|
||||
} catch (e) {
|
||||
raw = data;
|
||||
}
|
||||
} else if (data?.result) {
|
||||
raw = data.result;
|
||||
} else if (data?.data) {
|
||||
raw = data.data;
|
||||
}
|
||||
|
||||
const $ = cheerio.load(raw);
|
||||
|
||||
// Extract related anime
|
||||
const related = [];
|
||||
|
||||
// Try multiple selectors
|
||||
const selectors = [
|
||||
"#w-related .item",
|
||||
".item.flexserieslist",
|
||||
".scaff.side.items .item",
|
||||
".item"
|
||||
];
|
||||
|
||||
for (const selector of selectors) {
|
||||
$(selector).each((_, el) => {
|
||||
const link = $(el).find("a.name").first();
|
||||
const href = link.attr("href") || "";
|
||||
const title = link.text().trim() || "";
|
||||
const poster = $(el).find("img").attr("src") || "";
|
||||
const slug = href.split("/watch/").pop() || "";
|
||||
|
||||
// Extract relationship type
|
||||
const relationEl = $(el).find(".relation, .serieslabelitem").first();
|
||||
let relationType = relationEl.text().trim().toLowerCase() ||
|
||||
relationEl.attr("id") || "related";
|
||||
|
||||
if (slug || title) {
|
||||
related.push({
|
||||
title,
|
||||
slug,
|
||||
poster,
|
||||
url: href,
|
||||
relation: relationType
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (related.length > 0) break;
|
||||
}
|
||||
|
||||
return {
|
||||
animeId,
|
||||
totalRelated: related.length,
|
||||
related
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export { extractWatchOrder };
|
||||
|
||||
// ══════════════════════════════════════════════════════════════ END: watchOrder.extractor.js
|
||||
|
||||
Reference in New Issue
Block a user