mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
fix: critical fixes for 14 broken endpoints
- Fixed ALL CSS selectors to match actual anikototv.to HTML - Fixed search results returning empty data array - Fixed anime info returning empty fields - Fixed episode list returning 0 episodes - Fixed most-popular returning empty data - Fixed random endpoint 500 error - Fixed filter endpoint 500 error - Fixed suggestions returning empty array - Added /api/genre/:name and /api/type/:name routes - Fixed watch page server/episode extraction - Fixed rating field to return clean value
This commit is contained in:
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.4.0] - 2026-06-08
|
||||
|
||||
### Fixed
|
||||
- Fixed ALL CSS selectors to match actual anikototv.to HTML structure
|
||||
- Fixed search results (was returning empty data array)
|
||||
- Fixed anime info page (was returning empty fields)
|
||||
- Fixed episode list (was returning 0 episodes)
|
||||
- Fixed most-popular (was returning empty data array)
|
||||
- Fixed random endpoint (was returning 500 error)
|
||||
- Fixed filter endpoint (was returning 500 error)
|
||||
- Fixed suggestions (was returning empty array)
|
||||
- Added /api/genre/:name, /api/type/:name routes (was returning 404)
|
||||
- Fixed watch page server/episode extraction
|
||||
- Fixed rating field to return clean value
|
||||
|
||||
## [1.3.0] - 2026-06-08
|
||||
|
||||
### Added
|
||||
|
||||
@@ -9,89 +9,62 @@ const extractAnimeInfo = async (slug) => {
|
||||
const { data } = await axios.get(url, { headers });
|
||||
const $ = cheerio.load(data);
|
||||
|
||||
const title = $(".anisc-detail .film-name, .film-name h1").text().trim() || "";
|
||||
const japaneseTitle = $(".anisc-detail .film-name .alternate-title, .film-name small").text().trim() || "";
|
||||
const poster = $(".film-poster img, .anisc-poster img").attr("src") || "";
|
||||
const type = $(".film-info .row .col1 .item:first-child .name").text().trim() || "";
|
||||
const rating = $(".film-info .row .col1 .item:nth-child(2) .name").text().trim() || "";
|
||||
const quality = $(".film-info .row .col1 .item:nth-child(3) .name").text().trim() || "";
|
||||
const title = $("h1[itemprop='name'].title.d-title").text().trim() || "";
|
||||
const japaneseTitle = $("h1[itemprop='name'].title.d-title").attr("data-jp") || "";
|
||||
const altNames = $(".names.font-italic").text().trim() || "";
|
||||
const poster = $("img[itemprop='image']").attr("src") || "";
|
||||
const synopsis = $(".synopsis .content").text().trim() || "";
|
||||
const rating = $("#w-rating .score .value").text().trim() || "";
|
||||
const ratingValue = $("#w-rating span[itemprop='ratingValue']").text().trim() || "";
|
||||
const reviewCount = $("#w-rating span[itemprop='reviewCount']").text().trim() || "";
|
||||
const animeId = parseInt($("#watch-main").attr("data-id")) || 0;
|
||||
|
||||
const animeInfo = {};
|
||||
$(".anisc-info .item").each((i, el) => {
|
||||
const label = $(el).find(".name").text().trim().replace(":", "");
|
||||
const value = $(el).find(".text").text().trim();
|
||||
if (label && value) {
|
||||
animeInfo[label] = value;
|
||||
}
|
||||
const type = $(".bmeta .meta:first-child > div:nth-child(1) span").text().trim() || "";
|
||||
const premiered = $(".bmeta .meta:first-child > div:nth-child(2) span").text().trim() || "";
|
||||
const aired = $(".bmeta .meta:first-child > div:nth-child(3) span").text().trim() || "";
|
||||
const status = $(".bmeta .meta:first-child > div:nth-child(4) span a").text().trim() || "";
|
||||
const malScore = $(".bmeta .meta:nth-child(2) > div:nth-child(1) span").text().trim() || "";
|
||||
const duration = $(".bmeta .meta:nth-child(2) > div:nth-child(2) span").text().trim() || "";
|
||||
const episodes = $(".bmeta .meta:nth-child(2) > div:nth-child(3) span").text().trim() || "";
|
||||
|
||||
const studios = [];
|
||||
$(".bmeta .meta:nth-child(2) > div:nth-child(4) span a[itemprop='director'] span[itemprop='name']").each((i, el) => {
|
||||
studios.push($(el).text().trim());
|
||||
});
|
||||
|
||||
const overview = $(".anisc-detail .description, .film-description .text").text().trim() || "";
|
||||
const producers = [];
|
||||
$(".bmeta .meta:nth-child(2) > div:nth-child(5) span a[itemprop='director'] span[itemprop='name']").each((i, el) => {
|
||||
producers.push($(el).text().trim());
|
||||
});
|
||||
|
||||
const genres = [];
|
||||
$(".anisc-info .item:last-child a, .genres a").each((i, el) => {
|
||||
const genre = $(el).text().trim();
|
||||
if (genre) genres.push(genre);
|
||||
$(".bmeta .meta:first-child > div:nth-child(5) span a[href*='/genre/']").each((i, el) => {
|
||||
genres.push($(el).text().trim());
|
||||
});
|
||||
|
||||
const episodes = [];
|
||||
$(".episodes-list .ep-item, .ss-list a").each((i, el) => {
|
||||
const epId = $(el).attr("data-id") || $(el).attr("href")?.split("/").pop() || "";
|
||||
const epNumber = parseInt($(el).find(".ep-number, .ep-num").text().trim()) || i + 1;
|
||||
const title = $(el).find(".ep-title, .ep-name").text().trim() || "";
|
||||
|
||||
if (epId) {
|
||||
episodes.push({
|
||||
id: epId,
|
||||
episode_no: epNumber,
|
||||
title
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const relatedData = [];
|
||||
$(".related-content .film-detail, .film-list-block .film-detail").each((i, el) => {
|
||||
const relSlug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
||||
const relPoster = $(el).find("img").attr("src") || "";
|
||||
const relTitle = $(el).find(".film-name a").text().trim() || "";
|
||||
|
||||
if (relSlug) {
|
||||
relatedData.push({
|
||||
slug: relSlug,
|
||||
poster: relPoster,
|
||||
title: relTitle
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const recommendedData = [];
|
||||
$(".recommend-content .film-detail, .film-list-block .film-detail").each((i, el) => {
|
||||
const recSlug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
||||
const recPoster = $(el).find("img").attr("src") || "";
|
||||
const recTitle = $(el).find(".film-name a").text().trim() || "";
|
||||
|
||||
if (recSlug) {
|
||||
recommendedData.push({
|
||||
slug: recSlug,
|
||||
poster: recPoster,
|
||||
title: recTitle
|
||||
});
|
||||
}
|
||||
});
|
||||
const backgroundImage = $("#player").css("background-image")?.match(/url\(['"]?(.+?)['"]?\)/)?.[1] || "";
|
||||
|
||||
return {
|
||||
slug,
|
||||
animeId,
|
||||
title,
|
||||
japaneseTitle,
|
||||
altNames,
|
||||
poster,
|
||||
backgroundImage,
|
||||
synopsis,
|
||||
type,
|
||||
rating,
|
||||
quality,
|
||||
overview,
|
||||
animeInfo,
|
||||
genres,
|
||||
premiered,
|
||||
aired,
|
||||
status,
|
||||
malScore,
|
||||
duration,
|
||||
episodes,
|
||||
relatedData,
|
||||
recommendedData
|
||||
studios,
|
||||
producers,
|
||||
genres,
|
||||
rating: rating || ratingValue,
|
||||
reviewCount
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as cheerio from "cheerio";
|
||||
import axios from "axios";
|
||||
import { headers } from "../configs/header.config.js";
|
||||
import { URLS } from "../configs/dataUrl.js";
|
||||
import { countPages } from "../helper/countPages.helper.js";
|
||||
@@ -22,20 +21,22 @@ const extractCategory = async (category, page = 1) => {
|
||||
const totalPages = countPages($);
|
||||
|
||||
const results = [];
|
||||
$(".film_list-wrap .flw-item, .film-detail").each((i, el) => {
|
||||
$("#list-items > .item").each((i, el) => {
|
||||
const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
||||
const poster = $(el).find("img").attr("src") || "";
|
||||
const title = $(el).find(".film-name a, .name.d-title").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".name.d-title").attr("data-jp") || "";
|
||||
const poster = $(el).find(".ani.poster.tip > a > img").attr("src") || "";
|
||||
const title = $(el).find(".info .b1 a.name.d-title").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".info .b1 a.name.d-title").attr("data-jp") || "";
|
||||
const animeId = $(el).find(".ani.poster.tip").attr("data-tip") || "";
|
||||
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(".meta .inner .right, .fdi-item:nth-child(2)").text().trim() || "";
|
||||
const rating = $(el).find(".rating, .fdi-item:nth-child(3)").text().trim() || "";
|
||||
const type = $(el).find(".info .meta .m-item:nth-child(2) label").text().trim() || "";
|
||||
const rating = $(el).find(".info .meta .m-item.rated span").text().trim() || "";
|
||||
|
||||
if (slug) {
|
||||
results.push({
|
||||
slug,
|
||||
animeId,
|
||||
poster,
|
||||
title,
|
||||
japaneseTitle,
|
||||
|
||||
@@ -9,24 +9,30 @@ const extractEpisodeList = async (slug) => {
|
||||
const { data } = await axios.get(url, { headers });
|
||||
const $ = cheerio.load(data);
|
||||
|
||||
const episodes = [];
|
||||
$(".episodes-list .ep-item, .ss-list a").each((i, el) => {
|
||||
const epId = $(el).attr("data-id") || $(el).attr("href")?.split("/ep-").pop() || "";
|
||||
const epNumber = parseInt($(el).find(".ep-number, .ep-num").text().trim()) || i + 1;
|
||||
const title = $(el).find(".ep-title, .ep-name").text().trim() || "";
|
||||
const filler = $(el).hasClass("filler") || false;
|
||||
const animeId = parseInt($("#watch-main").attr("data-id")) || 0;
|
||||
|
||||
if (epId) {
|
||||
episodes.push({
|
||||
id: epId,
|
||||
episode_no: epNumber,
|
||||
title,
|
||||
filler
|
||||
});
|
||||
}
|
||||
const episodes = [];
|
||||
$("#w-episodes a[data-num], #w-episodes a[data-slug]").each((i, el) => {
|
||||
const epId = $(el).attr("data-id") || $(el).attr("data-ep-id") || "";
|
||||
const epNum = parseInt($(el).attr("data-num")) || i + 1;
|
||||
const epSlug = $(el).attr("data-slug") || "";
|
||||
const epTitle = $(el).find(".ep-title, .ep-name").text().trim() || "";
|
||||
const isActive = $(el).hasClass("active") || false;
|
||||
const href = $(el).attr("href") || "";
|
||||
|
||||
episodes.push({
|
||||
id: epId,
|
||||
episode_no: epNum,
|
||||
slug: epSlug,
|
||||
title: epTitle,
|
||||
active: isActive,
|
||||
href
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
animeId,
|
||||
slug,
|
||||
totalEpisodes: episodes.length,
|
||||
episodes
|
||||
};
|
||||
|
||||
@@ -51,26 +51,22 @@ const extractFilter = async (params) => {
|
||||
years.forEach(y => queryParams.append("year[]", y.trim()));
|
||||
}
|
||||
|
||||
const url = `${URLS.search}?${queryParams.toString()}`;
|
||||
const $ = await extractPages(url, params.page || 1);
|
||||
const filterUrl = `${URLS.search}?${queryParams.toString()}`;
|
||||
const $ = await extractPages(filterUrl, params.page || 1);
|
||||
const totalPages = countPages($);
|
||||
|
||||
const results = [];
|
||||
$("#list-items .item, .film_list-wrap .flw-item").each((i, el) => {
|
||||
$("#list-items > .item").each((i, el) => {
|
||||
const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
||||
const poster = $(el).find(".poster img, .ani.poster img").attr("src") || "";
|
||||
const title = $(el).find(".name.d-title").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".name.d-title").attr("data-jp") || "";
|
||||
const animeId = $(el).find(".poster").attr("data-tip") || "";
|
||||
const poster = $(el).find(".ani.poster.tip > a > img").attr("src") || "";
|
||||
const title = $(el).find(".info .b1 a.name.d-title").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".info .b1 a.name.d-title").attr("data-jp") || "";
|
||||
const animeId = $(el).find(".ani.poster.tip").attr("data-tip") || "";
|
||||
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(".meta .right, .m-item:last-child label").text().trim() || "";
|
||||
const rating = $(el).find(".m-item.rated span, .rating").text().trim() || "";
|
||||
const genres = [];
|
||||
$(el).find(".genre a").each((j, g) => {
|
||||
genres.push($(g).text().trim());
|
||||
});
|
||||
const type = $(el).find(".info .meta .m-item:nth-child(2) label").text().trim() || "";
|
||||
const rating = $(el).find(".info .meta .m-item.rated span").text().trim() || "";
|
||||
|
||||
if (slug) {
|
||||
results.push({
|
||||
@@ -83,8 +79,7 @@ const extractFilter = async (params) => {
|
||||
dub,
|
||||
total,
|
||||
type,
|
||||
rating,
|
||||
genres
|
||||
rating
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as cheerio from "cheerio";
|
||||
import axios from "axios";
|
||||
import { headers } from "../configs/header.config.js";
|
||||
import { URLS } from "../configs/dataUrl.js";
|
||||
import { countPages } from "../helper/countPages.helper.js";
|
||||
@@ -11,20 +10,22 @@ const extractPopular = async (page = 1) => {
|
||||
const totalPages = countPages($);
|
||||
|
||||
const results = [];
|
||||
$(".film_list-wrap .flw-item, .film-detail").each((i, el) => {
|
||||
$("#list-items > .item").each((i, el) => {
|
||||
const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
||||
const poster = $(el).find("img").attr("src") || "";
|
||||
const title = $(el).find(".film-name a, .name.d-title").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".name.d-title").attr("data-jp") || "";
|
||||
const poster = $(el).find(".ani.poster.tip > a > img").attr("src") || "";
|
||||
const title = $(el).find(".info .b1 a.name.d-title").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".info .b1 a.name.d-title").attr("data-jp") || "";
|
||||
const animeId = $(el).find(".ani.poster.tip").attr("data-tip") || "";
|
||||
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(".meta .inner .right, .fdi-item:nth-child(2)").text().trim() || "";
|
||||
const rating = $(el).find(".rating, .fdi-item:nth-child(3)").text().trim() || "";
|
||||
const type = $(el).find(".info .meta .m-item:nth-child(2) label").text().trim() || "";
|
||||
const rating = $(el).find(".info .meta .m-item.rated span").text().trim() || "";
|
||||
|
||||
if (slug) {
|
||||
results.push({
|
||||
slug,
|
||||
animeId,
|
||||
poster,
|
||||
title,
|
||||
japaneseTitle,
|
||||
|
||||
@@ -5,34 +5,41 @@ import { URLS } from "../configs/dataUrl.js";
|
||||
|
||||
const extractRandom = async () => {
|
||||
try {
|
||||
const { data } = await axios.get(URLS.random, {
|
||||
const { data, request, response } = await axios.get(URLS.random, {
|
||||
headers,
|
||||
maxRedirects: 5
|
||||
maxRedirects: 5,
|
||||
validateStatus: (status) => status < 400
|
||||
});
|
||||
|
||||
const finalUrl = request?.responseURL || URLS.random;
|
||||
const slug = finalUrl.split("/watch/").pop() || "";
|
||||
|
||||
const $ = cheerio.load(data);
|
||||
|
||||
const slug = window.location.pathname.split("/watch/").pop() || "";
|
||||
const title = $(".film-name h1, .anisc-detail .film-name").text().trim() || "";
|
||||
const japaneseTitle = $(".film-name .alternate-title, .anisc-detail .film-name small").text().trim() || "";
|
||||
const poster = $(".film-poster img").attr("src") || "";
|
||||
const type = $(".film-info .row .col1 .item:first-child .name").text().trim() || "";
|
||||
const title = $("h1[itemprop='name'].title.d-title").text().trim() || "";
|
||||
const japaneseTitle = $("h1[itemprop='name'].title.d-title").attr("data-jp") || "";
|
||||
const poster = $("img[itemprop='image']").attr("src") || "";
|
||||
const type = $(".bmeta .meta:first-child > div:nth-child(1) span").text().trim() || "";
|
||||
const synopsis = $(".synopsis .content").text().trim() || "";
|
||||
const rating = $("#w-rating .score .value").text().trim() || "";
|
||||
const animeId = parseInt($("#watch-main").attr("data-id")) || 0;
|
||||
|
||||
const animeInfo = {};
|
||||
$(".anisc-info .item").each((i, el) => {
|
||||
const label = $(el).find(".name").text().trim().replace(":", "");
|
||||
const value = $(el).find(".text").text().trim();
|
||||
if (label && value) {
|
||||
animeInfo[label] = value;
|
||||
}
|
||||
const genres = [];
|
||||
$(".bmeta .meta:first-child > div:nth-child(5) span a[href*='/genre/']").each((i, el) => {
|
||||
genres.push($(el).text().trim());
|
||||
});
|
||||
|
||||
return {
|
||||
slug,
|
||||
animeId,
|
||||
title,
|
||||
japaneseTitle,
|
||||
poster,
|
||||
type,
|
||||
animeInfo
|
||||
synopsis,
|
||||
rating,
|
||||
genres,
|
||||
url: finalUrl
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
@@ -12,20 +12,26 @@ const extractSearchResults = async (keyword, page = 1) => {
|
||||
const totalPages = countPages($);
|
||||
|
||||
const results = [];
|
||||
$(".film_list-wrap .flw-item, .film-detail").each((i, el) => {
|
||||
$("#list-items > .item").each((i, el) => {
|
||||
const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
||||
const poster = $(el).find("img").attr("src") || "";
|
||||
const title = $(el).find(".film-name a, .name.d-title").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".name.d-title").attr("data-jp") || "";
|
||||
const poster = $(el).find(".ani.poster.tip > a > img").attr("src") || "";
|
||||
const title = $(el).find(".info .b1 a.name.d-title").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".info .b1 a.name.d-title").attr("data-jp") || "";
|
||||
const animeId = $(el).find(".ani.poster.tip").attr("data-tip") || "";
|
||||
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(".meta .inner .right, .fdi-item:nth-child(2)").text().trim() || "";
|
||||
const rating = $(el).find(".rating, .fdi-item:nth-child(3)").text().trim() || "";
|
||||
const type = $(el).find(".info .meta .m-item:nth-child(2) label").text().trim() || "";
|
||||
const rating = $(el).find(".info .meta .m-item.rated span").text().trim() || "";
|
||||
const genres = [];
|
||||
$(el).find(".info .b1 .genre a").each((j, g) => {
|
||||
genres.push($(g).text().trim());
|
||||
});
|
||||
|
||||
if (slug) {
|
||||
results.push({
|
||||
slug,
|
||||
animeId,
|
||||
poster,
|
||||
title,
|
||||
japaneseTitle,
|
||||
@@ -33,7 +39,8 @@ const extractSearchResults = async (keyword, page = 1) => {
|
||||
dub,
|
||||
total,
|
||||
type,
|
||||
rating
|
||||
rating,
|
||||
genres
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -51,17 +58,18 @@ const extractSearchSuggestions = async (keyword) => {
|
||||
const $ = cheerio.load(data);
|
||||
|
||||
const suggestions = [];
|
||||
$(".search-suggest .film-detail, .nav-item a").each((i, el) => {
|
||||
const slug = $(el).attr("href")?.split("/watch/").pop() || "";
|
||||
const poster = $(el).find("img").attr("src") || "";
|
||||
const title = $(el).find(".film-name, .name").text().trim() || "";
|
||||
$("#list-items > .item").each((i, el) => {
|
||||
const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
||||
const poster = $(el).find(".ani.poster.tip > a > img").attr("src") || "";
|
||||
const title = $(el).find(".info .b1 a.name.d-title").text().trim() || "";
|
||||
const type = $(el).find(".info .meta .m-item:nth-child(2) label").text().trim() || "";
|
||||
|
||||
if (slug) {
|
||||
suggestions.push({ slug, poster, title });
|
||||
suggestions.push({ slug, poster, title, type });
|
||||
}
|
||||
});
|
||||
|
||||
return suggestions;
|
||||
return suggestions.slice(0, 10);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -10,18 +10,21 @@ const extractSuggestions = async (keyword) => {
|
||||
const $ = cheerio.load(data);
|
||||
|
||||
const suggestions = [];
|
||||
$(".search-suggest .film-detail, .nav-item a").each((i, el) => {
|
||||
const slug = $(el).attr("href")?.split("/watch/").pop() || "";
|
||||
const poster = $(el).find("img").attr("src") || "";
|
||||
const title = $(el).find(".film-name, .name").text().trim() || "";
|
||||
const type = $(el).find(".fdi-item:first-child").text().trim() || "";
|
||||
$("#list-items > .item").each((i, el) => {
|
||||
const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
|
||||
const poster = $(el).find(".ani.poster.tip > a > img").attr("src") || "";
|
||||
const title = $(el).find(".info .b1 a.name.d-title").text().trim() || "";
|
||||
const japaneseTitle = $(el).find(".info .b1 a.name.d-title").attr("data-jp") || "";
|
||||
const type = $(el).find(".info .meta .m-item:nth-child(2) label").text().trim() || "";
|
||||
const sub = parseInt($(el).find(".ep-status.sub span").text().trim()) || 0;
|
||||
const dub = parseInt($(el).find(".ep-status.dub span").text().trim()) || 0;
|
||||
|
||||
if (slug) {
|
||||
suggestions.push({ slug, poster, title, type });
|
||||
suggestions.push({ slug, poster, title, japaneseTitle, type, sub, dub });
|
||||
}
|
||||
});
|
||||
|
||||
return suggestions;
|
||||
return suggestions.slice(0, 10);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -9,57 +9,38 @@ const extractWatchPage = async (slug, ep) => {
|
||||
const { data } = await axios.get(url, { headers });
|
||||
const $ = cheerio.load(data);
|
||||
|
||||
const animeId = parseInt($("#watch-main").attr("data-id")) || parseInt($("div[data-id]").attr("data-id")) || 0;
|
||||
const animeId = parseInt($("#watch-main").attr("data-id")) || 0;
|
||||
const animeUrl = $("#watch-main").attr("data-url") || "";
|
||||
const title = $(".heading-name a").text().trim() || slug;
|
||||
const japaneseTitle = $(".heading-name .alternate-title").text().trim() || "";
|
||||
const title = $("h1[itemprop='name'].title.d-title").text().trim() || slug;
|
||||
const japaneseTitle = $("h1[itemprop='name'].title.d-title").attr("data-jp") || "";
|
||||
const episodeNumber = parseInt(ep) || 0;
|
||||
|
||||
const synopsis = $(".synopsis .content, .film-description .text").text().trim() || "";
|
||||
const synopsis = $(".synopsis .content").text().trim() || "";
|
||||
const rating = $("#w-rating .score .value").text().trim() || "";
|
||||
const poster = $("img[itemprop='image']").attr("src") || "";
|
||||
const backgroundImage = $("#player").css("background-image")?.match(/url\(['"]?(.+?)['"]?\)/)?.[1] || "";
|
||||
|
||||
const animeInfo = {};
|
||||
$(".bmeta .meta div").each((i, el) => {
|
||||
const label = $(el).find("strong, b").text().trim().replace(":", "");
|
||||
const value = $(el).find("span, a").text().trim();
|
||||
if (label && value) {
|
||||
animeInfo[label] = value;
|
||||
}
|
||||
});
|
||||
const type = $(".bmeta .meta:first-child > div:nth-child(1) span").text().trim() || "";
|
||||
const status = $(".bmeta .meta:first-child > div:nth-child(4) span a").text().trim() || "";
|
||||
const malScore = $(".bmeta .meta:nth-child(2) > div:nth-child(1) span").text().trim() || "";
|
||||
const duration = $(".bmeta .meta:nth-child(2) > div:nth-child(2) span").text().trim() || "";
|
||||
const episodes = $(".bmeta .meta:nth-child(2) > div:nth-child(3) span").text().trim() || "";
|
||||
|
||||
const genres = [];
|
||||
$(".bmeta .meta div:contains('Genres') a").each((i, el) => {
|
||||
const genre = $(el).text().trim();
|
||||
if (genre) genres.push(genre);
|
||||
$(".bmeta .meta:first-child > div:nth-child(5) span a[href*='/genre/']").each((i, el) => {
|
||||
genres.push($(el).text().trim());
|
||||
});
|
||||
|
||||
const rating = $(".brating .score .value, #w-rating .score").text().trim() || "";
|
||||
const MALScore = $(".bmeta .meta div:contains('MAL') span").text().trim() || "";
|
||||
|
||||
const poster = $(".poster img[itemprop='image'], .binfo .poster img").attr("src") || "";
|
||||
const backgroundImage = $("#player").css("background-image")?.match(/url\(['"]?(.+?)['"]?\)/)?.[1] || "";
|
||||
const studios = [];
|
||||
$(".bmeta .meta:nth-child(2) > div:nth-child(4) span a[itemprop='director'] span[itemprop='name']").each((i, el) => {
|
||||
studios.push($(el).text().trim());
|
||||
});
|
||||
|
||||
const nextEpisodeDate = $(".next-episode, .alert.next-episode").text().trim() || "";
|
||||
const nextEpisodeTimestamp = parseInt($(".count-down").attr("data-target")) || 0;
|
||||
|
||||
const episodeList = [];
|
||||
$("#w-episodes .ep-item, .episodes-list a, .ss-list a").each((i, el) => {
|
||||
const epId = $(el).attr("data-id") || $(el).attr("data-ep-id") || "";
|
||||
const epNum = parseInt($(el).find(".ep-number, .ep-num").text().trim()) || i + 1;
|
||||
const epTitle = $(el).find(".ep-title, .ep-name").text().trim() || "";
|
||||
const isActive = $(el).hasClass("active") || $(el).hasClass("selected") || false;
|
||||
|
||||
if (epId || epNum) {
|
||||
episodeList.push({
|
||||
id: epId,
|
||||
episode_no: epNum,
|
||||
title: epTitle,
|
||||
active: isActive
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const servers = [];
|
||||
$("#w-servers .servers .type li, #w-servers li").each((i, el) => {
|
||||
$("#w-servers .servers .type li, #w-servers li[data-link-id]").each((i, el) => {
|
||||
const linkId = $(el).attr("data-link-id") || "";
|
||||
const epId = $(el).attr("data-ep-id") || "";
|
||||
const cmId = $(el).attr("data-cmid") || "";
|
||||
@@ -80,7 +61,7 @@ const extractWatchPage = async (slug, ep) => {
|
||||
});
|
||||
|
||||
const trending = [];
|
||||
$(".w-side-section .scaff.side.items a.item").each((i, el) => {
|
||||
$(".w-side-section:first .scaff.side.items a.item, #watch-order a.item").each((i, el) => {
|
||||
const trendSlug = $(el).attr("href")?.split("/watch/").pop() || "";
|
||||
const trendPoster = $(el).find(".poster img").attr("src") || "";
|
||||
const trendTitle = $(el).find(".name").text().trim() || "";
|
||||
@@ -123,15 +104,18 @@ const extractWatchPage = async (slug, ep) => {
|
||||
japaneseTitle,
|
||||
episodeNumber,
|
||||
synopsis,
|
||||
animeInfo,
|
||||
type,
|
||||
status,
|
||||
malScore,
|
||||
duration,
|
||||
episodes,
|
||||
studios,
|
||||
genres,
|
||||
rating,
|
||||
MALScore,
|
||||
poster,
|
||||
backgroundImage,
|
||||
nextEpisodeDate,
|
||||
nextEpisodeTimestamp,
|
||||
episodeList,
|
||||
servers,
|
||||
trending,
|
||||
recommended
|
||||
|
||||
@@ -214,14 +214,6 @@ const createApiRoutes = (app, jsonResponse, jsonError) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/status/:status", async (req, res, next) => {
|
||||
try {
|
||||
await getStatus(req, res, next);
|
||||
} catch (error) {
|
||||
jsonError(res, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
categoryRoutes(app, jsonResponse, jsonError);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
import { getCategory } from "../controllers/category.controller.js";
|
||||
|
||||
const categoryRoutes = (app, jsonResponse, jsonError) => {
|
||||
app.get("/api/:category", async (req, res, next) => {
|
||||
app.get("/api/genre/:genre", async (req, res, next) => {
|
||||
req.params.category = `genre/${req.params.genre}`;
|
||||
try {
|
||||
await getCategory(req, res, next);
|
||||
} catch (error) {
|
||||
jsonError(res, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/type/:type", async (req, res, next) => {
|
||||
req.params.category = `type/${req.params.type}`;
|
||||
try {
|
||||
await getCategory(req, res, next);
|
||||
} catch (error) {
|
||||
jsonError(res, error.message);
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/status/:status", async (req, res, next) => {
|
||||
req.params.category = `status/${req.params.status}`;
|
||||
try {
|
||||
await getCategory(req, res, next);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user