UNPKG

aniwatch

Version:

📦 A scraper package serving anime information from hianimez.to

1,549 lines (1,525 loc) • 74.7 kB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/hianime/hianime.ts var hianime_exports = {}; __export(hianime_exports, { AZ_LIST_SORT_OPTIONS: () => AZ_LIST_SORT_OPTIONS, SEARCH_PAGE_FILTERS: () => SEARCH_PAGE_FILTERS, Scraper: () => Scraper, Servers: () => Servers }); // src/hianime/scrapers/homePage.ts import { load } from "cheerio"; // src/config/client.ts import axios, { AxiosError } from "axios"; // src/utils/constants.ts var ACCEPT_ENCODING_HEADER = "gzip, deflate, br"; var USER_AGENT_HEADER = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"; var ACCEPT_HEADER = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"; var DOMAIN = "hianimez.to"; var SRC_BASE_URL = `https://${DOMAIN}`; var SRC_AJAX_URL = `${SRC_BASE_URL}/ajax`; var SRC_HOME_URL = `${SRC_BASE_URL}/home`; var SRC_SEARCH_URL = `${SRC_BASE_URL}/search`; var SEARCH_PAGE_FILTERS = { GENRES_ID_MAP: { action: 1, adventure: 2, cars: 3, comedy: 4, dementia: 5, demons: 6, drama: 8, ecchi: 9, fantasy: 10, game: 11, harem: 35, historical: 13, horror: 14, isekai: 44, josei: 43, kids: 15, magic: 16, "martial-arts": 17, mecha: 18, military: 38, music: 19, mystery: 7, parody: 20, police: 39, psychological: 40, romance: 22, samurai: 21, school: 23, "sci-fi": 24, seinen: 42, shoujo: 25, "shoujo-ai": 26, shounen: 27, "shounen-ai": 28, "slice-of-life": 36, space: 29, sports: 30, "super-power": 31, supernatural: 37, thriller: 41, vampire: 32 }, TYPE_ID_MAP: { all: 0, movie: 1, tv: 2, ova: 3, ona: 4, special: 5, music: 6 }, STATUS_ID_MAP: { all: 0, "finished-airing": 1, "currently-airing": 2, "not-yet-aired": 3 }, RATED_ID_MAP: { all: 0, g: 1, pg: 2, "pg-13": 3, r: 4, "r+": 5, rx: 6 }, SCORE_ID_MAP: { all: 0, appalling: 1, horrible: 2, "very-bad": 3, bad: 4, average: 5, fine: 6, good: 7, "very-good": 8, great: 9, masterpiece: 10 }, SEASON_ID_MAP: { all: 0, spring: 1, summer: 2, fall: 3, winter: 4 }, LANGUAGE_ID_MAP: { all: 0, sub: 1, dub: 2, "sub-&-dub": 3 }, SORT_ID_MAP: { default: "default", "recently-added": "recently_added", "recently-updated": "recently_updated", score: "score", "name-a-z": "name_az", "released-date": "released_date", "most-watched": "most_watched" } }; var AZ_LIST_SORT_OPTIONS = { all: true, other: true, "0-9": true, a: true, b: true, c: true, d: true, e: true, f: true, g: true, h: true, i: true, j: true, k: true, l: true, m: true, n: true, o: true, p: true, q: true, r: true, s: true, t: true, u: true, v: true, w: true, x: true, y: true, z: true }; // src/config/client.ts var clientConfig = { timeout: 8e3, // baseURL: SRC_BASE_URL, headers: { Accept: ACCEPT_HEADER, "User-Agent": USER_AGENT_HEADER, "Accept-Encoding": ACCEPT_ENCODING_HEADER } }; var client = axios.create(clientConfig); // src/hianime/error.ts import { AxiosError as AxiosError2 } from "axios"; // src/config/logger.ts import { pino } from "pino"; function isDevEnv() { return !process.env.NODE_ENV || process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test"; } var loggerOptions = { level: "info", transport: isDevEnv() ? { target: "pino-pretty", options: { colorize: true, translateTime: "SYS:standard" } } : void 0, formatters: { level(label) { return { level: label.toUpperCase(), context: "aniwatch-pkg" }; } }, redact: !isDevEnv() ? ["hostname"] : [], timestamp: pino.stdTimeFunctions.isoTime }; var log = pino(loggerOptions); // src/hianime/error.ts var ANSI_ESC_CODE_COLOR_RED = "\x1B[31m"; var ANSI_ESC_CODE_COLOR_RESET = "\x1B[0m"; var HiAnimeError = class _HiAnimeError extends Error { static DEFAULT_ERROR_STATUS = 500; static DEFAULT_ERROR_MESSAGE = "Something went wrong"; scraper = _HiAnimeError.DEFAULT_ERROR_MESSAGE; status = _HiAnimeError.DEFAULT_ERROR_STATUS; constructor(errMsg, scraperName, status) { super(`${scraperName}: ${errMsg}`); this.name = _HiAnimeError.name; this.scraper = scraperName; if (status) { this.status = status >= 400 && status < 600 ? status : _HiAnimeError.DEFAULT_ERROR_STATUS; } if (Error.captureStackTrace) { Error.captureStackTrace(this, _HiAnimeError); } this.logError(); } static wrapError(err, scraperName) { if (err instanceof _HiAnimeError) { return err; } if (err instanceof AxiosError2) { const statusText = err?.response?.statusText || _HiAnimeError.DEFAULT_ERROR_MESSAGE; return new _HiAnimeError( "fetchError: " + statusText, scraperName, err.status || _HiAnimeError.DEFAULT_ERROR_STATUS ); } return new _HiAnimeError( err?.message || _HiAnimeError.DEFAULT_ERROR_MESSAGE, scraperName ); } json() { return { status: this.status, message: this.message }; } logError() { log.error( ANSI_ESC_CODE_COLOR_RED + JSON.stringify( { status: this.status, scraper: this.scraper, message: this.message }, null, 2 ) + ANSI_ESC_CODE_COLOR_RESET ); } }; // src/utils/methods.ts var extractAnimes = ($, selector, scraperName) => { try { const animes = []; $(selector).each((_, el) => { const animeId = $(el).find(".film-detail .film-name .dynamic-name")?.attr("href")?.slice(1).split("?ref=search")[0] || null; animes.push({ id: animeId, name: $(el).find(".film-detail .film-name .dynamic-name")?.text()?.trim(), jname: $(el).find(".film-detail .film-name .dynamic-name")?.attr("data-jname")?.trim() || null, poster: $(el).find(".film-poster .film-poster-img")?.attr("data-src")?.trim() || null, duration: $(el).find(".film-detail .fd-infor .fdi-item.fdi-duration")?.text()?.trim(), type: $(el).find(".film-detail .fd-infor .fdi-item:nth-of-type(1)")?.text()?.trim(), rating: $(el).find(".film-poster .tick-rate")?.text()?.trim() || null, episodes: { sub: Number( $(el).find(".film-poster .tick-sub")?.text()?.trim().split(" ").pop() ) || null, dub: Number( $(el).find(".film-poster .tick-dub")?.text()?.trim().split(" ").pop() ) || null } }); }); return animes; } catch (err) { throw HiAnimeError.wrapError(err, scraperName); } }; var extractTop10Animes = ($, period, scraperName) => { try { const animes = []; const selector = `#top-viewed-${period} ul li`; $(selector).each((_, el) => { animes.push({ id: $(el).find(".film-detail .dynamic-name")?.attr("href")?.slice(1).trim() || null, rank: Number($(el).find(".film-number span")?.text()?.trim()) || null, name: $(el).find(".film-detail .dynamic-name")?.text()?.trim() || null, jname: $(el).find(".film-detail .dynamic-name")?.attr("data-jname")?.trim() || null, poster: $(el).find(".film-poster .film-poster-img")?.attr("data-src")?.trim() || null, episodes: { sub: Number( $(el).find( ".film-detail .fd-infor .tick-item.tick-sub" )?.text()?.trim() ) || null, dub: Number( $(el).find( ".film-detail .fd-infor .tick-item.tick-dub" )?.text()?.trim() ) || null } }); }); return animes; } catch (err) { throw HiAnimeError.wrapError(err, scraperName); } }; var extractMostPopularAnimes = ($, selector, scraperName) => { try { const animes = []; $(selector).each((_, el) => { animes.push({ id: $(el).find(".film-detail .dynamic-name")?.attr("href")?.slice(1).trim() || null, name: $(el).find(".film-detail .dynamic-name")?.text()?.trim() || null, jname: $(el).find(".film-detail .film-name .dynamic-name").attr("data-jname")?.trim() || null, poster: $(el).find(".film-poster .film-poster-img")?.attr("data-src")?.trim() || null, episodes: { sub: Number( $(el)?.find(".fd-infor .tick .tick-sub")?.text()?.trim() ) || null, dub: Number( $(el)?.find(".fd-infor .tick .tick-dub")?.text()?.trim() ) || null }, type: $(el)?.find(".fd-infor .tick")?.text()?.trim()?.replace(/[\s\n]+/g, " ")?.split(" ")?.pop() || null }); }); return animes; } catch (err) { throw HiAnimeError.wrapError(err, scraperName); } }; function retrieveServerId($, index, category) { return $( `.ps_-block.ps_-block-sub.servers-${category} > .ps__-list .server-item` )?.map( (_, el) => $(el).attr("data-server-id") == `${index}` ? $(el) : null )?.get()[0]?.attr("data-id") || null; } function getGenresFilterVal(genreNames) { if (genreNames.length < 1) { return void 0; } return genreNames.map((name) => SEARCH_PAGE_FILTERS["GENRES_ID_MAP"][name]).join(","); } function getSearchFilterValue(key, rawValue) { rawValue = rawValue.trim(); if (!rawValue) return void 0; switch (key) { case "genres": { return getGenresFilterVal(rawValue.split(",")); } case "type": { const val = SEARCH_PAGE_FILTERS["TYPE_ID_MAP"][rawValue] ?? 0; return val === 0 ? void 0 : `${val}`; } case "status": { const val = SEARCH_PAGE_FILTERS["STATUS_ID_MAP"][rawValue] ?? 0; return val === 0 ? void 0 : `${val}`; } case "rated": { const val = SEARCH_PAGE_FILTERS["RATED_ID_MAP"][rawValue] ?? 0; return val === 0 ? void 0 : `${val}`; } case "score": { const val = SEARCH_PAGE_FILTERS["SCORE_ID_MAP"][rawValue] ?? 0; return val === 0 ? void 0 : `${val}`; } case "season": { const val = SEARCH_PAGE_FILTERS["SEASON_ID_MAP"][rawValue] ?? 0; return val === 0 ? void 0 : `${val}`; } case "language": { const val = SEARCH_PAGE_FILTERS["LANGUAGE_ID_MAP"][rawValue] ?? 0; return val === 0 ? void 0 : `${val}`; } case "sort": { return SEARCH_PAGE_FILTERS["SORT_ID_MAP"][rawValue] ?? void 0; } default: return void 0; } } function getSearchDateFilterValue(isStartDate, rawValue) { rawValue = rawValue.trim(); if (!rawValue) return void 0; const dateRegex = /^\d{4}-([0-9]|1[0-2])-([0-9]|[12][0-9]|3[01])$/; const dateCategory = isStartDate ? "s" : "e"; const [year, month, date] = rawValue.split("-"); if (!dateRegex.test(rawValue)) { return void 0; } return [ Number(year) > 0 ? `${dateCategory}y=${year}` : "", Number(month) > 0 ? `${dateCategory}m=${month}` : "", Number(date) > 0 ? `${dateCategory}d=${date}` : "" ].filter((d) => Boolean(d)); } function substringAfter(str, toFind) { const index = str.indexOf(toFind); return index == -1 ? "" : str.substring(index + toFind.length); } function substringBefore(str, toFind) { const index = str.indexOf(toFind); return index == -1 ? "" : str.substring(0, index); } // src/hianime/scrapers/homePage.ts async function getHomePage() { const res = { spotlightAnimes: [], trendingAnimes: [], latestEpisodeAnimes: [], topUpcomingAnimes: [], top10Animes: { today: [], week: [], month: [] }, topAiringAnimes: [], mostPopularAnimes: [], mostFavoriteAnimes: [], latestCompletedAnimes: [], genres: [] }; try { const mainPage = await client.get(SRC_HOME_URL); const $ = load(mainPage.data); const spotlightSelector = "#slider .swiper-wrapper .swiper-slide"; $(spotlightSelector).each((_, el) => { const otherInfo = $(el).find(".deslide-item-content .sc-detail .scd-item").map((_2, el2) => $(el2).text().trim()).get().slice(0, -1); res.spotlightAnimes.push({ rank: Number( $(el).find(".deslide-item-content .desi-sub-text")?.text().trim().split(" ")[0].slice(1) ) || null, id: $(el).find(".deslide-item-content .desi-buttons a")?.last()?.attr("href")?.slice(1)?.trim() || null, name: $(el).find(".deslide-item-content .desi-head-title.dynamic-name")?.text().trim(), description: $(el).find(".deslide-item-content .desi-description")?.text()?.split("[")?.shift()?.trim() || null, poster: $(el).find( ".deslide-cover .deslide-cover-img .film-poster-img" )?.attr("data-src")?.trim() || null, jname: $(el).find( ".deslide-item-content .desi-head-title.dynamic-name" )?.attr("data-jname")?.trim() || null, episodes: { sub: Number( $(el).find( ".deslide-item-content .sc-detail .scd-item .tick-item.tick-sub" )?.text()?.trim() ) || null, dub: Number( $(el).find( ".deslide-item-content .sc-detail .scd-item .tick-item.tick-dub" )?.text()?.trim() ) || null }, type: otherInfo?.[0] || null, otherInfo }); }); const trendingSelector = "#trending-home .swiper-wrapper .swiper-slide"; $(trendingSelector).each((_, el) => { res.trendingAnimes.push({ rank: parseInt( $(el).find(".item .number")?.children()?.first()?.text()?.trim() ), id: $(el).find(".item .film-poster")?.attr("href")?.slice(1)?.trim() || null, name: $(el).find(".item .number .film-title.dynamic-name")?.text()?.trim(), jname: $(el).find(".item .number .film-title.dynamic-name")?.attr("data-jname")?.trim() || null, poster: $(el).find(".item .film-poster .film-poster-img")?.attr("data-src")?.trim() || null }); }); const latestEpisodeSelector = "#main-content .block_area_home:nth-of-type(1) .tab-content .film_list-wrap .flw-item"; res.latestEpisodeAnimes = extractAnimes( $, latestEpisodeSelector, getHomePage.name ); const topUpcomingSelector = "#main-content .block_area_home:nth-of-type(3) .tab-content .film_list-wrap .flw-item"; res.topUpcomingAnimes = extractAnimes( $, topUpcomingSelector, getHomePage.name ); const genreSelector = "#main-sidebar .block_area.block_area_sidebar.block_area-genres .sb-genre-list li"; $(genreSelector).each((_, el) => { res.genres.push(`${$(el).text().trim()}`); }); const mostViewedSelector = '#main-sidebar .block_area-realtime [id^="top-viewed-"]'; $(mostViewedSelector).each((_, el) => { const period = $(el).attr("id")?.split("-")?.pop()?.trim(); if (period === "day") { res.top10Animes.today = extractTop10Animes( $, period, getHomePage.name ); return; } if (period === "week") { res.top10Animes.week = extractTop10Animes( $, period, getHomePage.name ); return; } if (period === "month") { res.top10Animes.month = extractTop10Animes( $, period, getHomePage.name ); } }); res.topAiringAnimes = extractMostPopularAnimes( $, "#anime-featured .row div:nth-of-type(1) .anif-block-ul ul li", getHomePage.name ); res.mostPopularAnimes = extractMostPopularAnimes( $, "#anime-featured .row div:nth-of-type(2) .anif-block-ul ul li", getHomePage.name ); res.mostFavoriteAnimes = extractMostPopularAnimes( $, "#anime-featured .row div:nth-of-type(3) .anif-block-ul ul li", getHomePage.name ); res.latestCompletedAnimes = extractMostPopularAnimes( $, "#anime-featured .row div:nth-of-type(4) .anif-block-ul ul li", getHomePage.name ); return res; } catch (err) { throw HiAnimeError.wrapError(err, getHomePage.name); } } // src/hianime/scrapers/animeAZList.ts import { load as load2 } from "cheerio"; async function getAZList(sortOption, page) { const res = { sortOption: sortOption.trim(), animes: [], totalPages: 0, hasNextPage: false, currentPage: (Number(page) || 0) < 1 ? 1 : Number(page) }; sortOption = res.sortOption; page = res.currentPage; try { if (sortOption === "" || !Boolean(AZ_LIST_SORT_OPTIONS[sortOption])) { throw new HiAnimeError( "invalid az-list sort option", getAZList.name, 400 ); } switch (sortOption) { case "all": sortOption = ""; break; case "other": sortOption = "other"; break; default: sortOption = sortOption.toUpperCase(); } const azURL = new URL( `/az-list/${sortOption}?page=${page}`, SRC_BASE_URL ); const resp = await client.get(azURL.href); const $ = load2(resp.data); const selector = "#main-wrapper .tab-content .film_list-wrap .flw-item"; res.hasNextPage = $(".pagination > li").length > 0 ? $(".pagination li.active").length > 0 ? $(".pagination > li").last().hasClass("active") ? false : true : false : false; res.totalPages = Number( $('.pagination > .page-item a[title="Last"]')?.attr("href")?.split("=").pop() ?? $('.pagination > .page-item a[title="Next"]')?.attr("href")?.split("=").pop() ?? $(".pagination > .page-item.active a")?.text()?.trim() ) || 1; res.animes = extractAnimes($, selector, getAZList.name); if (res.animes.length === 0 && !res.hasNextPage) { res.totalPages = 0; } return res; } catch (err) { throw HiAnimeError.wrapError(err, getAZList.name); } } // src/hianime/scrapers/animeGenre.ts import { load as load3 } from "cheerio"; async function getGenreAnime(genreName, page) { const res = { // there's a typo with hianime where "martial" arts is "marial" arts genreName: genreName === "martial-arts" ? "marial-arts" : genreName.trim(), animes: [], genres: [], topAiringAnimes: [], totalPages: 1, hasNextPage: false, currentPage: (Number(page) || 0) < 1 ? 1 : Number(page) }; genreName = res.genreName; page = res.currentPage; try { if (genreName === "") { throw new HiAnimeError( "invalid genre name", getGenreAnime.name, 400 ); } const genreUrl = new URL( `/genre/${genreName}?page=${page}`, SRC_BASE_URL ); const mainPage = await client.get(genreUrl.href); const $ = load3(mainPage.data); const selector = "#main-content .tab-content .film_list-wrap .flw-item"; const genreNameSelector = "#main-content .block_area .block_area-header .cat-heading"; res.genreName = $(genreNameSelector)?.text()?.trim() ?? genreName; res.hasNextPage = $(".pagination > li").length > 0 ? $(".pagination li.active").length > 0 ? $(".pagination > li").last().hasClass("active") ? false : true : false : false; res.totalPages = Number( $('.pagination > .page-item a[title="Last"]')?.attr("href")?.split("=").pop() ?? $('.pagination > .page-item a[title="Next"]')?.attr("href")?.split("=").pop() ?? $(".pagination > .page-item.active a")?.text()?.trim() ) || 1; res.animes = extractAnimes($, selector, getGenreAnime.name); if (res.animes.length === 0 && !res.hasNextPage) { res.totalPages = 0; } const genreSelector = "#main-sidebar .block_area.block_area_sidebar.block_area-genres .sb-genre-list li"; $(genreSelector).each((_, el) => { res.genres.push(`${$(el).text().trim()}`); }); const topAiringSelector = "#main-sidebar .block_area.block_area_sidebar.block_area-realtime .anif-block-ul ul li"; res.topAiringAnimes = extractMostPopularAnimes( $, topAiringSelector, getGenreAnime.name ); return res; } catch (err) { throw HiAnimeError.wrapError(err, getGenreAnime.name); } } // src/hianime/scrapers/animeQtip.ts import { load as load4 } from "cheerio"; async function getAnimeQtipInfo(animeId) { const res = { anime: { id: animeId.trim(), name: null, malscore: null, quality: null, episodes: { sub: null, dub: null }, type: null, description: null, jname: null, synonyms: null, aired: null, status: null, genres: [] } }; try { animeId = String(res.anime.id); const id = animeId.split("-").pop(); if (animeId === "" || animeId.indexOf("-") === -1 || !id) { throw new HiAnimeError( "invalid anime id", getAnimeQtipInfo.name, 400 ); } const mainPage = await client.get(`${SRC_AJAX_URL}/movie/qtip/${id}`, { headers: { Referer: SRC_HOME_URL, "X-Requested-With": "XMLHttpRequest" } }); const $ = load4(mainPage.data); const selector = ".pre-qtip-content"; res.anime.id = $(selector)?.find(".pre-qtip-button a.btn-play")?.attr("href")?.trim()?.split("/")?.pop() || null; res.anime.name = $(selector)?.find(".pre-qtip-title")?.text()?.trim() || null; res.anime.malscore = $(selector)?.find(".pre-qtip-detail")?.children()?.first()?.text()?.trim() || null; res.anime.quality = $(selector)?.find(".tick .tick-quality")?.text()?.trim() || null; res.anime.type = $(selector)?.find(".badge.badge-quality")?.text()?.trim() || null; res.anime.episodes.sub = Number($(selector)?.find(".tick .tick-sub")?.text()?.trim()) || null; res.anime.episodes.dub = Number($(selector)?.find(".tick .tick-dub")?.text()?.trim()) || null; res.anime.description = $(selector)?.find(".pre-qtip-description")?.text()?.trim() || null; $(`${selector} .pre-qtip-line`).each((_, el) => { const key = $(el).find(".stick").text().trim().slice(0, -1).toLowerCase(); const value = key !== "genres" ? $(el)?.find(".stick-text")?.text()?.trim() || null : $(el)?.text()?.trim()?.slice(key.length + 1); switch (key) { case "japanese": res.anime.jname = value; break; case "synonyms": res.anime.synonyms = value; break; case "aired": res.anime.aired = value; break; case "status": res.anime.status = value; break; case "genres": res.anime.genres = value?.split(",")?.map((i) => i?.trim()) || []; break; } }); return res; } catch (err) { throw HiAnimeError.wrapError(err, getAnimeQtipInfo.name); } } // src/hianime/scrapers/animeEpisodes.ts import { load as load5 } from "cheerio"; async function getAnimeEpisodes(animeId) { const res = { totalEpisodes: 0, episodes: [] }; try { if (animeId.trim() === "" || animeId.indexOf("-") === -1) { throw new HiAnimeError( "invalid anime id", getAnimeEpisodes.name, 400 ); } const episodesAjax = await client.get( `${SRC_AJAX_URL}/v2/episode/list/${animeId.split("-").pop()}`, { headers: { "X-Requested-With": "XMLHttpRequest", Referer: `${SRC_BASE_URL}/watch/${animeId}` } } ); const $ = load5(episodesAjax.data.html); res.totalEpisodes = Number( $(".detail-infor-content .ss-list a").length ); $(".detail-infor-content .ss-list a").each((_, el) => { res.episodes.push({ title: $(el)?.attr("title")?.trim() || null, episodeId: $(el)?.attr("href")?.split("/")?.pop() || null, number: Number($(el).attr("data-number")), isFiller: $(el).hasClass("ssl-item-filler") }); }); return res; } catch (err) { throw HiAnimeError.wrapError(err, getAnimeEpisodes.name); } } // src/hianime/scrapers/animeCategory.ts import { load as load6 } from "cheerio"; async function getAnimeCategory(category, page) { const res = { animes: [], genres: [], top10Animes: { today: [], week: [], month: [] }, category, totalPages: 0, hasNextPage: false, currentPage: (Number(page) || 0) < 1 ? 1 : Number(page) }; try { if (category.trim() === "") { throw new HiAnimeError( "invalid anime category", getAnimeCategory.name, 400 ); } page = res.currentPage; const scrapeUrl = new URL(category, SRC_BASE_URL); const mainPage = await client.get(`${scrapeUrl}?page=${page}`); const $ = load6(mainPage.data); const selector = "#main-content .tab-content .film_list-wrap .flw-item"; const categoryNameSelector = "#main-content .block_area .block_area-header .cat-heading"; res.category = $(categoryNameSelector)?.text()?.trim() ?? category; res.hasNextPage = $(".pagination > li").length > 0 ? $(".pagination li.active").length > 0 ? $(".pagination > li").last().hasClass("active") ? false : true : false : false; res.totalPages = Number( $('.pagination > .page-item a[title="Last"]')?.attr("href")?.split("=").pop() ?? $('.pagination > .page-item a[title="Next"]')?.attr("href")?.split("=").pop() ?? $(".pagination > .page-item.active a")?.text()?.trim() ) || 1; res.animes = extractAnimes($, selector, getAnimeCategory.name); if (res.animes.length === 0 && !res.hasNextPage) { res.totalPages = 0; } const genreSelector = "#main-sidebar .block_area.block_area_sidebar.block_area-genres .sb-genre-list li"; $(genreSelector).each((_, el) => { res.genres.push(`${$(el).text().trim()}`); }); const top10AnimeSelector = '#main-sidebar .block_area-realtime [id^="top-viewed-"]'; $(top10AnimeSelector).each((_, el) => { const period = $(el).attr("id")?.split("-")?.pop()?.trim(); if (period === "day") { res.top10Animes.today = extractTop10Animes( $, period, getAnimeCategory.name ); return; } if (period === "week") { res.top10Animes.week = extractTop10Animes( $, period, getAnimeCategory.name ); return; } if (period === "month") { res.top10Animes.month = extractTop10Animes( $, period, getAnimeCategory.name ); } }); return res; } catch (err) { throw HiAnimeError.wrapError(err, getAnimeCategory.name); } } // src/hianime/scrapers/animeProducer.ts import { load as load7 } from "cheerio"; async function getProducerAnimes(producerName, page) { const res = { producerName, animes: [], top10Animes: { today: [], week: [], month: [] }, topAiringAnimes: [], totalPages: 0, hasNextPage: false, currentPage: (Number(page) || 0) < 1 ? 1 : Number(page) }; try { if (producerName.trim() === "") { throw new HiAnimeError( "invalid producer name", getProducerAnimes.name, 400 ); } page = res.currentPage; const producerUrl = new URL( `/producer/${producerName}?page=${page}`, SRC_BASE_URL ); const mainPage = await client.get(producerUrl.href); const $ = load7(mainPage.data); const animeSelector = "#main-content .tab-content .film_list-wrap .flw-item"; res.hasNextPage = $(".pagination > li").length > 0 ? $(".pagination li.active").length > 0 ? $(".pagination > li").last().hasClass("active") ? false : true : false : false; res.totalPages = Number( $('.pagination > .page-item a[title="Last"]')?.attr("href")?.split("=").pop() ?? $('.pagination > .page-item a[title="Next"]')?.attr("href")?.split("=").pop() ?? $(".pagination > .page-item.active a")?.text()?.trim() ) || 1; res.animes = extractAnimes($, animeSelector, getProducerAnimes.name); if (res.animes.length === 0 && !res.hasNextPage) { res.totalPages = 0; } const producerNameSelector = "#main-content .block_area .block_area-header .cat-heading"; res.producerName = $(producerNameSelector)?.text()?.trim() ?? producerName; const top10AnimeSelector = '#main-sidebar .block_area-realtime [id^="top-viewed-"]'; $(top10AnimeSelector).each((_, el) => { const period = $(el).attr("id")?.split("-")?.pop()?.trim(); if (period === "day") { res.top10Animes.today = extractTop10Animes( $, period, getProducerAnimes.name ); return; } if (period === "week") { res.top10Animes.week = extractTop10Animes( $, period, getProducerAnimes.name ); return; } if (period === "month") { res.top10Animes.month = extractTop10Animes( $, period, getProducerAnimes.name ); } }); const topAiringSelector = "#main-sidebar .block_area_sidebar:nth-child(2) .block_area-content .anif-block-ul ul li"; res.topAiringAnimes = extractMostPopularAnimes( $, topAiringSelector, getProducerAnimes.name ); return res; } catch (err) { throw HiAnimeError.wrapError(err, getProducerAnimes.name); } } // src/hianime/scrapers/episodeServers.ts import { load as load8 } from "cheerio"; async function getEpisodeServers(episodeId) { const res = { sub: [], dub: [], raw: [], episodeId, episodeNo: 0 }; try { if (episodeId.trim() === "" || episodeId.indexOf("?ep=") === -1) { throw new HiAnimeError( "invalid anime episode id", getEpisodeServers.name, 400 ); } const epId = episodeId.split("?ep=")[1]; const { data } = await client.get( `${SRC_AJAX_URL}/v2/episode/servers?episodeId=${epId}`, { headers: { "X-Requested-With": "XMLHttpRequest", Referer: new URL(`/watch/${episodeId}`, SRC_BASE_URL).href } } ); const $ = load8(data.html); const epNoSelector = ".server-notice strong"; res.episodeNo = Number($(epNoSelector).text().split(" ").pop()) || 0; $(`.ps_-block.ps_-block-sub.servers-sub .ps__-list .server-item`).each( (_, el) => { res.sub.push({ serverName: $(el).find("a").text().toLowerCase().trim(), serverId: Number($(el)?.attr("data-server-id")?.trim()) || null }); } ); $(`.ps_-block.ps_-block-sub.servers-dub .ps__-list .server-item`).each( (_, el) => { res.dub.push({ serverName: $(el).find("a").text().toLowerCase().trim(), serverId: Number($(el)?.attr("data-server-id")?.trim()) || null }); } ); $(`.ps_-block.ps_-block-sub.servers-raw .ps__-list .server-item`).each( (_, el) => { res.raw.push({ serverName: $(el).find("a").text().toLowerCase().trim(), serverId: Number($(el)?.attr("data-server-id")?.trim()) || null }); } ); return res; } catch (err) { throw HiAnimeError.wrapError(err, getEpisodeServers.name); } } // src/hianime/scrapers/animeAboutInfo.ts import { load as load9 } from "cheerio"; async function getAnimeAboutInfo(animeId) { const res = { anime: { info: { id: null, anilistId: null, malId: null, name: null, poster: null, description: null, stats: { rating: null, quality: null, episodes: { sub: null, dub: null }, type: null, duration: null }, promotionalVideos: [], charactersVoiceActors: [] }, moreInfo: {} }, seasons: [], mostPopularAnimes: [], relatedAnimes: [], recommendedAnimes: [] }; try { if (animeId.trim() === "" || animeId.indexOf("-") === -1) { throw new HiAnimeError( "invalid anime id", getAnimeAboutInfo.name, 400 ); } const animeUrl = new URL(animeId, SRC_BASE_URL); const mainPage = await client.get(animeUrl.href); const $ = load9(mainPage.data); try { res.anime.info.anilistId = Number( JSON.parse($("body")?.find("#syncData")?.text())?.anilist_id ); res.anime.info.malId = Number( JSON.parse($("body")?.find("#syncData")?.text())?.mal_id ); } catch (err) { res.anime.info.anilistId = null; res.anime.info.malId = null; } const selector = "#ani_detail .container .anis-content"; res.anime.info.id = $(selector)?.find(".anisc-detail .film-buttons a.btn-play")?.attr("href")?.split("/")?.pop() || null; res.anime.info.name = $(selector)?.find(".anisc-detail .film-name.dynamic-name")?.text()?.trim() || null; res.anime.info.description = $(selector)?.find(".anisc-detail .film-description .text").text()?.split("[")?.shift()?.trim() || null; res.anime.info.poster = $(selector)?.find(".film-poster .film-poster-img")?.attr("src")?.trim() || null; res.anime.info.stats.rating = $(`${selector} .film-stats .tick .tick-pg`)?.text()?.trim() || null; res.anime.info.stats.quality = $(`${selector} .film-stats .tick .tick-quality`)?.text()?.trim() || null; res.anime.info.stats.episodes = { sub: Number( $(`${selector} .film-stats .tick .tick-sub`)?.text()?.trim() ) || null, dub: Number( $(`${selector} .film-stats .tick .tick-dub`)?.text()?.trim() ) || null }; res.anime.info.stats.type = $(`${selector} .film-stats .tick`)?.text()?.trim()?.replace(/[\s\n]+/g, " ")?.split(" ")?.at(-2) || null; res.anime.info.stats.duration = $(`${selector} .film-stats .tick`)?.text()?.trim()?.replace(/[\s\n]+/g, " ")?.split(" ")?.pop() || null; $( ".block_area.block_area-promotions .block_area-promotions-list .screen-items .item" ).each((_, el) => { res.anime.info.promotionalVideos.push({ title: $(el).attr("data-title"), source: $(el).attr("data-src"), thumbnail: $(el).find("img").attr("src") }); }); $( ".block_area.block_area-actors .block-actors-content .bac-list-wrap .bac-item" ).each((_, el) => { res.anime.info.charactersVoiceActors.push({ character: { id: $(el).find($(".per-info.ltr .pi-avatar")).attr("href")?.split("/")[2] || "", poster: $(el).find($(".per-info.ltr .pi-avatar img")).attr("data-src") || "", name: $(el).find($(".per-info.ltr .pi-detail a")).text(), cast: $(el).find($(".per-info.ltr .pi-detail .pi-cast")).text() }, voiceActor: { id: $(el).find($(".per-info.rtl .pi-avatar")).attr("href")?.split("/")[2] || "", poster: $(el).find($(".per-info.rtl .pi-avatar img")).attr("data-src") || "", name: $(el).find($(".per-info.rtl .pi-detail a")).text(), cast: $(el).find($(".per-info.rtl .pi-detail .pi-cast")).text() } }); }); $(`${selector} .anisc-info-wrap .anisc-info .item:not(.w-hide)`).each( (_, el) => { let key = $(el).find(".item-head").text().toLowerCase().replace(":", "").trim(); key = key.includes(" ") ? key.replace(" ", "") : key; const value = [ ...$(el).find("*:not(.item-head)").map((_2, el2) => $(el2).text().trim()) ].map((i) => `${i}`).toString().trim(); if (key === "genres") { res.anime.moreInfo[key] = value.split(",").map((i) => i.trim()); return; } if (key === "producers") { res.anime.moreInfo[key] = value.split(",").map((i) => i.trim()); return; } res.anime.moreInfo[key] = value; } ); const seasonsSelector = "#main-content .os-list a.os-item"; $(seasonsSelector).each((_, el) => { res.seasons.push({ id: $(el)?.attr("href")?.slice(1)?.trim() || null, name: $(el)?.attr("title")?.trim() || null, title: $(el)?.find(".title")?.text()?.trim(), poster: $(el)?.find(".season-poster")?.attr("style")?.split(" ")?.pop()?.split("(")?.pop()?.split(")")[0] || null, isCurrent: $(el).hasClass("active") }); }); const relatedAnimeSelector = "#main-sidebar .block_area.block_area_sidebar.block_area-realtime:nth-of-type(1) .anif-block-ul ul li"; res.relatedAnimes = extractMostPopularAnimes( $, relatedAnimeSelector, getAnimeAboutInfo.name ); const mostPopularSelector = "#main-sidebar .block_area.block_area_sidebar.block_area-realtime:nth-of-type(2) .anif-block-ul ul li"; res.mostPopularAnimes = extractMostPopularAnimes( $, mostPopularSelector, getAnimeAboutInfo.name ); const recommendedAnimeSelector = "#main-content .block_area.block_area_category .tab-content .flw-item"; res.recommendedAnimes = extractAnimes( $, recommendedAnimeSelector, getAnimeAboutInfo.name ); return res; } catch (err) { throw HiAnimeError.wrapError(err, getAnimeAboutInfo.name); } } // src/hianime/scrapers/animeSearch.ts import { load as load10 } from "cheerio"; var searchFilters = { filter: true, type: true, status: true, rated: true, score: true, season: true, language: true, start_date: true, end_date: true, sort: true, genres: true }; async function _getAnimeSearchResults(q, page = 1, filters) { try { const res = { animes: [], mostPopularAnimes: [], searchQuery: q, searchFilters: filters, totalPages: 0, hasNextPage: false, currentPage: (Number(page) || 0) < 1 ? 1 : Number(page) }; const url = new URL(SRC_SEARCH_URL); url.searchParams.set("keyword", q); url.searchParams.set("page", `${res.currentPage}`); url.searchParams.set("sort", "default"); for (const key in filters) { if (key.includes("_date")) { const dates = getSearchDateFilterValue( key === "start_date", filters[key] || "" ); if (!dates) continue; dates.map((dateParam) => { const [key2, val] = dateParam.split("="); url.searchParams.set(key2, val); }); continue; } const filterVal = getSearchFilterValue( key, filters[key] || "" ); filterVal && url.searchParams.set(key, filterVal); } const mainPage = await client.get(url.href); const $ = load10(mainPage.data); const selector = "#main-content .tab-content .film_list-wrap .flw-item"; res.hasNextPage = $(".pagination > li").length > 0 ? $(".pagination li.active").length > 0 ? $(".pagination > li").last().hasClass("active") ? false : true : false : false; res.totalPages = Number( $('.pagination > .page-item a[title="Last"]')?.attr("href")?.split("=").pop() ?? $('.pagination > .page-item a[title="Next"]')?.attr("href")?.split("=").pop() ?? $(".pagination > .page-item.active a")?.text()?.trim() ) || 1; res.animes = extractAnimes($, selector, getAnimeSearchResults.name); if (res.animes.length === 0 && !res.hasNextPage) { res.totalPages = 0; } const mostPopularSelector = "#main-sidebar .block_area.block_area_sidebar.block_area-realtime .anif-block-ul ul li"; res.mostPopularAnimes = extractMostPopularAnimes( $, mostPopularSelector, getAnimeSearchResults.name ); return res; } catch (err) { throw HiAnimeError.wrapError(err, getAnimeSearchResults.name); } } async function getAnimeSearchResults(q, page, filters) { try { q = q.trim() ? decodeURIComponent(q.trim()) : ""; if (q.trim() === "") { throw new HiAnimeError( "invalid search query", getAnimeSearchResults.name, 400 ); } page = page < 1 ? 1 : page; const parsedFilters = {}; for (const key in filters) { if (searchFilters[key]) { parsedFilters[key] = filters[key]; } } return _getAnimeSearchResults(q, page, parsedFilters); } catch (err) { throw HiAnimeError.wrapError(err, getAnimeSearchResults.name); } } // src/hianime/scrapers/animeEpisodeSrcs.ts import axios6 from "axios"; import { load as load13 } from "cheerio"; // src/extractors/streamsb.ts import axios2 from "axios"; var StreamSB = class { // private serverName = "streamSB"; sources = []; host = "https://watchsb.com/sources50"; host2 = "https://streamsss.net/sources16"; PAYLOAD(hex) { return `566d337678566f743674494a7c7c${hex}7c7c346b6767586d6934774855537c7c73747265616d7362/6565417268755339773461447c7c346133383438333436313335376136323337373433383634376337633465366534393338373136643732373736343735373237613763376334363733353737303533366236333463353333363534366137633763373337343732363536313664373336327c7c6b586c3163614468645a47617c7c73747265616d7362`; } async extract(videoUrl, isAlt = false) { let headers = { watchsb: "sbstream", Referer: videoUrl.href, "User-Agent": USER_AGENT_HEADER }; let id = videoUrl.href.split("/e/").pop(); if (id?.includes("html")) { id = id.split(".html")[0]; } const bytes = new TextEncoder().encode(id); const res = await axios2.get( `${isAlt ? this.host2 : this.host}/${this.PAYLOAD( Buffer.from(bytes).toString("hex") )}`, { headers } ).catch(() => null); if (!res?.data.stream_data) { throw new Error("No source found. Try a different server"); } headers = { "User-Agent": USER_AGENT_HEADER, Referer: videoUrl.href.split("e/")[0] }; const m3u8_urls = await axios2.get(res.data.stream_data.file, { headers }); const videoList = m3u8_urls?.data?.split("#EXT-X-STREAM-INF:") ?? []; for (const video of videoList) { if (!video.includes("m3u8")) continue; const url = video.split("\n")[1]; const quality = video.split("RESOLUTION=")[1].split(",")[0].split("x")[1]; this.sources.push({ url, quality: `${quality}p`, isM3U8: true }); } this.sources.push({ url: res.data.stream_data.file, quality: "auto", isM3U8: res.data.stream_data.file.includes(".m3u8") }); return this.sources; } // private addSources(source: any): void { // this.sources.push({ // url: source.file, // isM3U8: source.file.includes(".m3u8"), // }); // } }; var streamsb_default = StreamSB; // src/extractors/streamtape.ts import axios3 from "axios"; import { load as load11 } from "cheerio"; var StreamTape = class { // private serverName = "StreamTape"; sources = []; async extract(videoUrl) { try { const { data } = await axios3.get(videoUrl.href).catch(() => { throw new Error("Video not found"); }); const $ = load11(data); let [fh, sh] = $.html()?.match(/robotlink'\).innerHTML = (.*)'/)[1].split("+ ('"); sh = sh.substring(3); fh = fh.replace(/\'/g, ""); const url = `https:${fh}${sh}`; this.sources.push({ url, isM3U8: url.includes(".m3u8") }); return this.sources; } catch (err) { throw new Error(err.message); } } }; var streamtape_default = StreamTape; // src/extractors/rapidcloud.ts import axios4 from "axios"; import CryptoJS from "crypto-js"; var RapidCloud = class { // private serverName = "RapidCloud"; sources = []; // https://rapid-cloud.co/embed-6/eVZPDXwVfrY3?vast=1 fallbackKey = "c1d17096f2ca11b7"; host = "https://rapid-cloud.co"; async extract(videoUrl) { const result = { sources: [], subtitles: [] }; try { const id = videoUrl.href.split("/").pop()?.split("?")[0]; const options = { headers: { "X-Requested-With": "XMLHttpRequest" } }; let res = null; res = await axios4.get( `https://${videoUrl.hostname}/embed-2/ajax/e-1/getSources?id=${id}`, options ); let { data: { sources, tracks, intro, outro, encrypted } } = res; let decryptKey = await (await axios4.get( "https://raw.githubusercontent.com/cinemaxhq/keys/e1/key" )).data; decryptKey = substringBefore( substringAfter( decryptKey, '"blob-code blob-code-inner js-file-line">' ), "</td>" ); if (!decryptKey) { decryptKey = await (await axios4.get( "https://raw.githubusercontent.com/cinemaxhq/keys/e1/key" )).data; } if (!decryptKey) decryptKey = this.fallbackKey; try { if (encrypted) { const sourcesArray = sources.split(""); let extractedKey = ""; let currentIndex = 0; for (const index of decryptKey) { const start = index[0] + currentIndex; const end = start + index[1]; for (let i = start; i < end; i++) { extractedKey += res.data.sources[i]; sourcesArray[i] = ""; } currentIndex += index[1]; } decryptKey = extractedKey; sources = sourcesArray.join(""); const decrypt = CryptoJS.AES.decrypt(sources, decryptKey); sources = JSON.parse(decrypt.toString(CryptoJS.enc.Utf8)); } } catch (err) { log.info(err.message); throw new Error( "Cannot decrypt sources. Perhaps the key is invalid." ); } this.sources = sources?.map((s) => ({ url: s.file, isM3U8: s.file.includes(".m3u8") })); result.sources.push(...this.sources); if (videoUrl.href.includes(new URL(this.host).host)) { result.sources = []; this.sources = []; for (const source of sources) { const { data } = await axios4.get(source.file, options); const m3u8data = data.split("\n").filter( (line) => line.includes(".m3u8") && line.includes("RESOLUTION=") ); const secondHalf = m3u8data.map( (line) => line.match(/RESOLUTION=.*,(C)|URI=.*/g)?.map((s) => s.split("=")[1]) ); const TdArray = secondHalf.map((s) => { const f1 = s[0].split(",C")[0]; const f2 = s[1].replace(/"/g, ""); return [f1, f2]; }); for (const [f1, f2] of TdArray) { this.sources.push({ url: `${source.file?.split("master.m3u8")[0]}${f2.replace( "iframes", "index" )}`, quality: f1.split("x")[1] + "p", isM3U8: f2.includes(".m3u8") }); } result.sources.push(...this.sources); } } result.intro = intro?.end > 1 ? { start: intro.start, end: intro.end } : void 0; result.outro = outro?.end > 1 ? { start: outro.start, end: outro.end } : void 0; result.sources.push({ url: sources[0].file, isM3U8: sources[0].file.includes(".m3u8"), quality: "auto" }); result.subtitles = tracks.map( (s) => s.file ? { url: s.file, lang: s.label ? s.label : "Thumbnails" } : null ).filter((s) => s); return result; } catch (err) { log.info(err.message); throw err; } } }; var rapidcloud_default = RapidCloud; // src/extractors/megacloud.ts import axios5 from "axios"; import crypto from "crypto"; import CryptoJS2 from "crypto-js"; import * as cheerio from "cheerio"; var megacloud = { script: "https://megacloud.tv/js/player/a/prod/e1-player.min.js?v=", sources: "https://megacloud.tv/embed-2/ajax/e-1/getSources?id=" }; var MegaCloud = class { // private serverName = "megacloud"; async extract(videoUrl) { try { const extractedData = { tracks: [], intro: { start: 0, end: 0 }, outro: { start: 0, end: 0 }, sources: [] }; const videoId = videoUrl?.href?.split("/")?.pop()?.split("?")[0]; const { data: srcsData } = await axios5.get( megacloud.sources.concat(videoId || ""), { headers: { Accept: "*/*", "X-Requested-With": "XMLHttpRequest", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", Referer: videoUrl.href } } ); if (!srcsData) { throw new HiAnimeError( "Url may have an invalid video id", "getAnimeEpisodeSources", 400 ); } const encryptedString = srcsData.sources; if (!srcsData.encrypted && Array.isArray(encryptedString)) { extractedData.intro = srcsData.intro; extractedData.outro = srcsData.outro; extractedData.tracks = srcsData.tracks; extractedData.sources = encryptedString.map((s) => ({ url: s.file, type: s.type })); return extractedData; } let text; const { data } = await axios5.get( megacloud.script.concat(Date.now().toString()) ); text = data; if (!text) { throw new HiAnimeError( "Couldn't fetch script to decrypt resource", "getAnimeEpisodeSources", 500 ); } const vars = this.extractVariables(text); if (!vars.length) { throw new Error( "Can't find variables. Perhaps the extractor is outdated." ); } const { secret, encryptedSource } = this.getSecret( encryptedString, vars ); const decrypted = this.decrypt(encryptedSource, secret); try { const sources = JSON.parse(decrypted); extractedData.intro = srcsData.intro; extractedDat