mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
fix: streaming endpoints - parse JSON responses, extract server HTML
- episodeList: Parse AJAX JSON response, extract server_ids/timestamp/mal_id - streamInfo: Parse server list HTML into structured server objects - streamInfo: Parse stream URL from JSON response properly - Added cheerio import to streamInfo extractor - Servers now return: type, link_id, cmid, sv_id, ep_id, name
This commit is contained in:
@@ -56,27 +56,32 @@ const extractEpisodeList = async (slug) => {
|
|||||||
try {
|
try {
|
||||||
// NOTE: AJAX endpoint requires X-Requested-With header to return HTML fragment
|
// NOTE: AJAX endpoint requires X-Requested-With header to return HTML fragment
|
||||||
const ajaxUrl = `${BASE_URL}/ajax/episode/list/${animeId}`;
|
const ajaxUrl = `${BASE_URL}/ajax/episode/list/${animeId}`;
|
||||||
const { data: ajaxData } = await axios.get(ajaxUrl, {
|
const { data: ajaxRaw } = await axios.get(ajaxUrl, {
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...headers,
|
||||||
"X-Requested-With": "XMLHttpRequest"
|
"X-Requested-With": "XMLHttpRequest"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const $ep = cheerio.load(ajaxData);
|
// NOTE: AJAX response is JSON {status, result} where result is HTML
|
||||||
|
const ajaxHtml = typeof ajaxRaw === "string" ? ajaxRaw : (ajaxRaw?.result || "");
|
||||||
|
const $ep = cheerio.load(ajaxHtml);
|
||||||
|
|
||||||
// ---- FEATURE: Parse episode list from AJAX response HTML ----
|
// ---- FEATURE: Parse episode list from AJAX response HTML ----
|
||||||
// NOTE: Episode links have either data-num or data-ep-id attributes
|
// NOTE: Episode links have either data-num or data-ep-id attributes
|
||||||
const episodes = [];
|
const episodes = [];
|
||||||
$ep("a[data-num], a[data-ep-id]").each((i, el) => {
|
$ep("a[data-num], a[data-ep-id], a[data-id]").each((i, el) => {
|
||||||
const epId = $ep(el).attr("data-ep-id") || $ep(el).attr("data-id") || "";
|
const epId = $ep(el).attr("data-ep-id") || $ep(el).attr("data-id") || "";
|
||||||
const epNum = parseInt($ep(el).attr("data-num")) || i + 1;
|
const epNum = parseInt($ep(el).attr("data-num")) || i + 1;
|
||||||
const epSlug = $ep(el).attr("data-slug") || "";
|
const epSlug = $ep(el).attr("data-slug") || "";
|
||||||
const href = $ep(el).attr("href") || "";
|
const href = $ep(el).attr("href") || "";
|
||||||
// NOTE: Episode title is in .ep-title or .ep-name span
|
|
||||||
const epTitle = $ep(el).find(".ep-title, .ep-name").text().trim() || "";
|
const epTitle = $ep(el).find(".ep-title, .ep-name").text().trim() || "";
|
||||||
// NOTE: Active class indicates the currently-playing episode
|
|
||||||
const isActive = $ep(el).hasClass("active") || false;
|
const isActive = $ep(el).hasClass("active") || false;
|
||||||
|
// NOTE: data-ids is the base64-encoded server ID string needed for /api/servers
|
||||||
|
const serverIds = $ep(el).attr("data-ids") || "";
|
||||||
|
// NOTE: data-timestamp used for mapper API
|
||||||
|
const timestamp = $ep(el).attr("data-timestamp") || "";
|
||||||
|
const malId = $ep(el).attr("data-mal") || "";
|
||||||
|
|
||||||
episodes.push({
|
episodes.push({
|
||||||
id: epId,
|
id: epId,
|
||||||
@@ -84,7 +89,10 @@ const extractEpisodeList = async (slug) => {
|
|||||||
slug: epSlug,
|
slug: epSlug,
|
||||||
title: epTitle,
|
title: epTitle,
|
||||||
active: isActive,
|
active: isActive,
|
||||||
href
|
href,
|
||||||
|
server_ids: serverIds,
|
||||||
|
timestamp,
|
||||||
|
mal_id: malId
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
* ======= • ======= • ======= • ======= • =======• =======
|
* ======= • ======= • ======= • ======= • =======• =======
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as cheerio from "cheerio";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { headers } from "../configs/header.config.js";
|
import { headers } from "../configs/header.config.js";
|
||||||
import { BASE_URL } from "../configs/dataUrl.js";
|
import { BASE_URL } from "../configs/dataUrl.js";
|
||||||
@@ -40,7 +41,6 @@ import { BASE_URL } from "../configs/dataUrl.js";
|
|||||||
const extractStreamInfo = async (linkId) => {
|
const extractStreamInfo = async (linkId) => {
|
||||||
try {
|
try {
|
||||||
const url = `${BASE_URL}/ajax/server?get=${linkId}`;
|
const url = `${BASE_URL}/ajax/server?get=${linkId}`;
|
||||||
// NOTE: X-Requested-With header is required for AJAX endpoints on anikototv.to
|
|
||||||
const { data } = await axios.get(url, {
|
const { data } = await axios.get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...headers,
|
||||||
@@ -48,14 +48,17 @@ const extractStreamInfo = async (linkId) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// NOTE: Response is JSON {status, result: {url, skip_data}}
|
||||||
if (!data || !data.result) {
|
if (!data || !data.result) {
|
||||||
return { linkId, url: null, skipData: null };
|
return { linkId, url: null, skipData: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = typeof data.result === "string" ? (() => { try { return JSON.parse(data.result); } catch { return {}; } })() : data.result;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
linkId,
|
linkId,
|
||||||
url: data.result.url || null,
|
url: result.url || null,
|
||||||
skipData: data.result.skip_data || null
|
skipData: result.skip_data || null
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -81,14 +84,34 @@ const extractStreamInfo = async (linkId) => {
|
|||||||
const extractServerList = async (episodeIds) => {
|
const extractServerList = async (episodeIds) => {
|
||||||
try {
|
try {
|
||||||
const url = `${BASE_URL}/ajax/server/list?servers=${episodeIds}`;
|
const url = `${BASE_URL}/ajax/server/list?servers=${episodeIds}`;
|
||||||
const { data } = await axios.get(url, {
|
const { data: raw } = await axios.get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...headers,
|
||||||
"X-Requested-With": "XMLHttpRequest"
|
"X-Requested-With": "XMLHttpRequest"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
// 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) => {
|
||||||
|
servers.push({
|
||||||
|
type,
|
||||||
|
ep_id: $(li).attr("data-ep-id") || "",
|
||||||
|
link_id: $(li).attr("data-link-id") || "",
|
||||||
|
cmid: $(li).attr("data-cmid") || "",
|
||||||
|
sv_id: $(li).attr("data-sv-id") || "",
|
||||||
|
name: $(li).text().trim() || ""
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return servers;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user