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.

GET - /api/info?id={anime-id} + /api/info?id={anime-slug}

Get detailed information about a specific anime

@@ -137,14 +137,14 @@
GET - /api/episodes/{anime-id} + /api/episodes/{anime-slug}

Get episode list for an anime

GET - /api/stream?id={episode-id}&server={server}&type={sub/dub} -

Get streaming information for an episode

+ /api/stream?id={episode-id} +

Get streaming servers for an episode

@@ -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 + }); } });