From 1906ba4a34bc1817886bf5fc3e8a7bc7dc47cbcf 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:18:27 +0530
Subject: [PATCH] fix: update extractors to match actual anikototv.to structure
- Analyzed actual HTML structure of anikototv.to
- Updated all extractors with correct CSS selectors
- Changed ID-based to slug-based lookups
- Updated URL patterns to match actual website
- Fixed stream info to use AJAX server list
- Improved error handling
---
CHANGELOG.md | 22 +++-
public/index.html | 44 ++++++--
src/configs/dataUrl.js | 28 ++---
src/controllers/animeInfo.controller.js | 2 +-
src/controllers/episodeList.controller.js | 2 +-
src/controllers/streamInfo.controller.js | 6 +-
src/extractors/animeInfo.extractor.js | 64 ++++++-----
src/extractors/category.extractor.js | 39 ++++---
src/extractors/episodeList.extractor.js | 10 +-
src/extractors/filter.extractor.js | 45 ++++----
src/extractors/homeInfo.extractor.js | 129 +++++++++-------------
src/extractors/popular.extractor.js | 27 +++--
src/extractors/random.extractor.js | 10 +-
src/extractors/schedule.extractor.js | 16 +--
src/extractors/search.extractor.js | 39 ++++---
src/extractors/spotlight.extractor.js | 30 +++--
src/extractors/streamInfo.extractor.js | 43 +++-----
src/extractors/suggestion.extractor.js | 12 +-
src/extractors/topten.extractor.js | 56 +++++-----
src/extractors/trending.extractor.js | 28 +++--
20 files changed, 344 insertions(+), 308 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e3cfc7..cbdd886 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,11 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Initial project setup
- Express.js server with Vercel deployment support
- Home page API endpoint (`/api/`)
-- Anime info endpoint (`/api/info?id={anime-id}`)
+- Anime info endpoint (`/api/info?id={anime-slug}`)
- Search endpoint (`/api/search?keyword={keyword}`)
- Search suggestions endpoint (`/api/search/suggest?keyword={keyword}`)
-- Episode list endpoint (`/api/episodes/{anime-id}`)
-- Stream info endpoint (`/api/stream?id={episode-id}&server={server}&type={type}`)
+- Episode list endpoint (`/api/episodes/{anime-slug}`)
+- Stream info endpoint (`/api/stream?id={episode-id}`)
- Schedule endpoint (`/api/schedule?date={YYYY-MM-DD}`)
- Spotlight endpoint (`/api/spotlight`)
- Trending endpoint (`/api/trending`)
@@ -32,4 +32,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dockerfile for containerization
- Render.yaml for Render deployment
- MIT License
-- README with API documentation
\ No newline at end of file
+- README with API documentation
+
+### Changed
+- Updated all extractors to match actual anikototv.to HTML structure
+- Changed ID-based lookups to slug-based lookups
+- Updated URL patterns to match actual website structure
+- Improved CSS selectors for better accuracy
+
+## [1.1.0] - 2024-12-19
+
+### Fixed
+- Corrected HTML selectors based on actual website analysis
+- Updated episode list extraction
+- Fixed stream info endpoint to use AJAX server list
+- Improved error handling
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index a020e24..b60e493 100644
--- a/public/index.html
+++ b/public/index.html
@@ -114,12 +114,12 @@
GET
/api/
-
Get home page data including spotlight, trending, top airing, etc.
+
Get home page data including spotlight, trending, top anime, etc.
@@ -192,20 +192,48 @@
GET
/api/filter?{params}
-
Filter anime by type, status, genre, etc.
+
Filter anime by genre, type, status, etc.
GET
/api/{category}?page={page}
-
Get anime by category (movie, tv, ova, ona, etc.)
+
Get anime by category (genre/action, type/tv, etc.)
+
+
+
+
+
URL Structure
+
+ Anime: /watch/{anime-slug}
+
+
+ Episode: /watch/{anime-slug}/ep-{number}
+
+
+ Search: /filter?keyword={query}
+
+
+ Genre: /genre/{genre-slug}
+
+
+ Type: /type/{type-slug}
+
+
+ Status: /status/{status-slug}
Categories
- top-airing, most-popular, most-favorite, completed, recently-updated, recently-added, subbed-anime, dubbed-anime, movie, tv, ova, ona, special, music
+ Genres: action, adventure, cars, comedy, dementia, demons, drama, ecchi, fantasy, game, harem, historical, horror, isekai, josei, kids, magic, mahou-shoujo, martial-arts, mecha, military, music, mystery, parody, police, psychological, romance, samurai, school, sci-fi, seinen, shoujo, shoujo-ai, shounen, shounen-ai, slice-of-life, space, sports, super-power, supernatural, thriller, unknown, vampire
+
+
+ Types: movie, music, ona, ova, special, tv
+
+
+ Status: currently-airing, finished-airing, not-yet-aired
diff --git a/src/configs/dataUrl.js b/src/configs/dataUrl.js
index 6458939..869db39 100644
--- a/src/configs/dataUrl.js
+++ b/src/configs/dataUrl.js
@@ -9,26 +9,18 @@ const ALT_DOMAINS = [
const URLS = {
home: `${BASE_URL}/home`,
- search: `${BASE_URL}/search`,
+ search: `${BASE_URL}/filter`,
genre: (genre) => `${BASE_URL}/genre/${genre}`,
+ type: (type) => `${BASE_URL}/type/${type}`,
+ status: (status) => `${BASE_URL}/status/${status}`,
azList: (letter) => `${BASE_URL}/az-list/${letter}`,
- category: (cat) => `${BASE_URL}/${cat}`,
- anime: (id) => `${BASE_URL}/anime/${id}`,
- episode: (id) => `${BASE_URL}/episode/${id}`,
- schedule: (date) => `${BASE_URL}/schedule/${date}`,
- topTen: `${BASE_URL}/top-ten`,
- mostPopular: `${BASE_URL}/most-popular`,
- mostFavorite: `${BASE_URL}/most-favorite`,
- recentlyUpdated: `${BASE_URL}/recently-updated`,
- recentlyAdded: `${BASE_URL}/recently-added`,
- subbedAnime: `${BASE_URL}/subbed-anime`,
- dubbedAnime: `${BASE_URL}/dubbed-anime`,
- movie: `${BASE_URL}/movie`,
- ova: `${BASE_URL}/ova`,
- ona: `${BASE_URL}/ona`,
- special: `${BASE_URL}/special`,
- tv: `${BASE_URL}/tv`,
- music: `${BASE_URL}/music`
+ watch: (slug) => `${BASE_URL}/watch/${slug}`,
+ episode: (slug, ep) => `${BASE_URL}/watch/${slug}/ep-${ep}`,
+ latestUpdated: `${BASE_URL}/latest-updated`,
+ newRelease: `${BASE_URL}/new-release`,
+ mostViewed: `${BASE_URL}/most-viewed`,
+ random: `${BASE_URL}/random`,
+ serverList: `${BASE_URL}/ajax/server/list`
};
export { BASE_URL, ALT_DOMAINS, URLS };
\ No newline at end of file
diff --git a/src/controllers/animeInfo.controller.js b/src/controllers/animeInfo.controller.js
index b6c265e..a6ffb83 100644
--- a/src/controllers/animeInfo.controller.js
+++ b/src/controllers/animeInfo.controller.js
@@ -5,7 +5,7 @@ const getAnimeInfo = async (req, res, next) => {
try {
const { id } = req.query;
if (!id) {
- return res.status(400).json({ success: false, message: "Anime ID is required" });
+ return res.status(400).json({ success: false, message: "Anime slug is required" });
}
const cacheKey = `anime_${id}`;
diff --git a/src/controllers/episodeList.controller.js b/src/controllers/episodeList.controller.js
index 3feb520..04ae8c2 100644
--- a/src/controllers/episodeList.controller.js
+++ b/src/controllers/episodeList.controller.js
@@ -6,7 +6,7 @@ const getEpisodeList = async (req, res, next) => {
const { id } = req.params;
if (!id) {
- return res.status(400).json({ success: false, message: "Anime ID is required" });
+ return res.status(400).json({ success: false, message: "Anime slug is required" });
}
const cacheKey = `episodes_${id}`;
diff --git a/src/controllers/streamInfo.controller.js b/src/controllers/streamInfo.controller.js
index c13c812..7aa9a2e 100644
--- a/src/controllers/streamInfo.controller.js
+++ b/src/controllers/streamInfo.controller.js
@@ -3,19 +3,19 @@ import { getCache, setCache } from "../helper/cache.helper.js";
const getStreamInfo = async (req, res, next) => {
try {
- const { id, server = "hd-1", type = "sub" } = req.query;
+ const { id } = req.query;
if (!id) {
return res.status(400).json({ success: false, message: "Episode ID is required" });
}
- const cacheKey = `stream_${id}_${server}_${type}`;
+ const cacheKey = `stream_${id}`;
const cached = getCache(cacheKey);
if (cached) {
return res.json({ success: true, results: cached });
}
- const data = await extractStreamInfo(id, server, type);
+ const data = await extractStreamInfo(id);
setCache(cacheKey, data);
res.json({ success: true, results: data });
} catch (error) {
diff --git a/src/extractors/animeInfo.extractor.js b/src/extractors/animeInfo.extractor.js
index 59a21a7..4f69440 100644
--- a/src/extractors/animeInfo.extractor.js
+++ b/src/extractors/animeInfo.extractor.js
@@ -1,19 +1,20 @@
import * as cheerio from "cheerio";
import axios from "axios";
import { headers } from "../configs/header.config.js";
-import { BASE_URL } from "../configs/dataUrl.js";
+import { URLS } from "../configs/dataUrl.js";
-const extractAnimeInfo = async (id) => {
+const extractAnimeInfo = async (slug) => {
try {
- const url = `${BASE_URL}/anime/${id}`;
+ const url = URLS.watch(slug);
const { data } = await axios.get(url, { headers });
const $ = cheerio.load(data);
- 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 showType = $(".film-info .row .col1 .item:first-child .name").text().trim() || "";
- const quality = $(".film-info .row .col1 .item:nth-child(2) .name").text().trim() || "";
+ 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 animeInfo = {};
$(".anisc-info .item").each((i, el) => {
@@ -32,66 +33,63 @@ const extractAnimeInfo = async (id) => {
if (genre) genres.push(genre);
});
- const seasons = [];
- $(".seasons .season-item, .ss-list a").each((i, el) => {
- const seasonId = $(el).attr("href")?.split("/").pop() || "";
- const seasonTitle = $(el).text().trim() || "";
- const seasonPoster = $(el).find("img").attr("src") || "";
+ 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 (seasonId) {
- seasons.push({
- id: seasonId,
- title: seasonTitle,
- poster: seasonPoster
+ 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 relId = $(el).find("a").attr("href")?.split("/").pop() || "";
+ 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() || "";
- const relType = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
- if (relId) {
+ if (relSlug) {
relatedData.push({
- id: relId,
+ slug: relSlug,
poster: relPoster,
- title: relTitle,
- type: relType
+ title: relTitle
});
}
});
const recommendedData = [];
$(".recommend-content .film-detail, .film-list-block .film-detail").each((i, el) => {
- const recId = $(el).find("a").attr("href")?.split("/").pop() || "";
+ 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() || "";
- const recType = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
- if (recId) {
+ if (recSlug) {
recommendedData.push({
- id: recId,
+ slug: recSlug,
poster: recPoster,
- title: recTitle,
- type: recType
+ title: recTitle
});
}
});
return {
- id,
+ slug,
title,
japaneseTitle,
poster,
- showType,
+ type,
+ rating,
quality,
overview,
animeInfo,
genres,
- seasons,
+ episodes,
relatedData,
recommendedData
};
diff --git a/src/extractors/category.extractor.js b/src/extractors/category.extractor.js
index c261b8f..35b2a06 100644
--- a/src/extractors/category.extractor.js
+++ b/src/extractors/category.extractor.js
@@ -7,30 +7,43 @@ import { extractPages } from "../helper/extractPages.helper.js";
const extractCategory = async (category, page = 1) => {
try {
- const url = URLS.category(category);
+ let url;
+ if (category.startsWith("genre/")) {
+ url = URLS.genre(category.replace("genre/", ""));
+ } else if (category.startsWith("type/")) {
+ url = URLS.type(category.replace("type/", ""));
+ } else if (category.startsWith("status/")) {
+ url = URLS.status(category.replace("status/", ""));
+ } 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) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
+ const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
- const showType = $(el).find(".fd-infor .fdi-item:nth-child(2)").text().trim() || "";
- const sub = parseInt($(el).find(".tick-sub").text().trim()) || 0;
- const dub = parseInt($(el).find(".tick-dub").text().trim()) || 0;
- const eps = parseInt($(el).find(".tick-eps").text().trim()) || 0;
- const adultContent = $(el).find(".tick-adult, .adult-badge").length > 0;
+ const title = $(el).find(".film-name a, .name.d-title").text().trim() || "";
+ const japaneseTitle = $(el).find(".name.d-title").attr("data-jp") || "";
+ 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() || "";
- if (id) {
+ if (slug) {
results.push({
- id,
+ slug,
poster,
title,
japaneseTitle,
- tvInfo: { showType, sub, dub, eps },
- adultContent
+ sub,
+ dub,
+ total,
+ type,
+ rating
});
}
});
diff --git a/src/extractors/episodeList.extractor.js b/src/extractors/episodeList.extractor.js
index d3343c2..7a6dd8d 100644
--- a/src/extractors/episodeList.extractor.js
+++ b/src/extractors/episodeList.extractor.js
@@ -1,20 +1,19 @@
import * as cheerio from "cheerio";
import axios from "axios";
import { headers } from "../configs/header.config.js";
-import { BASE_URL } from "../configs/dataUrl.js";
+import { URLS } from "../configs/dataUrl.js";
-const extractEpisodeList = async (id) => {
+const extractEpisodeList = async (slug) => {
try {
- const url = `${BASE_URL}/anime/${id}`;
+ const url = URLS.watch(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("/").pop() || "";
+ 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 japaneseTitle = $(el).find(".ep-jp").text().trim() || "";
const filler = $(el).hasClass("filler") || false;
if (epId) {
@@ -22,7 +21,6 @@ const extractEpisodeList = async (id) => {
id: epId,
episode_no: epNumber,
title,
- japaneseTitle,
filler
});
}
diff --git a/src/extractors/filter.extractor.js b/src/extractors/filter.extractor.js
index 8244fbf..05a985f 100644
--- a/src/extractors/filter.extractor.js
+++ b/src/extractors/filter.extractor.js
@@ -1,7 +1,7 @@
import * as cheerio from "cheerio";
import axios from "axios";
import { headers } from "../configs/header.config.js";
-import { BASE_URL } from "../configs/dataUrl.js";
+import { URLS } from "../configs/dataUrl.js";
import { countPages } from "../helper/countPages.helper.js";
import { extractPages } from "../helper/extractPages.helper.js";
@@ -9,44 +9,43 @@ const extractFilter = async (params) => {
try {
const queryParams = new URLSearchParams();
+ if (params.keyword) queryParams.set("keyword", params.keyword);
+ if (params.genre) queryParams.set("genre", params.genre);
+ if (params.season) queryParams.set("season", params.season);
+ if (params.year) queryParams.set("year", params.year);
if (params.type) queryParams.set("type", params.type);
if (params.status) queryParams.set("status", params.status);
- if (params.rated) queryParams.set("rated", params.rated);
- if (params.score) queryParams.set("score", params.score);
- if (params.season) queryParams.set("season", params.season);
if (params.language) queryParams.set("language", params.language);
- if (params.genres) queryParams.set("genres", params.genres);
+ if (params.rating) queryParams.set("rating", params.rating);
if (params.sort) queryParams.set("sort", params.sort);
- if (params.sy) queryParams.set("sy", params.sy);
- if (params.sm) queryParams.set("sm", params.sm);
- if (params.sd) queryParams.set("sd", params.sd);
- if (params.ey) queryParams.set("ey", params.ey);
- if (params.em) queryParams.set("em", params.em);
- if (params.ed) queryParams.set("ed", params.ed);
- if (params.keyword) queryParams.set("keyword", params.keyword);
- const url = `${BASE_URL}/filter?${queryParams.toString()}`;
+ const url = `${URLS.search}?${queryParams.toString()}`;
const $ = await extractPages(url, params.page || 1);
const totalPages = countPages($);
const results = [];
$(".film_list-wrap .flw-item, .film-detail").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
+ const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
- const showType = $(el).find(".fd-infor .fdi-item:nth-child(2)").text().trim() || "";
- const sub = parseInt($(el).find(".tick-sub").text().trim()) || 0;
- const dub = parseInt($(el).find(".tick-dub").text().trim()) || 0;
- const eps = parseInt($(el).find(".tick-eps").text().trim()) || 0;
+ const title = $(el).find(".film-name a, .name.d-title").text().trim() || "";
+ const japaneseTitle = $(el).find(".name.d-title").attr("data-jp") || "";
+ 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() || "";
- if (id) {
+ if (slug) {
results.push({
- id,
+ slug,
poster,
title,
japaneseTitle,
- tvInfo: { showType, sub, dub, eps }
+ sub,
+ dub,
+ total,
+ type,
+ rating
});
}
});
diff --git a/src/extractors/homeInfo.extractor.js b/src/extractors/homeInfo.extractor.js
index d06b75f..cdc2c0b 100644
--- a/src/extractors/homeInfo.extractor.js
+++ b/src/extractors/homeInfo.extractor.js
@@ -9,111 +9,86 @@ const extractHomeInfo = async () => {
const $ = cheerio.load(data);
const spotlights = [];
- $(".swiper-slide, .spotlight-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fdi-item:nth-child(1)").text().trim() || "";
- const description = $(el).find(".fd-infor .desc, .film-description").text().trim() || "";
- const showType = $(el).find(".fdi-item:nth-child(2)").text().trim() || "";
- const duration = $(el).find(".fdi-item:nth-child(3)").text().trim() || "";
- const releaseDate = $(el).find(".fdi-item:nth-child(4)").text().trim() || "";
+ $("#hotest .swiper-slide.item").each((i, el) => {
+ const slug = $(el).find("a.play").attr("href")?.split("/watch/").pop() || "";
+ const poster = $(el).find(".image div").attr("style")?.match(/url\(['"]?(.+?)['"]?\)/)?.[1] || "";
+ const title = $(el).find("h2.title.d-title").text().trim() || "";
+ const japaneseTitle = $(el).find("h2.title.d-title").attr("data-jp") || "";
+ const description = $(el).find(".desc").text().trim() || "";
+ const rating = $(el).find("i.rating").text().trim() || "";
+ const quality = $(el).find("i.quality").text().trim() || "";
+ const sub = parseInt($(el).find("i.sub").text().trim()) || 0;
+ const dub = parseInt($(el).find("i.dub").text().trim()) || 0;
+ const date = $(el).find("i.date").text().trim() || "";
- if (id) {
+ if (slug) {
spotlights.push({
- id,
+ slug,
poster,
title,
japaneseTitle,
description,
- tvInfo: { showType, duration, releaseDate }
+ rating,
+ quality,
+ sub,
+ dub,
+ date
});
}
});
const trending = [];
- $(".trending-list .film-detail, .trending-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const number = parseInt($(el).find(".number").text().trim()) || i + 1;
+ $(".section-updated .item, #recent-update .item").each((i, el) => {
+ const slug = $(el).find("a.name.d-title").attr("href")?.split("/watch/").pop() || "";
+ const poster = $(el).find(".poster img").attr("src") || "";
+ const title = $(el).find("a.name.d-title").text().trim() || "";
+ const japaneseTitle = $(el).find("a.name.d-title").attr("data-jp") || "";
+ 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").text().trim() || "";
- if (id) {
- trending.push({ id, number, poster, title });
+ if (slug) {
+ trending.push({
+ slug,
+ poster,
+ title,
+ japaneseTitle,
+ sub,
+ dub,
+ total,
+ type
+ });
}
});
const topAiring = [];
- $(".section-id-02 .film-detail, .top-airing .film-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
+ $("#top-anime .tab-content[data-name='day'] a.item").each((i, el) => {
+ const slug = $(el).attr("href")?.split("/watch/").pop() || "";
+ const poster = $(el).find(".poster img").attr("src") || "";
+ const title = $(el).find(".name").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;
+ const type = $(el).find(".type").text().trim() || "";
- if (id) {
- topAiring.push({ id, poster, title, japaneseTitle });
- }
- });
-
- const mostPopular = [];
- $(".section-id-03 .film-detail, .popular .film-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
-
- if (id) {
- mostPopular.push({ id, poster, title });
- }
- });
-
- const mostFavorite = [];
- $(".section-id-04 .film-detail, .favorite .film-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
-
- if (id) {
- mostFavorite.push({ id, poster, title });
- }
- });
-
- const latestCompleted = [];
- $(".section-id-05 .film-detail, .completed .film-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
-
- if (id) {
- latestCompleted.push({ id, poster, title });
- }
- });
-
- const latestEpisode = [];
- $(".section-id-06 .film-detail, .latest .film-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const episode = $(el).find(".tick-eps, .tick-sub").text().trim() || "";
-
- if (id) {
- latestEpisode.push({ id, poster, title, episode });
+ if (slug) {
+ topAiring.push({ slug, poster, title, sub, dub, type });
}
});
const genres = [];
- $(".genre-item, .genres a").each((i, el) => {
+ $("#menu ul.c4 li a").each((i, el) => {
const genre = $(el).text().trim();
- if (genre) genres.push(genre);
+ const href = $(el).attr("href") || "";
+ if (genre && href.includes("/genre/")) {
+ genres.push(genre);
+ }
});
return {
spotlights,
trending,
topAiring,
- mostPopular,
- mostFavorite,
- latestCompleted,
- latestEpisode,
genres
};
} catch (error) {
diff --git a/src/extractors/popular.extractor.js b/src/extractors/popular.extractor.js
index 2e073fb..e5b0f9e 100644
--- a/src/extractors/popular.extractor.js
+++ b/src/extractors/popular.extractor.js
@@ -7,27 +7,32 @@ import { extractPages } from "../helper/extractPages.helper.js";
const extractPopular = async (page = 1) => {
try {
- const $ = await extractPages(URLS.mostPopular, page);
+ const $ = await extractPages(URLS.mostViewed, page);
const totalPages = countPages($);
const results = [];
$(".film_list-wrap .flw-item, .film-detail").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
+ const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
- const showType = $(el).find(".fd-infor .fdi-item:nth-child(2)").text().trim() || "";
- const sub = parseInt($(el).find(".tick-sub").text().trim()) || 0;
- const dub = parseInt($(el).find(".tick-dub").text().trim()) || 0;
- const eps = parseInt($(el).find(".tick-eps").text().trim()) || 0;
+ const title = $(el).find(".film-name a, .name.d-title").text().trim() || "";
+ const japaneseTitle = $(el).find(".name.d-title").attr("data-jp") || "";
+ 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() || "";
- if (id) {
+ if (slug) {
results.push({
- id,
+ slug,
poster,
title,
japaneseTitle,
- tvInfo: { showType, sub, dub, eps }
+ sub,
+ dub,
+ total,
+ type,
+ rating
});
}
});
diff --git a/src/extractors/random.extractor.js b/src/extractors/random.extractor.js
index 89293fb..e83faad 100644
--- a/src/extractors/random.extractor.js
+++ b/src/extractors/random.extractor.js
@@ -1,20 +1,21 @@
import * as cheerio from "cheerio";
import axios from "axios";
import { headers } from "../configs/header.config.js";
-import { BASE_URL } from "../configs/dataUrl.js";
+import { URLS } from "../configs/dataUrl.js";
const extractRandom = async () => {
try {
- const { data } = await axios.get(`${BASE_URL}/random`, {
+ const { data } = await axios.get(URLS.random, {
headers,
maxRedirects: 5
});
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 showType = $(".film-info .row .col1 .item:first-child .name").text().trim() || "";
+ const type = $(".film-info .row .col1 .item:first-child .name").text().trim() || "";
const animeInfo = {};
$(".anisc-info .item").each((i, el) => {
@@ -26,10 +27,11 @@ const extractRandom = async () => {
});
return {
+ slug,
title,
japaneseTitle,
poster,
- showType,
+ type,
animeInfo
};
} catch (error) {
diff --git a/src/extractors/schedule.extractor.js b/src/extractors/schedule.extractor.js
index 2703090..c10e948 100644
--- a/src/extractors/schedule.extractor.js
+++ b/src/extractors/schedule.extractor.js
@@ -5,27 +5,23 @@ import { URLS } from "../configs/dataUrl.js";
const extractSchedule = async (date) => {
try {
- const url = URLS.schedule(date);
+ const url = `${URLS.home}?date=${date}`;
const { data } = await axios.get(url, { headers });
const $ = cheerio.load(data);
const schedule = [];
$(".schedule-item, .anime-schedule .item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
+ const slug = $(el).find("a").attr("href")?.split("/watch/").pop() || "";
+ const title = $(el).find(".film-name a, .name").text().trim() || "";
const time = $(el).find(".time, .schedule-time").text().trim() || "";
const episodeNo = parseInt($(el).find(".episode-no, .ep-num").text().trim()) || 0;
- const releaseDate = $(el).find(".date, .release-date").text().trim() || "";
- if (id) {
+ if (slug) {
schedule.push({
- id,
+ slug,
title,
- japaneseTitle,
time,
- episode_no: episodeNo,
- releaseDate
+ episode_no: episodeNo
});
}
});
diff --git a/src/extractors/search.extractor.js b/src/extractors/search.extractor.js
index bf0251c..a0fc8b8 100644
--- a/src/extractors/search.extractor.js
+++ b/src/extractors/search.extractor.js
@@ -2,9 +2,8 @@ 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";
-
import { extractPages } from "../helper/extractPages.helper.js";
+import { countPages } from "../helper/countPages.helper.js";
const extractSearchResults = async (keyword, page = 1) => {
try {
@@ -13,23 +12,28 @@ const extractSearchResults = async (keyword, page = 1) => {
const totalPages = countPages($);
const results = [];
- $(".film_list-wrap .flw-item, .search-list .film-detail").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
+ $(".film_list-wrap .flw-item, .film-detail").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, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
- const showType = $(el).find(".fd-infor .fdi-item:nth-child(2)").text().trim() || "";
- const sub = parseInt($(el).find(".tick-sub").text().trim()) || 0;
- const dub = parseInt($(el).find(".tick-dub").text().trim()) || 0;
- const eps = parseInt($(el).find(".tick-eps").text().trim()) || 0;
+ const title = $(el).find(".film-name a, .name.d-title").text().trim() || "";
+ const japaneseTitle = $(el).find(".name.d-title").attr("data-jp") || "";
+ 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() || "";
- if (id) {
+ if (slug) {
results.push({
- id,
+ slug,
poster,
title,
japaneseTitle,
- tvInfo: { showType, sub, dub, eps }
+ sub,
+ dub,
+ total,
+ type,
+ rating
});
}
});
@@ -48,13 +52,12 @@ const extractSearchSuggestions = async (keyword) => {
const suggestions = [];
$(".search-suggest .film-detail, .nav-item a").each((i, el) => {
- const id = $(el).attr("href")?.split("/").pop() || "";
+ const slug = $(el).attr("href")?.split("/watch/").pop() || "";
const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name, .dynamic-name").text().trim() || "";
- const showType = $(el).find(".fdi-item:first-child").text().trim() || "";
+ const title = $(el).find(".film-name, .name").text().trim() || "";
- if (id) {
- suggestions.push({ id, poster, title, showType });
+ if (slug) {
+ suggestions.push({ slug, poster, title });
}
});
diff --git a/src/extractors/spotlight.extractor.js b/src/extractors/spotlight.extractor.js
index d4dd6a8..25e369c 100644
--- a/src/extractors/spotlight.extractor.js
+++ b/src/extractors/spotlight.extractor.js
@@ -9,24 +9,30 @@ const extractSpotlight = async () => {
const $ = cheerio.load(data);
const spotlights = [];
- $(".swiper-slide, .spotlight-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fdi-item:nth-child(1)").text().trim() || "";
- const description = $(el).find(".fd-infor .desc, .film-description").text().trim() || "";
- const showType = $(el).find(".fdi-item:nth-child(2)").text().trim() || "";
- const duration = $(el).find(".fdi-item:nth-child(3)").text().trim() || "";
- const releaseDate = $(el).find(".fdi-item:nth-child(4)").text().trim() || "";
+ $("#hotest .swiper-slide.item").each((i, el) => {
+ const slug = $(el).find("a.play").attr("href")?.split("/watch/").pop() || "";
+ const poster = $(el).find(".image div").attr("style")?.match(/url\(['"]?(.+?)['"]?\)/)?.[1] || "";
+ const title = $(el).find("h2.title.d-title").text().trim() || "";
+ const japaneseTitle = $(el).find("h2.title.d-title").attr("data-jp") || "";
+ const description = $(el).find(".desc").text().trim() || "";
+ const rating = $(el).find("i.rating").text().trim() || "";
+ const quality = $(el).find("i.quality").text().trim() || "";
+ const sub = parseInt($(el).find("i.sub").text().trim()) || 0;
+ const dub = parseInt($(el).find("i.dub").text().trim()) || 0;
+ const date = $(el).find("i.date").text().trim() || "";
- if (id) {
+ if (slug) {
spotlights.push({
- id,
+ slug,
poster,
title,
japaneseTitle,
description,
- tvInfo: { showType, duration, releaseDate }
+ rating,
+ quality,
+ sub,
+ dub,
+ date
});
}
});
diff --git a/src/extractors/streamInfo.extractor.js b/src/extractors/streamInfo.extractor.js
index 6d70eab..c98ca79 100644
--- a/src/extractors/streamInfo.extractor.js
+++ b/src/extractors/streamInfo.extractor.js
@@ -1,12 +1,18 @@
import * as cheerio from "cheerio";
import axios from "axios";
import { headers } from "../configs/header.config.js";
-import { BASE_URL } from "../configs/dataUrl.js";
+import { URLS } from "../configs/dataUrl.js";
-const extractStreamInfo = async (episodeId, server = "hd-1", type = "sub") => {
+const extractStreamInfo = async (episodeId) => {
try {
- const url = `${BASE_URL}/watch/${episodeId}`;
- const { data } = await axios.get(url, { headers });
+ const url = `${URLS.serverList}?episodeId=${episodeId}`;
+ const { data } = await axios.get(url, {
+ headers: {
+ ...headers,
+ "X-Requested-With": "XMLHttpRequest"
+ }
+ });
+
const $ = cheerio.load(data);
const servers = [];
@@ -20,35 +26,20 @@ const extractStreamInfo = async (episodeId, server = "hd-1", type = "sub") => {
}
});
- const sources = [];
- $(".player-source .source-item, .embed-source").each((i, el) => {
- const sourceId = $(el).attr("data-id") || "";
- const sourceUrl = $(el).attr("data-src") || $(el).find("iframe").attr("src") || "";
- const sourceType = $(el).attr("data-type") || "hls";
+ const links = [];
+ $("li[data-link-id]").each((i, el) => {
+ const linkId = $(el).attr("data-link-id") || "";
+ const linkType = $(el).attr("data-type") || "hls";
- if (sourceId || sourceUrl) {
- sources.push({ id: sourceId, url: sourceUrl, type: sourceType });
- }
- });
-
- const subtitles = [];
- $(".player-sub .sub-item, .subtitle-item").each((i, el) => {
- const subUrl = $(el).attr("data-url") || $(el).attr("src") || "";
- const subLabel = $(el).text().trim() || "";
- const subLang = $(el).attr("data-lang") || "en";
-
- if (subUrl) {
- subtitles.push({ url: subUrl, label: subLabel, language: subLang });
+ if (linkId) {
+ links.push({ id: linkId, type: linkType });
}
});
return {
episodeId,
- server,
- type,
servers,
- sources,
- subtitles
+ links
};
} catch (error) {
throw error;
diff --git a/src/extractors/suggestion.extractor.js b/src/extractors/suggestion.extractor.js
index d6ab880..6b7586c 100644
--- a/src/extractors/suggestion.extractor.js
+++ b/src/extractors/suggestion.extractor.js
@@ -11,15 +11,13 @@ const extractSuggestions = async (keyword) => {
const suggestions = [];
$(".search-suggest .film-detail, .nav-item a").each((i, el) => {
- const id = $(el).attr("href")?.split("/").pop() || "";
+ const slug = $(el).attr("href")?.split("/watch/").pop() || "";
const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
- const showType = $(el).find(".fdi-item:first-child").text().trim() || "";
- const releaseDate = $(el).find(".fdi-item:nth-child(2)").text().trim() || "";
+ const title = $(el).find(".film-name, .name").text().trim() || "";
+ const type = $(el).find(".fdi-item:first-child").text().trim() || "";
- if (id) {
- suggestions.push({ id, poster, title, japaneseTitle, showType, releaseDate });
+ if (slug) {
+ suggestions.push({ slug, poster, title, type });
}
});
diff --git a/src/extractors/topten.extractor.js b/src/extractors/topten.extractor.js
index 1a28f76..d6c25f7 100644
--- a/src/extractors/topten.extractor.js
+++ b/src/extractors/topten.extractor.js
@@ -5,46 +5,52 @@ import { URLS } from "../configs/dataUrl.js";
const extractTopTen = async () => {
try {
- const { data } = await axios.get(URLS.topTen, { headers });
+ const { data } = await axios.get(URLS.home, { headers });
const $ = cheerio.load(data);
const today = [];
const week = [];
const month = [];
- $(".top-ten-list.today .top-ten-item, #top-viewed-day .top-list-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const name = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const number = parseInt($(el).find(".number").text().trim()) || i + 1;
- const showType = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
+ $("#top-anime .tab-content[data-name='day'] a.item").each((i, el) => {
+ const slug = $(el).attr("href")?.split("/watch/").pop() || "";
+ const poster = $(el).find(".poster img").attr("src") || "";
+ const name = $(el).find(".name").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;
+ const type = $(el).find(".type").text().trim() || "";
+ const rank = i + 1;
- if (id) {
- today.push({ id, number, name, poster, tvInfo: { showType } });
+ if (slug) {
+ today.push({ slug, rank, name, poster, sub, dub, type });
}
});
- $(".top-ten-list.week .top-ten-item, #top-viewed-week .top-list-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const name = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const number = parseInt($(el).find(".number").text().trim()) || i + 1;
- const showType = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
+ $("#top-anime .tab-content[data-name='week'] a.item").each((i, el) => {
+ const slug = $(el).attr("href")?.split("/watch/").pop() || "";
+ const poster = $(el).find(".poster img").attr("src") || "";
+ const name = $(el).find(".name").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;
+ const type = $(el).find(".type").text().trim() || "";
+ const rank = i + 1;
- if (id) {
- week.push({ id, number, name, poster, tvInfo: { showType } });
+ if (slug) {
+ week.push({ slug, rank, name, poster, sub, dub, type });
}
});
- $(".top-ten-list.month .top-ten-item, #top-viewed-month .top-list-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const name = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const number = parseInt($(el).find(".number").text().trim()) || i + 1;
- const showType = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
+ $("#top-anime .tab-content[data-name='month'] a.item").each((i, el) => {
+ const slug = $(el).attr("href")?.split("/watch/").pop() || "";
+ const poster = $(el).find(".poster img").attr("src") || "";
+ const name = $(el).find(".name").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;
+ const type = $(el).find(".type").text().trim() || "";
+ const rank = i + 1;
- if (id) {
- month.push({ id, number, name, poster, tvInfo: { showType } });
+ if (slug) {
+ month.push({ slug, rank, name, poster, sub, dub, type });
}
});
diff --git a/src/extractors/trending.extractor.js b/src/extractors/trending.extractor.js
index 7ecdc4c..fb88a5d 100644
--- a/src/extractors/trending.extractor.js
+++ b/src/extractors/trending.extractor.js
@@ -9,15 +9,27 @@ const extractTrending = async () => {
const $ = cheerio.load(data);
const trending = [];
- $(".trending-list .film-detail, .trending-item").each((i, el) => {
- const id = $(el).find("a").attr("href")?.split("/").pop() || "";
- const poster = $(el).find("img").attr("src") || "";
- const title = $(el).find(".film-name a, .dynamic-name").text().trim() || "";
- const japaneseTitle = $(el).find(".fd-infor .fdi-item:first-child").text().trim() || "";
- const number = parseInt($(el).find(".number").text().trim()) || i + 1;
+ $(".section-updated .item, #recent-update .item").each((i, el) => {
+ const slug = $(el).find("a.name.d-title").attr("href")?.split("/watch/").pop() || "";
+ const poster = $(el).find(".poster img").attr("src") || "";
+ const title = $(el).find("a.name.d-title").text().trim() || "";
+ const japaneseTitle = $(el).find("a.name.d-title").attr("data-jp") || "";
+ 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").text().trim() || "";
- if (id) {
- trending.push({ id, number, poster, title, japaneseTitle });
+ if (slug) {
+ trending.push({
+ slug,
+ poster,
+ title,
+ japaneseTitle,
+ sub,
+ dub,
+ total,
+ type
+ });
}
});