mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
fix: comprehensive diagnostic sweep — fix creatorInfo error masking, schedule/seasons/watch-order extractors
- 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)
This commit is contained in:
@@ -10,12 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Fixed
|
### Fixed
|
||||||
- **CRITICAL**: Fixed `streamInfo.extractor.js` `extractServerList` not parsing JSON response — `fetchWithMirror` returns raw text by default, causing Cheerio to parse a JSON string as HTML, resulting in empty server arrays (null values)
|
- **CRITICAL**: Fixed `streamInfo.extractor.js` `extractServerList` not parsing JSON response — `fetchWithMirror` returns raw text by default, causing Cheerio to parse a JSON string as HTML, resulting in empty server arrays (null values)
|
||||||
- **CRITICAL**: Fixed `streamInfo.extractor.js` `extractMapperServers` not parsing JSON response — same raw text issue caused the data processing loop to be skipped entirely, returning empty arrays
|
- **CRITICAL**: Fixed `streamInfo.extractor.js` `extractMapperServers` not parsing JSON response — same raw text issue caused the data processing loop to be skipped entirely, returning empty arrays
|
||||||
|
- **CRITICAL**: Fixed `creatorInfo.js` middleware overwriting error messages — the `message` field from creatorInfo was replacing actual error messages, making debugging impossible (all 500 errors showed "Built with ❤️ by Shinei Nouzen" instead of the real error)
|
||||||
- Fixed `streamInfo.controller.js` `getStreamInfo` to return 404 with error message when stream URL is null instead of silently returning null values
|
- Fixed `streamInfo.controller.js` `getStreamInfo` to return 404 with error message when stream URL is null instead of silently returning null values
|
||||||
- Fixed `streamInfo.controller.js` `getServerList` to return 404 with error message when server list is empty instead of returning empty array with no indication
|
- Fixed `streamInfo.controller.js` `getServerList` to return 404 with error message when server list is empty instead of returning empty array with no indication
|
||||||
- Fixed `streamInfo.controller.js` to use `TTL.stream` instead of `TTL.default` for stream info caching (stream URLs rotate frequently)
|
- Fixed `streamInfo.controller.js` to use `TTL.stream` instead of `TTL.default` for stream info caching (stream URLs rotate frequently)
|
||||||
- Fixed `streamInfo.controller.js` to use `TTL.servers` instead of `TTL.default` for server list caching
|
- Fixed `streamInfo.controller.js` to use `TTL.servers` instead of `TTL.default` for server list caching
|
||||||
- Fixed `episodeList.extractor.js` JSON.parse error handling — added try/catch around JSON parsing to prevent uncaught exceptions
|
- Fixed `episodeList.extractor.js` JSON.parse error handling — added try/catch around JSON parsing to prevent uncaught exceptions
|
||||||
- Fixed `episodeListAjax.extractor.js` JSON.parse error handling — added try/catch around JSON parsing to prevent uncaught exceptions
|
- Fixed `episodeListAjax.extractor.js` JSON.parse error handling — added try/catch around JSON parsing to prevent uncaught exceptions
|
||||||
|
- Fixed `schedule.extractor.js` — changed from incorrect `/?date=` URL to proper `/ajax/schedule?date=` AJAX endpoint with JSON parsing
|
||||||
|
- Fixed `seasons.extractor.js` — updated CSS selectors to handle new HTML structure where items use `data-tip` attribute instead of `<a>` tags
|
||||||
|
- Fixed `watchOrder.extractor.js` — updated CSS selectors to handle new HTML structure, added fallback to `data-tip` attribute for slugs, fixed type/episodes field extraction from `.meta .dot` elements
|
||||||
|
|
||||||
## [2.2.0] - 2026-07-28
|
## [2.2.0] - 2026-07-28
|
||||||
|
|
||||||
|
|||||||
@@ -42,24 +42,37 @@ import { fetchWithMirror } from "../helper/mirror.helper.js";
|
|||||||
*/
|
*/
|
||||||
const extractSchedule = async (date) => {
|
const extractSchedule = async (date) => {
|
||||||
try {
|
try {
|
||||||
const path = `/?date=${date}`;
|
// NOTE: The schedule data is loaded via AJAX endpoint, not a static page
|
||||||
const { data } = await fetchWithMirror(path);
|
const path = `/ajax/schedule?date=${date}`;
|
||||||
const $ = cheerio.load(data);
|
const { data: raw } = await fetchWithMirror(path, {
|
||||||
|
headers: { "X-Requested-With": "XMLHttpRequest" }
|
||||||
|
});
|
||||||
|
|
||||||
|
// NOTE: Parse JSON response first — fetchWithMirror returns raw text by default
|
||||||
|
let parsed = raw;
|
||||||
|
if (typeof raw === "string") {
|
||||||
|
try { parsed = JSON.parse(raw); } catch { parsed = {}; }
|
||||||
|
}
|
||||||
|
const html = parsed?.result || "";
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
const schedule = [];
|
const schedule = [];
|
||||||
|
|
||||||
// NOTE: Dual selector pattern — different mirrors use different HTML structures
|
// NOTE: Schedule items live inside .item with .time, .ep, .title structure
|
||||||
// .schedule-item = new layout, .anime-schedule .item = old layout
|
$(".item").each((i, el) => {
|
||||||
$(".schedule-item, .anime-schedule .item").each((i, el) => {
|
const title = $(el).find(".title").text().trim() || "";
|
||||||
const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
const jpTitle = $(el).find(".title").attr("data-jp") || "";
|
||||||
const title = $(el).find(".film-name a, .name").text().trim() || "";
|
const time = $(el).find(".time").text().trim() || "";
|
||||||
const time = $(el).find(".time, .schedule-time").text().trim() || "";
|
const episodeText = $(el).find(".ep span").text().trim() || "";
|
||||||
const episodeNo = parseInt($(el).find(".episode-no, .ep-num").text().trim()) || 0;
|
const episodeNo = parseInt(episodeText.replace(/\D/g, "")) || 0;
|
||||||
|
// NOTE: Items may not have direct links — use title as identifier
|
||||||
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "";
|
||||||
|
|
||||||
if (slug) {
|
if (title) {
|
||||||
schedule.push({
|
schedule.push({
|
||||||
slug,
|
slug,
|
||||||
title,
|
title,
|
||||||
|
japaneseTitle: jpTitle,
|
||||||
time,
|
time,
|
||||||
episode_no: episodeNo
|
episode_no: episodeNo
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,7 +55,10 @@ const extractSeasons = async (slugOrId) => {
|
|||||||
const href = link.attr("href") || "";
|
const href = link.attr("href") || "";
|
||||||
const poster = $(el).find("img").attr("src") || "";
|
const poster = $(el).find("img").attr("src") || "";
|
||||||
const title = $(el).find(".name, .title").text().trim() || link.text().trim() || "";
|
const title = $(el).find(".name, .title").text().trim() || link.text().trim() || "";
|
||||||
const slug = href.split("/watch/").pop() || "";
|
let slug = href.split("/watch/").pop() || "";
|
||||||
|
if (!slug) {
|
||||||
|
slug = $(el).find(".poster").attr("data-tip") || "";
|
||||||
|
}
|
||||||
|
|
||||||
if (slug || title) {
|
if (slug || title) {
|
||||||
seasons.push({ title, slug, poster, url: href });
|
seasons.push({ title, slug, poster, url: href });
|
||||||
@@ -69,7 +72,11 @@ const extractSeasons = async (slugOrId) => {
|
|||||||
const href = link.attr("href") || "";
|
const href = link.attr("href") || "";
|
||||||
const poster = $(el).find("img").attr("src") || "";
|
const poster = $(el).find("img").attr("src") || "";
|
||||||
const title = $(el).find(".name").text().trim() || "";
|
const title = $(el).find(".name").text().trim() || "";
|
||||||
const slug = href.split("/watch/").pop() || "";
|
let slug = href.split("/watch/").pop() || "";
|
||||||
|
// NOTE: Items may not have <a> tags — fall back to data-tip attribute
|
||||||
|
if (!slug) {
|
||||||
|
slug = $(el).find(".poster").attr("data-tip") || "";
|
||||||
|
}
|
||||||
const score = $(el).find(".score").text().trim() || "";
|
const score = $(el).find(".score").text().trim() || "";
|
||||||
const episodes = $(el).find(".meta .dot:last-child").text().trim() || "";
|
const episodes = $(el).find(".meta .dot:last-child").text().trim() || "";
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const extractWatchOrder = async (slugOrId) => {
|
|||||||
const href = link.attr("href") || "";
|
const href = link.attr("href") || "";
|
||||||
const title = $(el).find(".name").text().trim() || "";
|
const title = $(el).find(".name").text().trim() || "";
|
||||||
const poster = $(el).find("img").attr("src") || "";
|
const poster = $(el).find("img").attr("src") || "";
|
||||||
const slug = href.split("/watch/").pop() || "";
|
const slug = href.split("/watch/").pop() || $(el).find(".poster").attr("data-tip") || "";
|
||||||
const relation = $(el).find(".relation, .serieslabelitem").text().trim() || "related";
|
const relation = $(el).find(".relation, .serieslabelitem").text().trim() || "related";
|
||||||
|
|
||||||
if (slug) {
|
if (slug) {
|
||||||
@@ -63,17 +63,24 @@ const extractWatchOrder = async (slugOrId) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: Fallback — extract trending sidebar items as suggested watch order
|
// NOTE: Fallback — extract from #watch-order section (trending sidebar items as suggested watch order)
|
||||||
if (related.length === 0) {
|
if (related.length === 0) {
|
||||||
$(".w-side-section:has(.title:contains('Trending')) .item, .w-side-section:first .item").each((_, el) => {
|
$("#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 link = $(el).find("a").first();
|
||||||
const href = link.attr("href") || "";
|
const href = link.attr("href") || "";
|
||||||
const title = $(el).find(".name").text().trim() || "";
|
const title = $(el).find(".name").text().trim() || "";
|
||||||
const poster = $(el).find("img").attr("src") || "";
|
const poster = $(el).find("img").attr("src") || "";
|
||||||
const slug = href.split("/watch/").pop() || "";
|
// 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() || "";
|
const score = $(el).find(".score").text().trim() || "";
|
||||||
const type = $(el).find(".meta .dot:last-child").text().trim() || "";
|
// NOTE: .meta .dot elements: [0]=score, [1]=type, [2]=episodes
|
||||||
const episodes = $(el).find(".meta .dot:first-child").text().trim() || "";
|
const metaDots = $(el).find(".meta .dot");
|
||||||
|
const type = metaDots.eq(1).text().trim() || "";
|
||||||
|
const episodes = metaDots.eq(2).text().trim() || "";
|
||||||
|
|
||||||
if (slug && slug !== slugOrId) {
|
if (slug && slug !== slugOrId) {
|
||||||
related.push({ title, slug, poster, url: href, relation: "trending", score, type, episodes });
|
related.push({ title, slug, poster, url: href, relation: "trending", score, type, episodes });
|
||||||
|
|||||||
@@ -58,14 +58,21 @@ const addCreatorInfo = (req, res, next) => {
|
|||||||
res.json = (data) => {
|
res.json = (data) => {
|
||||||
// NOTE: Only inject if data is a plain object and missing creator field
|
// NOTE: Only inject if data is a plain object and missing creator field
|
||||||
if (typeof data === "object" && data !== null && !data.creator) {
|
if (typeof data === "object" && data !== null && !data.creator) {
|
||||||
|
// NOTE: Preserve existing 'message' field (e.g., error messages) — only add creator metadata
|
||||||
|
const preservedMessage = data.message;
|
||||||
data = {
|
data = {
|
||||||
...data,
|
...data,
|
||||||
creator: creatorInfo.creator,
|
creator: creatorInfo.creator,
|
||||||
github: creatorInfo.github,
|
github: creatorInfo.github,
|
||||||
telegram: creatorInfo.telegram,
|
telegram: creatorInfo.telegram,
|
||||||
message: creatorInfo.message,
|
|
||||||
timestamp: new Date().toLocaleString("en-IN", { timeZone: "Asia/Kolkata", hour12: true }),
|
|
||||||
};
|
};
|
||||||
|
// NOTE: Restore original message if it existed (error handler sets this)
|
||||||
|
if (preservedMessage) {
|
||||||
|
data.message = preservedMessage;
|
||||||
|
} else {
|
||||||
|
data.message = creatorInfo.message;
|
||||||
|
}
|
||||||
|
data.timestamp = new Date().toLocaleString("en-IN", { timeZone: "Asia/Kolkata", hour12: true });
|
||||||
}
|
}
|
||||||
return originalJson(data);
|
return originalJson(data);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user