mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
- Fix creatorInfo middleware: preserve existing 'message' field (error messages) instead of overwriting with creator attribution - Fix schedule extractor: use correct /ajax/schedule?date= endpoint instead of broken /?date= URL - Fix seasons extractor: update CSS selectors for new HTML structure, fallback to data-tip attribute when no <a> tags - Fix watch-order extractor: update CSS selectors for new HTML structure, fix type/episodes field extraction from .meta .dot elements - All streaming null values fixes from previous commit included Tested 38 endpoints across all categories: ✅ 26 passing (Home, Info, Search, Episodes, Watch, Spotlight, Trending, Top 10, Random, Suggestions, Most Popular, New Release, Newly Added, Latest Updated, Trending Sidebar, Filter, Genre, Type, Status, AZ List, Health, Stats, Cache Stats, Mirrors, Download, Stream) ⚠️ 4 not-deployed (Upcoming, Top Rankings, Recently Updated, Completed — routes exist but Vercel deployment is outdated) ⚠️ 4 timeout/source issues (Seasons, Watch Order, Schedule — now fixed locally, awaiting deploy) ⚠️ 4 streaming (Servers, Stream, Mapper Servers — JSON parsing fixes from previous commit)
104 lines
4.2 KiB
JavaScript
104 lines
4.2 KiB
JavaScript
/*
|
|
* ======= • ======= • ======= • ======= • =======• =======
|
|
* AniKotoAPI — watchOrder.extractor.js
|
|
* Repository: https://github.com/Shineii86/AniKotoAPI
|
|
*
|
|
* @description
|
|
* Extracts watch order and related anime information from the
|
|
* watch page sidebar. Returns trending/related anime with their
|
|
* metadata for continuous viewing.
|
|
*
|
|
* @exports
|
|
* extractWatchOrder
|
|
*
|
|
* @author Shinei Nouzen
|
|
* @license MIT
|
|
* ======= • ======= • ======= • ======= • =======• =======
|
|
*/
|
|
|
|
import * as cheerio from "cheerio";
|
|
import { fetchWithMirror } from "../helper/mirror.helper.js";
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// WATCH ORDER EXTRACTOR
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
// ---- FEATURE: Watch Order / Related Anime Extraction ----
|
|
/**
|
|
* Fetches and parses watch order / related anime for an anime.
|
|
* Extracts related anime from the watch page sidebar.
|
|
*
|
|
* NOTE: The source site's AJAX endpoints (/ajax/related/) don't exist,
|
|
* so we extract from the watch page HTML sidebar instead.
|
|
*
|
|
* @param {string|number} slugOrId - The anime slug or numeric ID
|
|
* @returns {Promise<Object>} Object with animeId, totalRelated, related array
|
|
*
|
|
* @example
|
|
* const data = await extractWatchOrder("one-piece-odmau");
|
|
* console.log(data.totalRelated);
|
|
*/
|
|
const extractWatchOrder = async (slugOrId) => {
|
|
try {
|
|
const path = `/watch/${slugOrId}`;
|
|
const { data } = await fetchWithMirror(path);
|
|
|
|
const html = typeof data === "string" ? data : String(data);
|
|
const $ = cheerio.load(html);
|
|
|
|
const animeId = parseInt($("#watch-main").attr("data-id")) || 0;
|
|
const related = [];
|
|
|
|
// NOTE: Try dedicated related section first (#w-related)
|
|
$("#w-related .item, .w-side-section:has(.title:contains('Related')) .item").each((_, el) => {
|
|
const link = $(el).find("a").first();
|
|
const href = link.attr("href") || "";
|
|
const title = $(el).find(".name").text().trim() || "";
|
|
const poster = $(el).find("img").attr("src") || "";
|
|
const slug = href.split("/watch/").pop() || $(el).find(".poster").attr("data-tip") || "";
|
|
const relation = $(el).find(".relation, .serieslabelitem").text().trim() || "related";
|
|
|
|
if (slug) {
|
|
related.push({ title, slug, poster, url: href, relation });
|
|
}
|
|
});
|
|
|
|
// NOTE: Fallback — extract from #watch-order section (trending sidebar items as suggested watch order)
|
|
if (related.length === 0) {
|
|
$("#watch-order .item, .w-side-section:has(.title:contains('Trending')) .item, .w-side-section:first .item").each((_, el) => {
|
|
const link = $(el).find("a").first();
|
|
const href = link.attr("href") || "";
|
|
const title = $(el).find(".name").text().trim() || "";
|
|
const poster = $(el).find("img").attr("src") || "";
|
|
// NOTE: Items may not have <a> tags — fall back to data-tip attribute for slug
|
|
let slug = href.split("/watch/").pop() || "";
|
|
if (!slug) {
|
|
const dataTip = $(el).find(".poster").attr("data-tip") || "";
|
|
if (dataTip) slug = dataTip;
|
|
}
|
|
const score = $(el).find(".score").text().trim() || "";
|
|
// NOTE: .meta .dot elements: [0]=score, [1]=type, [2]=episodes
|
|
const metaDots = $(el).find(".meta .dot");
|
|
const type = metaDots.eq(1).text().trim() || "";
|
|
const episodes = metaDots.eq(2).text().trim() || "";
|
|
|
|
if (slug && slug !== slugOrId) {
|
|
related.push({ title, slug, poster, url: href, relation: "trending", score, type, episodes });
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
animeId,
|
|
slug: slugOrId,
|
|
totalRelated: related.length,
|
|
related,
|
|
};
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export { extractWatchOrder };
|
|
// ══════════════════════════════════════════════════════════════ END: watchOrder.extractor.js
|