From 8269dcea8bd1b10c9f58f19b19e947964c9248a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=CA=9C=C9=AA=C9=B4=E1=B4=87=C9=AA=20N=E1=B4=8F=E1=B4=9C?= =?UTF-8?q?=E1=B4=A2=E1=B4=87=C9=B4?= <157171073+Shineii86@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:54:22 +0530 Subject: [PATCH] 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 --- CHANGELOG.md | 15 ++++ src/extractors/animeInfo.extractor.js | 109 +++++++++--------------- src/extractors/category.extractor.js | 17 ++-- src/extractors/episodeList.extractor.js | 34 +++++--- src/extractors/filter.extractor.js | 25 +++--- src/extractors/popular.extractor.js | 15 ++-- src/extractors/random.extractor.js | 37 ++++---- src/extractors/search.extractor.js | 34 +++++--- src/extractors/suggestion.extractor.js | 17 ++-- src/extractors/watchPage.extractor.js | 68 ++++++--------- src/routes/apiRoutes.js | 8 -- src/routes/category.route.js | 21 ++++- 12 files changed, 202 insertions(+), 198 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f322277..fc7805b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/extractors/animeInfo.extractor.js b/src/extractors/animeInfo.extractor.js index 4f69440..49ce20c 100644 --- a/src/extractors/animeInfo.extractor.js +++ b/src/extractors/animeInfo.extractor.js @@ -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; diff --git a/src/extractors/category.extractor.js b/src/extractors/category.extractor.js index 35b2a06..c8ca8d9 100644 --- a/src/extractors/category.extractor.js +++ b/src/extractors/category.extractor.js @@ -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"; @@ -17,25 +16,27 @@ const extractCategory = async (category, page = 1) => { } else { url = `${URLS.home}/${category}`; } - + const $ = await extractPages(url, page); 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, diff --git a/src/extractors/episodeList.extractor.js b/src/extractors/episodeList.extractor.js index 7a6dd8d..f0effae 100644 --- a/src/extractors/episodeList.extractor.js +++ b/src/extractors/episodeList.extractor.js @@ -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 }; diff --git a/src/extractors/filter.extractor.js b/src/extractors/filter.extractor.js index 7ce9aad..e94d149 100644 --- a/src/extractors/filter.extractor.js +++ b/src/extractors/filter.extractor.js @@ -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 }); } }); diff --git a/src/extractors/popular.extractor.js b/src/extractors/popular.extractor.js index e5b0f9e..5b5decd 100644 --- a/src/extractors/popular.extractor.js +++ b/src/extractors/popular.extractor.js @@ -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, diff --git a/src/extractors/random.extractor.js b/src/extractors/random.extractor.js index e83faad..5c89e9c 100644 --- a/src/extractors/random.extractor.js +++ b/src/extractors/random.extractor.js @@ -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; diff --git a/src/extractors/search.extractor.js b/src/extractors/search.extractor.js index a0fc8b8..f7f7e1d 100644 --- a/src/extractors/search.extractor.js +++ b/src/extractors/search.extractor.js @@ -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; } diff --git a/src/extractors/suggestion.extractor.js b/src/extractors/suggestion.extractor.js index 6b7586c..2c8644a 100644 --- a/src/extractors/suggestion.extractor.js +++ b/src/extractors/suggestion.extractor.js @@ -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; } diff --git a/src/extractors/watchPage.extractor.js b/src/extractors/watchPage.extractor.js index 32a88c5..cd839e0 100644 --- a/src/extractors/watchPage.extractor.js +++ b/src/extractors/watchPage.extractor.js @@ -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 diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 49d63c2..3ec8eb5 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -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); }; diff --git a/src/routes/category.route.js b/src/routes/category.route.js index f7c7921..6e9cf3c 100644 --- a/src/routes/category.route.js +++ b/src/routes/category.route.js @@ -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) {