aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config/axios.ts21
-rw-r--r--src/config/cors.ts17
-rw-r--r--src/config/errorHandler.ts11
-rw-r--r--src/config/notFoundHandler.ts8
-rw-r--r--src/config/ratelimit.ts24
-rw-r--r--src/controllers/animeAboutInfo.controller.ts31
-rw-r--r--src/controllers/animeCategory.controller.ts39
-rw-r--r--src/controllers/animeEpisodeSrcs.controller.ts75
-rw-r--r--src/controllers/animeEpisodes.controller.ts31
-rw-r--r--src/controllers/animeGenre.controller.ts37
-rw-r--r--src/controllers/animeProducer.controller.ts37
-rw-r--r--src/controllers/animeSearch.controller.ts57
-rw-r--r--src/controllers/animeSearchSuggestion.controller.ts31
-rw-r--r--src/controllers/episodeServers.controller.ts30
-rw-r--r--src/controllers/estimatedSchedule.controller.ts36
-rw-r--r--src/controllers/homePage.controller.ts18
-rw-r--r--src/controllers/index.ts25
-rw-r--r--src/extractors/index.ts6
-rw-r--r--src/extractors/megacloud.ts226
-rw-r--r--src/extractors/rapidcloud.ts166
-rw-r--r--src/extractors/streamsb.ts83
-rw-r--r--src/extractors/streamtape.ts37
-rw-r--r--src/parsers/animeAboutInfo.ts229
-rw-r--r--src/parsers/animeCategory.ts118
-rw-r--r--src/parsers/animeEpisodeSrcs.ts129
-rw-r--r--src/parsers/animeEpisodes.ts61
-rw-r--r--src/parsers/animeGenre.ts105
-rw-r--r--src/parsers/animeProducer.ts120
-rw-r--r--src/parsers/animeSearch.ts118
-rw-r--r--src/parsers/animeSearchSuggestion.ts77
-rw-r--r--src/parsers/episodeServers.ts85
-rw-r--r--src/parsers/estimatedSchedule.ts74
-rw-r--r--src/parsers/homePage.ts186
-rw-r--r--src/parsers/index.ts25
-rw-r--r--src/routes/hianime.ts125
-rw-r--r--src/routes/index.ts55
-rw-r--r--src/server.ts80
-rw-r--r--src/types/anime.ts140
-rw-r--r--src/types/controllers/animeAboutInfo.ts3
-rw-r--r--src/types/controllers/animeCategory.ts7
-rw-r--r--src/types/controllers/animeEpisodeSrcs.ts7
-rw-r--r--src/types/controllers/animeEpisodes.ts3
-rw-r--r--src/types/controllers/animeGenre.ts7
-rw-r--r--src/types/controllers/animeProducer.ts7
-rw-r--r--src/types/controllers/animeSearch.ts20
-rw-r--r--src/types/controllers/animeSearchSuggestion.ts3
-rw-r--r--src/types/controllers/episodeServers.ts3
-rw-r--r--src/types/controllers/estimatedSchedule.ts3
-rw-r--r--src/types/controllers/index.ts10
-rw-r--r--src/types/extractor.ts18
-rw-r--r--src/types/parsers/animeAboutInfo.ts19
-rw-r--r--src/types/parsers/animeCategory.ts22
-rw-r--r--src/types/parsers/animeEpisodeSrcs.ts12
-rw-r--r--src/types/parsers/animeEpisodes.ts6
-rw-r--r--src/types/parsers/animeGenre.ts11
-rw-r--r--src/types/parsers/animeProducer.ts8
-rw-r--r--src/types/parsers/animeSearch.ts14
-rw-r--r--src/types/parsers/animeSearchSuggestion.ts6
-rw-r--r--src/types/parsers/episodeServers.ts9
-rw-r--r--src/types/parsers/estimatedSchedule.ts13
-rw-r--r--src/types/parsers/homePage.ts24
-rw-r--r--src/types/parsers/index.ts25
-rw-r--r--src/utils/constants.ts129
-rw-r--r--src/utils/index.ts2
-rw-r--r--src/utils/methods.ts297
65 files changed, 197 insertions, 3264 deletions
diff --git a/src/config/axios.ts b/src/config/axios.ts
deleted file mode 100644
index 6292e9f..0000000
--- a/src/config/axios.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import axios, { AxiosError, type AxiosRequestConfig } from "axios";
-import {
- SRC_BASE_URL,
- ACCEPT_HEADER,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
-} from "../utils/constants.js";
-
-const clientConfig: AxiosRequestConfig = {
- timeout: 10000,
- baseURL: SRC_BASE_URL,
- headers: {
- Accept: ACCEPT_HEADER,
- "User-Agent": USER_AGENT_HEADER,
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- },
-};
-
-const client = axios.create(clientConfig);
-
-export { client, AxiosError };
diff --git a/src/config/cors.ts b/src/config/cors.ts
index 8089522..6d6e138 100644
--- a/src/config/cors.ts
+++ b/src/config/cors.ts
@@ -1,24 +1,17 @@
-import cors from "cors";
import { config } from "dotenv";
+import { cors } from "hono/cors";
config();
-const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS
- ? process.env.CORS_ALLOWED_ORIGINS.split(",")
+const allowedOrigins = process.env.ANIWATCH_API_CORS_ALLOWED_ORIGINS
+ ? process.env.ANIWATCH_API_CORS_ALLOWED_ORIGINS.split(",")
: ["http://localhost:4000", "*"];
const corsConfig = cors({
- origin: function (origin, callback) {
- if (!origin || allowedOrigins.includes(origin)) {
- callback(null, true);
- } else {
- callback(new Error("Not allowed by CORS"));
- }
- },
- methods: ["GET"],
+ allowMethods: ["GET"],
maxAge: 600,
credentials: true,
- optionsSuccessStatus: 200,
+ origin: allowedOrigins,
});
export default corsConfig;
diff --git a/src/config/errorHandler.ts b/src/config/errorHandler.ts
deleted file mode 100644
index 560f141..0000000
--- a/src/config/errorHandler.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { ErrorRequestHandler } from "express";
-
-const errorHandler: ErrorRequestHandler = (error, req, res, next) => {
- const status = error?.status || 500;
- res.status(status).json({
- status,
- message: error?.message || "Something Went Wrong",
- });
-};
-
-export default errorHandler;
diff --git a/src/config/notFoundHandler.ts b/src/config/notFoundHandler.ts
deleted file mode 100644
index a372e8e..0000000
--- a/src/config/notFoundHandler.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { RequestHandler } from "express";
-import createHttpError from "http-errors";
-
-const notFoundHandler: RequestHandler = (req, res, next) => {
- return next(createHttpError.NotFound());
-};
-
-export default notFoundHandler;
diff --git a/src/config/ratelimit.ts b/src/config/ratelimit.ts
index c5add3f..afd6e54 100644
--- a/src/config/ratelimit.ts
+++ b/src/config/ratelimit.ts
@@ -1,17 +1,21 @@
import { config } from "dotenv";
-import createHttpError from "http-errors";
-import { rateLimit } from "express-rate-limit";
+import { rateLimiter } from "hono-rate-limiter";
+import { getConnInfo } from "@hono/node-server/conninfo";
config();
-export const ratelimit = rateLimit({
- windowMs: Number(process.env.WINDOWMS) || 30 * 60 * 1000,
- limit: Number(process.env.MAX) || 6,
- legacyHeaders: true,
+export const ratelimit = rateLimiter({
+ windowMs: Number(process.env.ANIWATCH_API_WINDOW_MS) || 30 * 60 * 1000,
+ limit: Number(process.env.ANIWATCH_API_MAX_REQS) || 6,
standardHeaders: "draft-7",
- handler: function (_, __, next) {
- next(
- createHttpError.TooManyRequests("Too many API requests, try again later")
- );
+ keyGenerator(c) {
+ const { remote } = getConnInfo(c);
+ const key =
+ `${String(remote.addressType)}_` +
+ `${String(remote.address)}:${String(remote.port)}`;
+
+ return key;
},
+ handler: (c) =>
+ c.json({ status: 429, message: "Too Many Requests 😵" }, { status: 429 }),
});
diff --git a/src/controllers/animeAboutInfo.controller.ts b/src/controllers/animeAboutInfo.controller.ts
deleted file mode 100644
index 6d50b50..0000000
--- a/src/controllers/animeAboutInfo.controller.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import createHttpError from "http-errors";
-import { type RequestHandler } from "express";
-import { scrapeAnimeAboutInfo } from "../parsers/index.js";
-import { type AnimeAboutInfoQueryParams } from "../types/controllers/index.js";
-
-// /anime/info?id=${anime-id}
-const getAnimeAboutInfo: RequestHandler<
- unknown,
- Awaited<ReturnType<typeof scrapeAnimeAboutInfo>>,
- unknown,
- AnimeAboutInfoQueryParams
-> = async (req, res, next) => {
- try {
- const animeId = req.query.id
- ? decodeURIComponent(req.query.id as string)
- : null;
-
- if (animeId === null) {
- throw createHttpError.BadRequest("Anime unique id required");
- }
-
- const data = await scrapeAnimeAboutInfo(animeId);
-
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getAnimeAboutInfo;
diff --git a/src/controllers/animeCategory.controller.ts b/src/controllers/animeCategory.controller.ts
deleted file mode 100644
index 373b8e3..0000000
--- a/src/controllers/animeCategory.controller.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import createHttpError from "http-errors";
-import type { RequestHandler } from "express";
-import type { AnimeCategories } from "../types/anime.js";
-import { scrapeAnimeCategory } from "../parsers/index.js";
-import type {
- CategoryAnimePathParams,
- CategoryAnimeQueryParams,
-} from "../types/controllers/index.js";
-
-// /anime/:category?page=${page}
-const getAnimeCategory: RequestHandler<
- CategoryAnimePathParams,
- Awaited<ReturnType<typeof scrapeAnimeCategory>>,
- unknown,
- CategoryAnimeQueryParams
-> = async (req, res, next) => {
- try {
- const category = req.params.category
- ? decodeURIComponent(req.params.category)
- : null;
-
- const page: number = req.query.page
- ? Number(decodeURIComponent(req.query?.page as string))
- : 1;
-
- if (category === null) {
- throw createHttpError.BadRequest("category required");
- }
-
- const data = await scrapeAnimeCategory(category as AnimeCategories, page);
-
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getAnimeCategory;
diff --git a/src/controllers/animeEpisodeSrcs.controller.ts b/src/controllers/animeEpisodeSrcs.controller.ts
deleted file mode 100644
index 6190ce5..0000000
--- a/src/controllers/animeEpisodeSrcs.controller.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import axios from "axios";
-import createHttpError from "http-errors";
-import { type RequestHandler } from "express";
-import { type CheerioAPI, load } from "cheerio";
-import { scrapeAnimeEpisodeSources } from "../parsers/index.js";
-import { USER_AGENT_HEADER, SRC_BASE_URL } from "../utils/constants.js";
-import { type AnimeServers, Servers } from "../types/anime.js";
-import { type AnimeEpisodeSrcsQueryParams } from "../types/controllers/index.js";
-
-type AnilistID = number | null;
-type MalID = number | null;
-
-// /anime/episode-srcs?id=${episodeId}?server=${server}&category=${category (dub or sub)}
-const getAnimeEpisodeSources: RequestHandler<
- unknown,
- Awaited<ReturnType<typeof scrapeAnimeEpisodeSources & AnilistID>>,
- unknown,
- AnimeEpisodeSrcsQueryParams
-> = async (req, res, next) => {
- try {
- const episodeId = req.query.id ? decodeURIComponent(req.query.id) : null;
-
- const server = (
- req.query.server
- ? decodeURIComponent(req.query.server)
- : Servers.VidStreaming
- ) as AnimeServers;
-
- const category = (
- req.query.category ? decodeURIComponent(req.query.category) : "sub"
- ) as "sub" | "dub";
-
- if (episodeId === null) {
- throw createHttpError.BadRequest("Anime episode id required");
- }
-
- let malID: MalID;
- let anilistID: AnilistID;
- const animeURL = new URL(episodeId?.split("?ep=")[0], SRC_BASE_URL)?.href;
-
- const [episodeSrcData, animeSrc] = await Promise.all([
- scrapeAnimeEpisodeSources(episodeId, server, category),
- axios.get(animeURL, {
- headers: {
- Referer: SRC_BASE_URL,
- "User-Agent": USER_AGENT_HEADER,
- "X-Requested-With": "XMLHttpRequest",
- },
- }),
- ]);
-
- const $: CheerioAPI = load(animeSrc?.data);
-
- try {
- anilistID = Number(
- JSON.parse($("body")?.find("#syncData")?.text())?.anilist_id
- );
- malID = Number(JSON.parse($("body")?.find("#syncData")?.text())?.mal_id);
- } catch (err) {
- anilistID = null;
- malID = null;
- }
-
- res.status(200).json({
- ...episodeSrcData,
- anilistID,
- malID,
- });
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getAnimeEpisodeSources;
diff --git a/src/controllers/animeEpisodes.controller.ts b/src/controllers/animeEpisodes.controller.ts
deleted file mode 100644
index cf815f5..0000000
--- a/src/controllers/animeEpisodes.controller.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import createHttpError from "http-errors";
-import { type RequestHandler } from "express";
-import { scrapeAnimeEpisodes } from "../parsers/index.js";
-import { type AnimeEpisodePathParams } from "../types/controllers/index.js";
-
-// /anime/episodes/${anime-id}
-const getAnimeEpisodes: RequestHandler<
- AnimeEpisodePathParams,
- Awaited<ReturnType<typeof scrapeAnimeEpisodes>>,
- unknown,
- unknown
-> = async (req, res, next) => {
- try {
- const animeId = req.params.animeId
- ? decodeURIComponent(req.params.animeId)
- : null;
-
- if (animeId === null) {
- throw createHttpError.BadRequest("Anime Id required");
- }
-
- const data = await scrapeAnimeEpisodes(animeId);
-
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getAnimeEpisodes;
diff --git a/src/controllers/animeGenre.controller.ts b/src/controllers/animeGenre.controller.ts
deleted file mode 100644
index 486b4c7..0000000
--- a/src/controllers/animeGenre.controller.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import createHttpError from "http-errors";
-import { type RequestHandler } from "express";
-import { scrapeGenreAnime } from "../parsers/index.js";
-import type {
- GenreAnimePathParams,
- GenreAnimeQueryParams,
-} from "../types/controllers/index.js";
-
-// /anime/genre/${name}?page=${page}
-const getGenreAnime: RequestHandler<
- GenreAnimePathParams,
- Awaited<ReturnType<typeof scrapeGenreAnime>>,
- unknown,
- GenreAnimeQueryParams
-> = async (req, res, next) => {
- try {
- const name: string | null = req.params.name
- ? decodeURIComponent(req.params.name as string)
- : null;
-
- const page: number = req.query.page
- ? Number(decodeURIComponent(req.query?.page as string))
- : 1;
-
- if (name === null) {
- throw createHttpError.BadRequest("Anime genre required");
- }
-
- const data = await scrapeGenreAnime(name, page);
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getGenreAnime;
diff --git a/src/controllers/animeProducer.controller.ts b/src/controllers/animeProducer.controller.ts
deleted file mode 100644
index 3ebcd8a..0000000
--- a/src/controllers/animeProducer.controller.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import createHttpError from "http-errors";
-import { type RequestHandler } from "express";
-import { scrapeProducerAnimes } from "../parsers/index.js";
-import type {
- AnimeProducerPathParams,
- AnimeProducerQueryParams,
-} from "../types/controllers/index.js";
-
-// /anime/producer/${name}?page=${page}
-const getProducerAnimes: RequestHandler<
- AnimeProducerPathParams,
- Awaited<ReturnType<typeof scrapeProducerAnimes>>,
- unknown,
- AnimeProducerQueryParams
-> = async (req, res, next) => {
- try {
- const name: string | null = req.params.name
- ? decodeURIComponent(req.params.name as string)
- : null;
-
- const page: number = req.query.page
- ? Number(decodeURIComponent(req.query?.page as string))
- : 1;
-
- if (name === null) {
- throw createHttpError.BadRequest("Anime producer name required");
- }
-
- const data = await scrapeProducerAnimes(name, page);
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getProducerAnimes;
diff --git a/src/controllers/animeSearch.controller.ts b/src/controllers/animeSearch.controller.ts
deleted file mode 100644
index 8937b0a..0000000
--- a/src/controllers/animeSearch.controller.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import createHttpError from "http-errors";
-import { type RequestHandler } from "express";
-import { scrapeAnimeSearch } from "../parsers/index.js";
-import type {
- SearchFilters,
- AnimeSearchQueryParams,
-} from "../types/controllers/index.js";
-
-const searchFilters: Record<string, boolean> = {
- filter: true,
- type: true,
- status: true,
- rated: true,
- score: true,
- season: true,
- language: true,
- start_date: true,
- end_date: true,
- sort: true,
- genres: true,
-} as const;
-
-// /anime/search?q=${query}&page=${page}
-const getAnimeSearch: RequestHandler<
- unknown,
- Awaited<ReturnType<typeof scrapeAnimeSearch>>,
- unknown,
- AnimeSearchQueryParams
-> = async (req, res, next) => {
- try {
- let { q: query, page, ...filters } = req.query;
-
- query = query ? decodeURIComponent(query) : undefined;
- const pageNo = page ? Number(decodeURIComponent(page as string)) : 1;
-
- if (query === undefined) {
- throw createHttpError.BadRequest("Search keyword required");
- }
-
- const parsedFilters: SearchFilters = {};
- for (const key in filters) {
- if (searchFilters[key]) {
- parsedFilters[key as keyof SearchFilters] =
- filters[key as keyof SearchFilters];
- }
- }
-
- const data = await scrapeAnimeSearch(query, pageNo, parsedFilters);
-
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getAnimeSearch;
diff --git a/src/controllers/animeSearchSuggestion.controller.ts b/src/controllers/animeSearchSuggestion.controller.ts
deleted file mode 100644
index ed8784f..0000000
--- a/src/controllers/animeSearchSuggestion.controller.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import createHttpError from "http-errors";
-import { type RequestHandler } from "express";
-import { scrapeAnimeSearchSuggestion } from "../parsers/index.js";
-import { type AnimeSearchSuggestQueryParams } from "../types/controllers/index.js";
-
-// /anime/search/suggest?q=${query}
-const getAnimeSearchSuggestion: RequestHandler<
- unknown,
- Awaited<ReturnType<typeof scrapeAnimeSearchSuggestion>>,
- unknown,
- AnimeSearchSuggestQueryParams
-> = async (req, res, next) => {
- try {
- const query: string | null = req.query.q
- ? decodeURIComponent(req.query.q as string)
- : null;
-
- if (query === null) {
- throw createHttpError.BadRequest("Search keyword required");
- }
-
- const data = await scrapeAnimeSearchSuggestion(query);
-
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getAnimeSearchSuggestion;
diff --git a/src/controllers/episodeServers.controller.ts b/src/controllers/episodeServers.controller.ts
deleted file mode 100644
index 16e1ca5..0000000
--- a/src/controllers/episodeServers.controller.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import createHttpError from "http-errors";
-import { type RequestHandler } from "express";
-import { scrapeEpisodeServers } from "../parsers/index.js";
-import { type EpisodeServersQueryParams } from "../types/controllers/index.js";
-
-// /anime/servers?episodeId=${id}
-const getEpisodeServers: RequestHandler<
- unknown,
- Awaited<ReturnType<typeof scrapeEpisodeServers>>,
- unknown,
- EpisodeServersQueryParams
-> = async (req, res, next) => {
- try {
- const episodeId = req.query.episodeId
- ? decodeURIComponent(req.query?.episodeId as string)
- : null;
-
- if (episodeId === null) {
- throw createHttpError.BadRequest("Episode id required");
- }
-
- const data = await scrapeEpisodeServers(episodeId);
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getEpisodeServers;
diff --git a/src/controllers/estimatedSchedule.controller.ts b/src/controllers/estimatedSchedule.controller.ts
deleted file mode 100644
index fef2516..0000000
--- a/src/controllers/estimatedSchedule.controller.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import createHttpError from "http-errors";
-import { type RequestHandler } from "express";
-import { scrapeEstimatedSchedule } from "../parsers/index.js";
-import { type EstimatedScheduleQueryParams } from "../types/controllers/index.js";
-
-// /anime/schedule?date=${date}
-const getEstimatedSchedule: RequestHandler<
- unknown,
- Awaited<ReturnType<typeof scrapeEstimatedSchedule>>,
- unknown,
- EstimatedScheduleQueryParams
-> = async (req, res, next) => {
- try {
- const dateQuery = req.query.date
- ? decodeURIComponent(req.query.date as string)
- : null;
-
- if (dateQuery === null) {
- throw createHttpError.BadRequest("Date payload required");
- }
- if (!/^\d{4}-\d{2}-\d{2}$/.test(dateQuery)) {
- throw createHttpError.BadRequest(
- "Invalid date payload format. Months and days must have 2 digits"
- );
- }
-
- const data = await scrapeEstimatedSchedule(dateQuery);
-
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getEstimatedSchedule;
diff --git a/src/controllers/homePage.controller.ts b/src/controllers/homePage.controller.ts
deleted file mode 100644
index 36e1fe9..0000000
--- a/src/controllers/homePage.controller.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { type RequestHandler } from "express";
-import { scrapeHomePage } from "../parsers/index.js";
-
-// /anime/home
-const getHomePageInfo: RequestHandler<
- unknown,
- Awaited<ReturnType<typeof scrapeHomePage>>
-> = async (req, res, next) => {
- try {
- const data = await scrapeHomePage();
- res.status(200).json(data);
- } catch (err: any) {
- console.error(err);
- next(err);
- }
-};
-
-export default getHomePageInfo;
diff --git a/src/controllers/index.ts b/src/controllers/index.ts
deleted file mode 100644
index 6e90440..0000000
--- a/src/controllers/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import getGenreAnime from "./animeGenre.controller.js";
-import getHomePageInfo from "./homePage.controller.js";
-import getAnimeSearch from "./animeSearch.controller.js";
-import getAnimeEpisodes from "./animeEpisodes.controller.js";
-import getAnimeCategory from "./animeCategory.controller.js";
-import getProducerAnimes from "./animeProducer.controller.js";
-import getEpisodeServers from "./episodeServers.controller.js";
-import getAnimeAboutInfo from "./animeAboutInfo.controller.js";
-import getEstimatedSchedule from "./estimatedSchedule.controller.js";
-import getAnimeEpisodeSources from "./animeEpisodeSrcs.controller.js";
-import getAnimeSearchSuggestion from "./animeSearchSuggestion.controller.js";
-
-export {
- getGenreAnime,
- getAnimeSearch,
- getHomePageInfo,
- getAnimeEpisodes,
- getAnimeCategory,
- getEpisodeServers,
- getProducerAnimes,
- getAnimeAboutInfo,
- getEstimatedSchedule,
- getAnimeEpisodeSources,
- getAnimeSearchSuggestion,
-};
diff --git a/src/extractors/index.ts b/src/extractors/index.ts
deleted file mode 100644
index 788dc8c..0000000
--- a/src/extractors/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import StreamSB from "./streamsb.js";
-import StreamTape from "./streamtape.js";
-import RapidCloud from "./rapidcloud.js";
-import MegaCloud from "./megacloud.js";
-
-export { StreamSB, StreamTape, RapidCloud, MegaCloud };
diff --git a/src/extractors/megacloud.ts b/src/extractors/megacloud.ts
deleted file mode 100644
index bbed720..0000000
--- a/src/extractors/megacloud.ts
+++ /dev/null
@@ -1,226 +0,0 @@
-import axios from "axios";
-import crypto from "crypto";
-import createHttpError from "http-errors";
-
-// https://megacloud.tv/embed-2/e-1/dBqCr5BcOhnD?k=1
-
-const 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=",
-} as const;
-
-type track = {
- file: string;
- kind: string;
- label?: string;
- default?: boolean;
-};
-
-type intro_outro = {
- start: number;
- end: number;
-};
-
-type unencryptedSrc = {
- file: string;
- type: string;
-};
-
-type extractedSrc = {
- sources: string | unencryptedSrc[];
- tracks: track[];
- encrypted: boolean;
- intro: intro_outro;
- outro: intro_outro;
- server: number;
-};
-
-interface ExtractedData
- extends Pick<extractedSrc, "intro" | "outro" | "tracks"> {
- sources: { url: string; type: string }[];
-}
-
-class MegaCloud {
- private serverName = "megacloud";
-
- async extract(videoUrl: URL) {
- try {
- const extractedData: 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 axios.get<extractedSrc>(
- 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 createHttpError.NotFound("Url may have an invalid video id");
- }
-
- // console.log(JSON.stringify(srcsData, null, 2));
-
- 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: string;
- const { data } = await axios.get(
- megacloud.script.concat(Date.now().toString())
- );
-
- text = data;
- if (!text) {
- throw createHttpError.InternalServerError(
- "Couldn't fetch script to decrypt resource"
- );
- }
-
- 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 as string,
- vars
- );
- const decrypted = this.decrypt(encryptedSource, secret);
- try {
- const sources = JSON.parse(decrypted);
- extractedData.intro = srcsData.intro;
- extractedData.outro = srcsData.outro;
- extractedData.tracks = srcsData.tracks;
- extractedData.sources = sources.map((s: any) => ({
- url: s.file,
- type: s.type,
- }));
-
- return extractedData;
- } catch (error) {
- throw createHttpError.InternalServerError("Failed to decrypt resource");
- }
- } catch (err) {
- // console.log(err);
- throw err;
- }
- }
-
- extractVariables(text: string) {
- // copied from github issue #30 'https://github.com/ghoshRitesh12/aniwatch-api/issues/30'
- const regex =
- /case\s*0x[0-9a-f]+:(?![^;]*=partKey)\s*\w+\s*=\s*(\w+)\s*,\s*\w+\s*=\s*(\w+);/g;
- const matches = text.matchAll(regex);
- const vars = Array.from(matches, (match) => {
- const matchKey1 = this.matchingKey(match[1], text);
- const matchKey2 = this.matchingKey(match[2], text);
- try {
- return [parseInt(matchKey1, 16), parseInt(matchKey2, 16)];
- } catch (e) {
- return [];
- }
- }).filter((pair) => pair.length > 0);
-
- return vars;
- }
-
- getSecret(encryptedString: string, values: number[][]) {
- let secret = "",
- encryptedSource = "",
- encryptedSourceArray = encryptedString.split(""),
- currentIndex = 0;
-
- for (const index of values) {
- const start = index[0] + currentIndex;
- const end = start + index[1];
-
- for (let i = start; i < end; i++) {
- secret += encryptedString[i];
- encryptedSourceArray[i] = "";
- }
- currentIndex += index[1];
- }
-
- encryptedSource = encryptedSourceArray.join("");
-
- return { secret, encryptedSource };
- }
-
- decrypt(encrypted: string, keyOrSecret: string, maybe_iv?: string) {
- let key;
- let iv;
- let contents;
- if (maybe_iv) {
- key = keyOrSecret;
- iv = maybe_iv;
- contents = encrypted;
- } else {
- // copied from 'https://github.com/brix/crypto-js/issues/468'
- const cypher = Buffer.from(encrypted, "base64");
- const salt = cypher.subarray(8, 16);
- const password = Buffer.concat([
- Buffer.from(keyOrSecret, "binary"),
- salt,
- ]);
- const md5Hashes = [];
- let digest = password;
- for (let i = 0; i < 3; i++) {
- md5Hashes[i] = crypto.createHash("md5").update(digest).digest();
- digest = Buffer.concat([md5Hashes[i], password]);
- }
- key = Buffer.concat([md5Hashes[0], md5Hashes[1]]);
- iv = md5Hashes[2];
- contents = cypher.subarray(16);
- }
-
- const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
- const decrypted =
- decipher.update(
- contents as any,
- typeof contents === "string" ? "base64" : undefined,
- "utf8"
- ) + decipher.final();
-
- return decrypted;
- }
-
- // function copied from github issue #30 'https://github.com/ghoshRitesh12/aniwatch-api/issues/30'
- matchingKey(value: string, script: string) {
- const regex = new RegExp(`,${value}=((?:0x)?([0-9a-fA-F]+))`);
- const match = script.match(regex);
- if (match) {
- return match[1].replace(/^0x/, "");
- } else {
- throw new Error("Failed to match the key");
- }
- }
-
-}
-
-export default MegaCloud;
diff --git a/src/extractors/rapidcloud.ts b/src/extractors/rapidcloud.ts
deleted file mode 100644
index 8073c8b..0000000
--- a/src/extractors/rapidcloud.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-import axios from "axios";
-import CryptoJS from "crypto-js";
-import { substringAfter, substringBefore } from "../utils/index.js";
-import type { Video, Subtitle, Intro } from "../types/extractor.js";
-
-type extractReturn = {
- sources: Video[];
- subtitles: Subtitle[];
-};
-
-// https://megacloud.tv/embed-2/e-1/IxJ7GjGVCyml?k=1
-class RapidCloud {
- private serverName = "RapidCloud";
- private sources: Video[] = [];
-
- // https://rapid-cloud.co/embed-6/eVZPDXwVfrY3?vast=1
- private readonly fallbackKey = "c1d17096f2ca11b7";
- private readonly host = "https://rapid-cloud.co";
-
- async extract(videoUrl: URL): Promise<extractReturn> {
- const result: extractReturn & { intro?: Intro; outro?: Intro } = {
- sources: [],
- subtitles: [],
- };
-
- try {
- const id = videoUrl.href.split("/").pop()?.split("?")[0];
- const options = {
- headers: {
- "X-Requested-With": "XMLHttpRequest",
- },
- };
-
- let res = null;
-
- res = await axios.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 axios.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 axios.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: any) {
- console.log(err.message);
- throw new Error("Cannot decrypt sources. Perhaps the key is invalid.");
- }
-
- this.sources = sources?.map((s: any) => ({
- 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 axios.get(source.file, options);
- const m3u8data = data
- .split("\n")
- .filter(
- (line: string) =>
- line.includes(".m3u8") && line.includes("RESOLUTION=")
- );
-
- const secondHalf = m3u8data.map((line: string) =>
- line.match(/RESOLUTION=.*,(C)|URI=.*/g)?.map((s) => s.split("=")[1])
- );
-
- const TdArray = secondHalf.map((s: string[]) => {
- 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 } : undefined;
- result.outro =
- outro?.end > 1 ? { start: outro.start, end: outro.end } : undefined;
-
- result.sources.push({
- url: sources[0].file,
- isM3U8: sources[0].file.includes(".m3u8"),
- quality: "auto",
- });
-
- result.subtitles = tracks
- .map((s: any) =>
- s.file
- ? { url: s.file, lang: s.label ? s.label : "Thumbnails" }
- : null
- )
- .filter((s: any) => s);
-
- return result;
- } catch (err: any) {
- console.log(err.message);
- throw err;
- }
- }
-}
-
-export default RapidCloud;
diff --git a/src/extractors/streamsb.ts b/src/extractors/streamsb.ts
deleted file mode 100644
index 3eeaabe..0000000
--- a/src/extractors/streamsb.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import axios from "axios";
-import type { Video } from "../types/extractor.js";
-import { USER_AGENT_HEADER } from "../utils/index.js";
-
-class StreamSB {
- private serverName = "streamSB";
- private sources: Video[] = [];
-
- private readonly host = "https://watchsb.com/sources50";
- private readonly host2 = "https://streamsss.net/sources16";
-
- private PAYLOAD(hex: string): string {
- // `5363587530696d33443675687c7c${hex}7c7c433569475830474c497a65767c7c73747265616d7362`;
- return `566d337678566f743674494a7c7c${hex}7c7c346b6767586d6934774855537c7c73747265616d7362/6565417268755339773461447c7c346133383438333436313335376136323337373433383634376337633465366534393338373136643732373736343735373237613763376334363733353737303533366236333463353333363534366137633763373337343732363536313664373336327c7c6b586c3163614468645a47617c7c73747265616d7362`;
- }
-
- async extract(videoUrl: URL, isAlt: boolean = false): Promise<Video[]> {
- let headers: Record<string, string> = {
- 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 axios
- .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 axios.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: 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"),
- });
- }
-}
-
-export default StreamSB;
diff --git a/src/extractors/streamtape.ts b/src/extractors/streamtape.ts
deleted file mode 100644
index 69910ce..0000000
--- a/src/extractors/streamtape.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import axios from "axios";
-import { load, type CheerioAPI } from "cheerio";
-import type { Video } from "../types/extractor.js";
-
-class StreamTape {
- private serverName = "StreamTape";
- private sources: Video[] = [];
-
- async extract(videoUrl: URL): Promise<Video[]> {
- try {
- const { data } = await axios.get(videoUrl.href).catch(() => {
- throw new Error("Video not found");
- });
-
- const $: CheerioAPI = load(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: url,
- isM3U8: url.includes(".m3u8"),
- });
-
- return this.sources;
- } catch (err) {
- throw new Error((err as Error).message);
- }
- }
-}
-export default StreamTape;
diff --git a/src/parsers/animeAboutInfo.ts b/src/parsers/animeAboutInfo.ts
deleted file mode 100644
index f92b0fc..0000000
--- a/src/parsers/animeAboutInfo.ts
+++ /dev/null
@@ -1,229 +0,0 @@
-import {
- SRC_BASE_URL,
- extractAnimes,
- ACCEPT_HEADER,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
- extractMostPopularAnimes,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import createHttpError, { type HttpError } from "http-errors";
-import { load, type CheerioAPI, type SelectorType } from "cheerio";
-import { type ScrapedAnimeAboutInfo } from "../types/parsers/index.js";
-
-// /anime/info?id=${anime-id}
-async function scrapeAnimeAboutInfo(
- id: string
-): Promise<ScrapedAnimeAboutInfo | HttpError> {
- const res: ScrapedAnimeAboutInfo = {
- 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 {
- const animeUrl: URL = new URL(id, SRC_BASE_URL);
- const mainPage = await axios.get(animeUrl.href, {
- headers: {
- "User-Agent": USER_AGENT_HEADER,
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- Accept: ACCEPT_HEADER,
- },
- });
-
- const $: CheerioAPI = load(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: SelectorType = "#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;
-
- // stats
- 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;
-
- // get promotional videos
- $(".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"),
- });
- }
- );
-
- // get characters and voice actors
- $(".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(),
- },
- });
- }
- );
-
- // more information
- $(`${selector} .anisc-info-wrap .anisc-info .item:not(.w-hide)`).each(
- (i, 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((i, el) => $(el).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;
- }
- );
-
- // more seasons
- const seasonsSelector: SelectorType = "#main-content .os-list a.os-item";
- $(seasonsSelector).each((i, 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: SelectorType =
- "#main-sidebar .block_area.block_area_sidebar.block_area-realtime:nth-of-type(1) .anif-block-ul ul li";
- res.relatedAnimes = extractMostPopularAnimes($, relatedAnimeSelector);
-
- const mostPopularSelector: SelectorType =
- "#main-sidebar .block_area.block_area_sidebar.block_area-realtime:nth-of-type(2) .anif-block-ul ul li";
- res.mostPopularAnimes = extractMostPopularAnimes($, mostPopularSelector);
-
- const recommendedAnimeSelector: SelectorType =
- "#main-content .block_area.block_area_category .tab-content .flw-item";
- res.recommendedAnimes = extractAnimes($, recommendedAnimeSelector);
-
- return res;
- } catch (err: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeAnimeAboutInfo;
diff --git a/src/parsers/animeCategory.ts b/src/parsers/animeCategory.ts
deleted file mode 100644
index c79fcc7..0000000
--- a/src/parsers/animeCategory.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import {
- SRC_BASE_URL,
- extractAnimes,
- ACCEPT_HEADER,
- USER_AGENT_HEADER,
- extractTop10Animes,
- ACCEPT_ENCODING_HEADER,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import { type AnimeCategories } from "../types/anime.js";
-import createHttpError, { type HttpError } from "http-errors";
-import { load, type CheerioAPI, type SelectorType } from "cheerio";
-import { type ScrapedAnimeCategory } from "../types/parsers/index.js";
-
-// /anime/:category?page=${page}
-async function scrapeAnimeCategory(
- category: AnimeCategories,
- page: number = 1
-): Promise<ScrapedAnimeCategory | HttpError> {
- const res: ScrapedAnimeCategory = {
- animes: [],
- genres: [],
- top10Animes: {
- today: [],
- week: [],
- month: [],
- },
- category,
- currentPage: Number(page),
- hasNextPage: false,
- totalPages: 1,
- };
-
- try {
- const scrapeUrl: URL = new URL(category, SRC_BASE_URL);
- const mainPage = await axios.get(`${scrapeUrl}?page=${page}`, {
- headers: {
- "User-Agent": USER_AGENT_HEADER,
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- Accept: ACCEPT_HEADER,
- },
- });
-
- const $: CheerioAPI = load(mainPage.data);
-
- const selector: SelectorType =
- "#main-content .tab-content .film_list-wrap .flw-item";
-
- const categoryNameSelector: SelectorType =
- "#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);
-
- if (res.animes.length === 0 && !res.hasNextPage) {
- res.totalPages = 0;
- }
-
- const genreSelector: SelectorType =
- "#main-sidebar .block_area.block_area_sidebar.block_area-genres .sb-genre-list li";
- $(genreSelector).each((i, el) => {
- res.genres.push(`${$(el).text().trim()}`);
- });
-
- const top10AnimeSelector: SelectorType =
- '#main-sidebar .block_area-realtime [id^="top-viewed-"]';
-
- $(top10AnimeSelector).each((i, el) => {
- const period = $(el).attr("id")?.split("-")?.pop()?.trim();
-
- if (period === "day") {
- res.top10Animes.today = extractTop10Animes($, period);
- return;
- }
- if (period === "week") {
- res.top10Animes.week = extractTop10Animes($, period);
- return;
- }
- if (period === "month") {
- res.top10Animes.month = extractTop10Animes($, period);
- }
- });
-
- return res;
- } catch (err: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeAnimeCategory;
diff --git a/src/parsers/animeEpisodeSrcs.ts b/src/parsers/animeEpisodeSrcs.ts
deleted file mode 100644
index 913c535..0000000
--- a/src/parsers/animeEpisodeSrcs.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import {
- SRC_AJAX_URL,
- SRC_BASE_URL,
- retrieveServerId,
- USER_AGENT_HEADER,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import { load, type CheerioAPI } from "cheerio";
-import createHttpError, { type HttpError } from "http-errors";
-import { type AnimeServers, Servers } from "../types/anime.js";
-import {
- RapidCloud,
- StreamSB,
- StreamTape,
- MegaCloud,
-} from "../extractors/index.js";
-import { type ScrapedAnimeEpisodesSources } from "../types/parsers/index.js";
-
-// vidtreaming -> 4
-// rapidcloud -> 1
-// streamsb -> 5
-// streamtape -> 3
-
-// /anime/episode-srcs?id=${episodeId}?server=${server}&category=${category (dub or sub)}
-async function scrapeAnimeEpisodeSources(
- episodeId: string,
- server: AnimeServers = Servers.VidStreaming,
- category: "sub" | "dub" | "raw" = "sub"
-): Promise<ScrapedAnimeEpisodesSources | HttpError> {
- if (episodeId.startsWith("http")) {
- const serverUrl = new URL(episodeId);
- switch (server) {
- case Servers.VidStreaming:
- case Servers.VidCloud:
- return {
- ...(await new MegaCloud().extract(serverUrl)),
- };
- case Servers.StreamSB:
- return {
- headers: {
- Referer: serverUrl.href,
- watchsb: "streamsb",
- "User-Agent": USER_AGENT_HEADER,
- },
- sources: await new StreamSB().extract(serverUrl, true),
- };
- case Servers.StreamTape:
- return {
- headers: { Referer: serverUrl.href, "User-Agent": USER_AGENT_HEADER },
- sources: await new StreamTape().extract(serverUrl),
- };
- default: // vidcloud
- return {
- headers: { Referer: serverUrl.href },
- ...(await new RapidCloud().extract(serverUrl)),
- };
- }
- }
-
- const epId = new URL(`/watch/${episodeId}`, SRC_BASE_URL).href;
- console.log(epId);
-
- try {
- const resp = await axios.get(
- `${SRC_AJAX_URL}/v2/episode/servers?episodeId=${epId.split("?ep=")[1]}`,
- {
- headers: {
- Referer: epId,
- "User-Agent": USER_AGENT_HEADER,
- "X-Requested-With": "XMLHttpRequest",
- },
- }
- );
-
- const $: CheerioAPI = load(resp.data.html);
-
- let serverId: string | null = null;
-
- try {
- console.log("THE SERVER: ", server);
-
- switch (server) {
- case Servers.VidCloud: {
- serverId = retrieveServerId($, 1, category);
- if (!serverId) throw new Error("RapidCloud not found");
- break;
- }
- case Servers.VidStreaming: {
- serverId = retrieveServerId($, 4, category);
- console.log("SERVER_ID: ", serverId);
- if (!serverId) throw new Error("VidStreaming not found");
- break;
- }
- case Servers.StreamSB: {
- serverId = retrieveServerId($, 5, category);
- if (!serverId) throw new Error("StreamSB not found");
- break;
- }
- case Servers.StreamTape: {
- serverId = retrieveServerId($, 3, category);
- if (!serverId) throw new Error("StreamTape not found");
- break;
- }
- }
- } catch (err) {
- throw createHttpError.NotFound(
- "Couldn't find server. Try another server"
- );
- }
-
- const {
- data: { link },
- } = await axios.get(`${SRC_AJAX_URL}/v2/episode/sources?id=${serverId}`);
- console.log("THE LINK: ", link);
-
- return await scrapeAnimeEpisodeSources(link, server);
- } catch (err: any) {
- console.log(err);
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeAnimeEpisodeSources;
diff --git a/src/parsers/animeEpisodes.ts b/src/parsers/animeEpisodes.ts
deleted file mode 100644
index 41ac942..0000000
--- a/src/parsers/animeEpisodes.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import {
- SRC_BASE_URL,
- SRC_AJAX_URL,
- ACCEPT_HEADER,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import { load, type CheerioAPI } from "cheerio";
-import createHttpError, { type HttpError } from "http-errors";
-import { type ScrapedAnimeEpisodes } from "../types/parsers/index.js";
-
-// /anime/episodes/${anime-id}
-async function scrapeAnimeEpisodes(
- animeId: string
-): Promise<ScrapedAnimeEpisodes | HttpError> {
- const res: ScrapedAnimeEpisodes = {
- totalEpisodes: 0,
- episodes: [],
- };
-
- try {
- const episodesAjax = await axios.get(
- `${SRC_AJAX_URL}/v2/episode/list/${animeId.split("-").pop()}`,
- {
- headers: {
- Accept: ACCEPT_HEADER,
- "User-Agent": USER_AGENT_HEADER,
- "X-Requested-With": "XMLHttpRequest",
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- Referer: `${SRC_BASE_URL}/watch/${animeId}`,
- },
- }
- );
-
- const $: CheerioAPI = load(episodesAjax.data.html);
-
- res.totalEpisodes = Number($(".detail-infor-content .ss-list a").length);
-
- $(".detail-infor-content .ss-list a").each((i, 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: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeAnimeEpisodes;
diff --git a/src/parsers/animeGenre.ts b/src/parsers/animeGenre.ts
deleted file mode 100644
index 110398d..0000000
--- a/src/parsers/animeGenre.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import {
- SRC_BASE_URL,
- ACCEPT_HEADER,
- extractAnimes,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
- extractMostPopularAnimes,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import createHttpError, { type HttpError } from "http-errors";
-import { load, type CheerioAPI, type SelectorType } from "cheerio";
-import type { ScrapedGenreAnime } from "../types/parsers/index.js";
-
-// /anime/genre/${name}?page=${page}
-async function scrapeGenreAnime(
- genreName: string,
- page: number = 1
-): Promise<ScrapedGenreAnime | HttpError> {
- const res: ScrapedGenreAnime = {
- genreName,
- animes: [],
- genres: [],
- topAiringAnimes: [],
- totalPages: 1,
- hasNextPage: false,
- currentPage: Number(page),
- };
-
- // there's a typo with zoro where martial arts is marial arts
- genreName = genreName === "martial-arts" ? "marial-arts" : genreName;
-
- try {
- const genreUrl: URL = new URL(
- `/genre/${genreName}?page=${page}`,
- SRC_BASE_URL
- );
-
- const mainPage = await axios.get(genreUrl.href, {
- headers: {
- "User-Agent": USER_AGENT_HEADER,
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- Accept: ACCEPT_HEADER,
- },
- });
-
- const $: CheerioAPI = load(mainPage.data);
-
- const selector: SelectorType =
- "#main-content .tab-content .film_list-wrap .flw-item";
-
- const genreNameSelector: SelectorType =
- "#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);
-
- if (res.animes.length === 0 && !res.hasNextPage) {
- res.totalPages = 0;
- }
-
- const genreSelector: SelectorType =
- "#main-sidebar .block_area.block_area_sidebar.block_area-genres .sb-genre-list li";
- $(genreSelector).each((i, el) => {
- res.genres.push(`${$(el).text().trim()}`);
- });
-
- const topAiringSelector: SelectorType =
- "#main-sidebar .block_area.block_area_sidebar.block_area-realtime .anif-block-ul ul li";
- res.topAiringAnimes = extractMostPopularAnimes($, topAiringSelector);
-
- return res;
- } catch (err: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeGenreAnime;
diff --git a/src/parsers/animeProducer.ts b/src/parsers/animeProducer.ts
deleted file mode 100644
index 2862852..0000000
--- a/src/parsers/animeProducer.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import {
- SRC_BASE_URL,
- ACCEPT_HEADER,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
- extractMostPopularAnimes,
- extractAnimes,
- extractTop10Animes,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import createHttpError, { type HttpError } from "http-errors";
-import { load, type CheerioAPI, type SelectorType } from "cheerio";
-import type { ScrapedProducerAnime } from "../types/parsers/index.js";
-
-// /anime/producer/${name}?page=${page}
-async function scrapeProducerAnimes(
- producerName: string,
- page: number = 1
-): Promise<ScrapedProducerAnime | HttpError> {
- const res: ScrapedProducerAnime = {
- producerName,
- animes: [],
- top10Animes: {
- today: [],
- week: [],
- month: [],
- },
- topAiringAnimes: [],
- totalPages: 1,
- hasNextPage: false,
- currentPage: Number(page),
- };
-
- try {
- const producerUrl: URL = new URL(
- `/producer/${producerName}?page=${page}`,
- SRC_BASE_URL
- );
-
- const mainPage = await axios.get(producerUrl.href, {
- headers: {
- Accept: ACCEPT_HEADER,
- "User-Agent": USER_AGENT_HEADER,
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- },
- });
-
- const $: CheerioAPI = load(mainPage.data);
-
- const animeSelector: SelectorType =
- "#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);
-
- if (res.animes.length === 0 && !res.hasNextPage) {
- res.totalPages = 0;
- }
-
- const producerNameSelector: SelectorType =
- "#main-content .block_area .block_area-header .cat-heading";
- res.producerName = $(producerNameSelector)?.text()?.trim() ?? producerName;
-
- const top10AnimeSelector: SelectorType =
- '#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);
- return;
- }
- if (period === "week") {
- res.top10Animes.week = extractTop10Animes($, period);
- return;
- }
- if (period === "month") {
- res.top10Animes.month = extractTop10Animes($, period);
- }
- });
-
- const topAiringSelector: SelectorType =
- "#main-sidebar .block_area_sidebar:nth-child(2) .block_area-content .anif-block-ul ul li";
- res.topAiringAnimes = extractMostPopularAnimes($, topAiringSelector);
-
- return res;
- } catch (err: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeProducerAnimes;
diff --git a/src/parsers/animeSearch.ts b/src/parsers/animeSearch.ts
deleted file mode 100644
index 16818c6..0000000
--- a/src/parsers/animeSearch.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import {
- SRC_SEARCH_URL,
- ACCEPT_HEADER,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
- extractAnimes,
- getSearchFilterValue,
- extractMostPopularAnimes,
- getSearchDateFilterValue,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import createHttpError, { type HttpError } from "http-errors";
-import { load, type CheerioAPI, type SelectorType } from "cheerio";
-import type { ScrapedAnimeSearchResult } from "../types/parsers/index.js";
-import type { SearchFilters, FilterKeys } from "../types/controllers/index.js";
-
-// /anime/search?q=${query}&page=${page}
-async function scrapeAnimeSearch(
- q: string,
- page: number = 1,
- filters: SearchFilters
-): Promise<ScrapedAnimeSearchResult | HttpError> {
- const res: ScrapedAnimeSearchResult = {
- animes: [],
- mostPopularAnimes: [],
- currentPage: Number(page),
- hasNextPage: false,
- totalPages: 1,
- searchQuery: q,
- searchFilters: filters,
- };
-
- try {
- const url = new URL(SRC_SEARCH_URL);
- url.searchParams.set("keyword", q);
- url.searchParams.set("page", `${page}`);
- url.searchParams.set("sort", "default");
-
- for (const key in filters) {
- if (key.includes("_date")) {
- const dates = getSearchDateFilterValue(
- key === "start_date",
- filters[key as keyof SearchFilters] || ""
- );
- if (!dates) continue;
-
- dates.map((dateParam) => {
- const [key, val] = dateParam.split("=");
- url.searchParams.set(key, val);
- });
- continue;
- }
-
- const filterVal = getSearchFilterValue(
- key as FilterKeys,
- filters[key as keyof SearchFilters] || ""
- );
- filterVal && url.searchParams.set(key, filterVal);
- }
-
- const mainPage = await axios.get(url.href, {
- headers: {
- "User-Agent": USER_AGENT_HEADER,
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- Accept: ACCEPT_HEADER,
- },
- });
-
- const $: CheerioAPI = load(mainPage.data);
-
- const selector: SelectorType =
- "#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);
-
- if (res.animes.length === 0 && !res.hasNextPage) {
- res.totalPages = 0;
- }
-
- const mostPopularSelector: SelectorType =
- "#main-sidebar .block_area.block_area_sidebar.block_area-realtime .anif-block-ul ul li";
- res.mostPopularAnimes = extractMostPopularAnimes($, mostPopularSelector);
-
- return res;
- } catch (err: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeAnimeSearch;
diff --git a/src/parsers/animeSearchSuggestion.ts b/src/parsers/animeSearchSuggestion.ts
deleted file mode 100644
index acef2a4..0000000
--- a/src/parsers/animeSearchSuggestion.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import {
- SRC_HOME_URL,
- SRC_AJAX_URL,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import createHttpError, { type HttpError } from "http-errors";
-import { load, type CheerioAPI, type SelectorType } from "cheerio";
-import type { ScrapedAnimeSearchSuggestion } from "../types/parsers/index.js";
-
-// /anime/search/suggest?q=${query}
-async function scrapeAnimeSearchSuggestion(
- q: string
-): Promise<ScrapedAnimeSearchSuggestion | HttpError> {
- const res: ScrapedAnimeSearchSuggestion = {
- suggestions: [],
- };
-
- try {
- const { data } = await axios.get(
- `${SRC_AJAX_URL}/search/suggest?keyword=${encodeURIComponent(q)}`,
- {
- headers: {
- Accept: "*/*",
- Pragma: "no-cache",
- Referer: SRC_HOME_URL,
- "User-Agent": USER_AGENT_HEADER,
- "X-Requested-With": "XMLHttpRequest",
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- },
- }
- );
-
- const $: CheerioAPI = load(data.html);
- const selector: SelectorType = ".nav-item:has(.film-poster)";
-
- if ($(selector).length < 1) return res;
-
- $(selector).each((_, el) => {
- const id = $(el).attr("href")?.split("?")[0].includes("javascript")
- ? null
- : $(el).attr("href")?.split("?")[0]?.slice(1);
-
- res.suggestions.push({
- id,
- name: $(el).find(".srp-detail .film-name")?.text()?.trim() || null,
- jname:
- $(el).find(".srp-detail .film-name")?.attr("data-jname")?.trim() ||
- $(el).find(".srp-detail .alias-name")?.text()?.trim() ||
- null,
- poster: $(el)
- .find(".film-poster .film-poster-img")
- ?.attr("data-src")
- ?.trim(),
- moreInfo: [
- ...$(el)
- .find(".film-infor")
- .contents()
- .map((_, el) => $(el).text().trim()),
- ].filter((i) => i),
- });
- });
-
- return res;
- } catch (err: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeAnimeSearchSuggestion;
diff --git a/src/parsers/episodeServers.ts b/src/parsers/episodeServers.ts
deleted file mode 100644
index 8f95229..0000000
--- a/src/parsers/episodeServers.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import {
- SRC_BASE_URL,
- SRC_AJAX_URL,
- ACCEPT_HEADER,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import createHttpError, { type HttpError } from "http-errors";
-import { load, type CheerioAPI, type SelectorType } from "cheerio";
-import type { ScrapedEpisodeServers } from "../types/parsers/index.js";
-
-// /anime/servers?episodeId=${id}
-async function scrapeEpisodeServers(
- episodeId: string
-): Promise<ScrapedEpisodeServers | HttpError> {
- const res: ScrapedEpisodeServers = {
- sub: [],
- dub: [],
- raw: [],
- episodeId,
- episodeNo: 0,
- };
-
- try {
- const epId = episodeId.split("?ep=")[1];
-
- const { data } = await axios.get(
- `${SRC_AJAX_URL}/v2/episode/servers?episodeId=${epId}`,
- {
- headers: {
- Accept: ACCEPT_HEADER,
- "User-Agent": USER_AGENT_HEADER,
- "X-Requested-With": "XMLHttpRequest",
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- Referer: new URL(`/watch/${episodeId}`, SRC_BASE_URL).href,
- },
- }
- );
-
- const $: CheerioAPI = load(data.html);
-
- const epNoSelector: SelectorType = ".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: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeEpisodeServers;
diff --git a/src/parsers/estimatedSchedule.ts b/src/parsers/estimatedSchedule.ts
deleted file mode 100644
index abfce97..0000000
--- a/src/parsers/estimatedSchedule.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import {
- SRC_HOME_URL,
- SRC_AJAX_URL,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import createHttpError, { type HttpError } from "http-errors";
-import { load, type CheerioAPI, type SelectorType } from "cheerio";
-import { type ScrapedEstimatedSchedule } from "../types/parsers/index.js";
-
-// /anime/schedule?date=${date}
-async function scrapeEstimatedSchedule(
- date: string
-): Promise<ScrapedEstimatedSchedule | HttpError> {
- const res: ScrapedEstimatedSchedule = {
- scheduledAnimes: [],
- };
-
- try {
- const estScheduleURL =
- `${SRC_AJAX_URL}/schedule/list?tzOffset=-330&date=${date}` as const;
-
- const mainPage = await axios.get(estScheduleURL, {
- headers: {
- Accept: "*/*",
- Referer: SRC_HOME_URL,
- "User-Agent": USER_AGENT_HEADER,
- "X-Requested-With": "XMLHttpRequest",
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- },
- });
-
- const $: CheerioAPI = load(mainPage?.data?.html);
-
- const selector: SelectorType = "li";
-
- if ($(selector)?.text()?.trim()?.includes("No data to display")) {
- return res;
- }
-
- $(selector).each((_, el) => {
- const airingTimestamp = new Date(
- `${date}T${$(el)?.find("a .time")?.text()?.trim()}:00`
- ).getTime();
-
- res.scheduledAnimes.push({
- id: $(el)?.find("a")?.attr("href")?.slice(1)?.trim() || null,
- time: $(el)?.find("a .time")?.text()?.trim() || null,
- name: $(el)?.find("a .film-name.dynamic-name")?.text()?.trim() || null,
- jname:
- $(el)
- ?.find("a .film-name.dynamic-name")
- ?.attr("data-jname")
- ?.trim() || null,
- airingTimestamp,
- secondsUntilAiring: Math.floor((airingTimestamp - Date.now()) / 1000),
- episode: Number($(el).find("a .fd-play button").text().trim().split(" ")[1])
- });
- });
-
- return res;
- } catch (err: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeEstimatedSchedule;
diff --git a/src/parsers/homePage.ts b/src/parsers/homePage.ts
deleted file mode 100644
index cbdaef6..0000000
--- a/src/parsers/homePage.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-import {
- SRC_HOME_URL,
- ACCEPT_HEADER,
- USER_AGENT_HEADER,
- ACCEPT_ENCODING_HEADER,
- extractTop10Animes,
- extractAnimes,
- extractMostPopularAnimes,
-} from "../utils/index.js";
-import axios, { AxiosError } from "axios";
-import createHttpError, { type HttpError } from "http-errors";
-import type { ScrapedHomePage } from "../types/parsers/index.js";
-import { load, type CheerioAPI, type SelectorType } from "cheerio";
-
-// /anime/home
-async function scrapeHomePage(): Promise<ScrapedHomePage | HttpError> {
- const res: ScrapedHomePage = {
- spotlightAnimes: [],
- trendingAnimes: [],
- latestEpisodeAnimes: [],
- topUpcomingAnimes: [],
- top10Animes: {
- today: [],
- week: [],
- month: [],
- },
- topAiringAnimes: [],
- mostPopularAnimes: [],
- mostFavoriteAnimes: [],
- latestCompletedAnimes: [],
- genres: [],
- };
-
- try {
- const mainPage = await axios.get(SRC_HOME_URL as string, {
- headers: {
- "User-Agent": USER_AGENT_HEADER,
- "Accept-Encoding": ACCEPT_ENCODING_HEADER,
- Accept: ACCEPT_HEADER,
- },
- });
-
- const $: CheerioAPI = load(mainPage.data);
-
- const spotlightSelector: SelectorType =
- "#slider .swiper-wrapper .swiper-slide";
-
- $(spotlightSelector).each((i, el) => {
- const otherInfo = $(el)
- .find(".deslide-item-content .sc-detail .scd-item")
- .map((i, el) => $(el).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(),
- 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(),
- poster: $(el)
- .find(".deslide-cover .deslide-cover-img .film-poster-img")
- ?.attr("data-src")
- ?.trim(),
- jname: $(el)
- .find(".deslide-item-content .desi-head-title.dynamic-name")
- ?.attr("data-jname")
- ?.trim(),
- 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,
- },
- otherInfo,
- });
- });
-
- const trendingSelector: SelectorType =
- "#trending-home .swiper-wrapper .swiper-slide";
-
- $(trendingSelector).each((i, 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(),
- name: $(el)
- .find(".item .number .film-title.dynamic-name")
- ?.text()
- ?.trim(),
- jname: $(el)
- .find(".item .number .film-title.dynamic-name")
- ?.attr("data-jname")
- ?.trim(),
- poster: $(el)
- .find(".item .film-poster .film-poster-img")
- ?.attr("data-src")
- ?.trim(),
- });
- });
-
- const latestEpisodeSelector: SelectorType =
- "#main-content .block_area_home:nth-of-type(1) .tab-content .film_list-wrap .flw-item";
- res.latestEpisodeAnimes = extractAnimes($, latestEpisodeSelector);
-
- const topUpcomingSelector: SelectorType =
- "#main-content .block_area_home:nth-of-type(3) .tab-content .film_list-wrap .flw-item";
- res.topUpcomingAnimes = extractAnimes($, topUpcomingSelector);
-
- const genreSelector: SelectorType =
- "#main-sidebar .block_area.block_area_sidebar.block_area-genres .sb-genre-list li";
- $(genreSelector).each((i, el) => {
- res.genres.push(`${$(el).text().trim()}`);
- });
-
- const mostViewedSelector: SelectorType =
- '#main-sidebar .block_area-realtime [id^="top-viewed-"]';
- $(mostViewedSelector).each((i, el) => {
- const period = $(el).attr("id")?.split("-")?.pop()?.trim();
-
- if (period === "day") {
- res.top10Animes.today = extractTop10Animes($, period);
- return;
- }
- if (period === "week") {
- res.top10Animes.week = extractTop10Animes($, period);
- return;
- }
- if (period === "month") {
- res.top10Animes.month = extractTop10Animes($, period);
- }
- });
-
- res.topAiringAnimes = extractMostPopularAnimes($, "#anime-featured .row div:nth-of-type(1) .anif-block-ul ul li");
- res.mostPopularAnimes = extractMostPopularAnimes($, "#anime-featured .row div:nth-of-type(2) .anif-block-ul ul li");
- res.mostFavoriteAnimes = extractMostPopularAnimes($, "#anime-featured .row div:nth-of-type(3) .anif-block-ul ul li");
- res.latestCompletedAnimes = extractMostPopularAnimes($, "#anime-featured .row div:nth-of-type(4) .anif-block-ul ul li");
-
- return res;
- } catch (err: any) {
- if (err instanceof AxiosError) {
- throw createHttpError(
- err?.response?.status || 500,
- err?.response?.statusText || "Something went wrong"
- );
- }
- throw createHttpError.InternalServerError(err?.message);
- }
-}
-
-export default scrapeHomePage;
diff --git a/src/parsers/index.ts b/src/parsers/index.ts
deleted file mode 100644
index 395833a..0000000
--- a/src/parsers/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import scrapeHomePage from "./homePage.js";
-import scrapeGenreAnime from "./animeGenre.js";
-import scrapeAnimeSearch from "./animeSearch.js";
-import scrapeAnimeEpisodes from "./animeEpisodes.js";
-import scrapeAnimeCategory from "./animeCategory.js";
-import scrapeProducerAnimes from "./animeProducer.js";
-import scrapeEpisodeServers from "./episodeServers.js";
-import scrapeAnimeAboutInfo from "./animeAboutInfo.js";
-import scrapeEstimatedSchedule from "./estimatedSchedule.js";
-import scrapeAnimeEpisodeSources from "./animeEpisodeSrcs.js";
-import scrapeAnimeSearchSuggestion from "./animeSearchSuggestion.js";
-
-export {
- scrapeHomePage,
- scrapeGenreAnime,
- scrapeAnimeSearch,
- scrapeAnimeEpisodes,
- scrapeAnimeCategory,
- scrapeEpisodeServers,
- scrapeProducerAnimes,
- scrapeAnimeAboutInfo,
- scrapeEstimatedSchedule,
- scrapeAnimeEpisodeSources,
- scrapeAnimeSearchSuggestion,
-};
diff --git a/src/routes/hianime.ts b/src/routes/hianime.ts
new file mode 100644
index 0000000..f4e1ff9
--- /dev/null
+++ b/src/routes/hianime.ts
@@ -0,0 +1,125 @@
+import { Hono } from "hono";
+import { HiAnime } from "aniwatch";
+
+const hianime = new HiAnime.Scraper();
+const hianimeRouter = new Hono();
+
+// /api/v2/hianime
+hianimeRouter.get("/", (c) => c.redirect("/", 301));
+
+// /api/v2/hianime/home
+hianimeRouter.get("/home", async (c) => {
+ const data = await hianime.getHomePage();
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// /api/v2/hianime/category/{name}?page={page}
+hianimeRouter.get("/category/:name", async (c) => {
+ const categoryName = decodeURIComponent(
+ c.req.param("name").trim()
+ ) as HiAnime.AnimeCategories;
+
+ const page: number =
+ Number(decodeURIComponent(c.req.query("page") || "")) || 1;
+
+ const data = await hianime.getCategoryAnime(categoryName, page);
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// /api/v2/hianime/genre/{name}?page={page}
+hianimeRouter.get("/genre/:name", async (c) => {
+ const genreName = decodeURIComponent(c.req.param("name").trim());
+ const page: number =
+ Number(decodeURIComponent(c.req.query("page") || "")) || 1;
+
+ const data = await hianime.getGenreAnime(genreName, page);
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// /api/v2/hianime/producer/{name}?page={page}
+hianimeRouter.get("/producer/:name", async (c) => {
+ const producerName = decodeURIComponent(c.req.param("name").trim());
+ const page: number =
+ Number(decodeURIComponent(c.req.query("page") || "")) || 1;
+
+ const data = await hianime.getProducerAnimes(producerName, page);
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// /api/v2/hianime/schedule?date={date}
+hianimeRouter.get("/schedule", async (c) => {
+ const date = decodeURIComponent(c.req.query("date") || "");
+
+ const data = await hianime.getEstimatedSchedule(date);
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// /api/v2/hianime/search?q={query}&page={page}&filters={...filters}
+hianimeRouter.get("/search", async (c) => {
+ let { q: query, page, ...filters } = c.req.query();
+
+ query = decodeURIComponent(query || "");
+ const pageNo = Number(decodeURIComponent(page || "")) || 1;
+
+ const data = await hianime.search(query, pageNo, filters);
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// /api/v2/hianime/search/suggestion?q={query}
+hianimeRouter.get("/search/suggestion", async (c) => {
+ const query = decodeURIComponent(c.req.query("q") || "");
+
+ const data = await hianime.searchSuggestions(query);
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// /api/v2/hianime/anime/{animeId}
+hianimeRouter.get("/anime/:animeId", async (c) => {
+ const animeId = decodeURIComponent(c.req.param("animeId").trim());
+ const data = await hianime.getInfo(animeId);
+
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// /api/v2/hianime/episode/servers?animeEpisodeId={id}
+hianimeRouter.get("/episode/servers", async (c) => {
+ const animeEpisodeId = decodeURIComponent(
+ c.req.query("animeEpisodeId") || ""
+ );
+
+ const data = await hianime.getEpisodeServers(animeEpisodeId);
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// episodeId=steinsgate-3?ep=230
+// /api/v2/hianime/episode/sources?animeEpisodeId={episodeId}?server={server}&category={category (dub or sub)}
+hianimeRouter.get("/episode/sources", async (c) => {
+ const animeEpisodeId = decodeURIComponent(
+ c.req.query("animeEpisodeId") || ""
+ );
+ const server = decodeURIComponent(
+ c.req.query("server") || HiAnime.Servers.VidStreaming
+ ) as HiAnime.AnimeServers;
+
+ const category = decodeURIComponent(c.req.query("category") || "sub") as
+ | "sub"
+ | "dub"
+ | "raw";
+
+ const data = await hianime.getEpisodeSources(
+ animeEpisodeId,
+ server,
+ category
+ );
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+// /api/v2/hianime/anime/{anime-id}/episodes
+hianimeRouter.get("/anime/:animeId/episodes", async (c) => {
+ const animeId = decodeURIComponent(c.req.param("animeId").trim());
+ const data = await hianime.getEpisodes(animeId);
+
+ return c.json({ success: true, data }, { status: 200 });
+});
+
+export { hianimeRouter };
diff --git a/src/routes/index.ts b/src/routes/index.ts
deleted file mode 100644
index 7046ae1..0000000
--- a/src/routes/index.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Router, type IRouter } from "express";
-import {
- getGenreAnime,
- getAnimeSearch,
- getHomePageInfo,
- getAnimeCategory,
- getAnimeEpisodes,
- getEpisodeServers,
- getProducerAnimes,
- getAnimeAboutInfo,
- getEstimatedSchedule,
- getAnimeEpisodeSources,
- getAnimeSearchSuggestion,
-} from "../controllers/index.js";
-
-const router: IRouter = Router();
-
-// /anime
-router.get("/", (_, res) => res.redirect("/"));
-
-// /anime/home
-router.get("/home", getHomePageInfo);
-
-// /anime/info?id=${anime-id}
-router.get("/info", getAnimeAboutInfo);
-
-// /anime/genre/${name}?page=${page}
-router.get("/genre/:name", getGenreAnime);
-
-// /anime/search?q=${query}&page=${page}
-router.get("/search", getAnimeSearch);
-
-// /anime/search/suggest?q=${query}
-router.get("/search/suggest", getAnimeSearchSuggestion);
-
-// /anime/episodes/${anime-id}
-router.get("/episodes/:animeId", getAnimeEpisodes);
-
-// /anime/servers?episodeId=${id}
-router.get("/servers", getEpisodeServers);
-
-// episodeId=steinsgate-3?ep=230
-// /anime/episode-srcs?id=${episodeId}?server=${server}&category=${category (dub or sub)}
-router.get("/episode-srcs", getAnimeEpisodeSources);
-
-// /anime/schedule?date=${date}
-router.get("/schedule", getEstimatedSchedule);
-
-// /anime/producer/${name}?page=${page}
-router.get("/producer/:name", getProducerAnimes);
-
-// /anime/:category?page=${page}
-router.get("/:category", getAnimeCategory);
-
-export default router;
diff --git a/src/server.ts b/src/server.ts
index 3520958..6a9c06e 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -1,59 +1,85 @@
import https from "https";
-import morgan from "morgan";
-import express from "express";
-import { resolve } from "path";
import { config } from "dotenv";
import corsConfig from "./config/cors.js";
import { ratelimit } from "./config/ratelimit.js";
-import errorHandler from "./config/errorHandler.js";
-import notFoundHandler from "./config/notFoundHandler.js";
-import animeRouter from "./routes/index.js";
+import { hianimeRouter } from "./routes/hianime.js";
+
+import { serveStatic } from "@hono/node-server/serve-static";
+import { serve } from "@hono/node-server";
+import { Hono } from "hono";
+import { logger } from "hono/logger";
+
+import { HiAnimeError } from "aniwatch";
config();
-const app: express.Application = express();
-const PORT: number = Number(process.env.PORT) || 4000;
-app.use(morgan("dev"));
+const BASE_PATH = "/api/v2" as const;
+const PORT: number = Number(process.env.ANIWATCH_API_PORT) || 4000;
+const ANIWATCH_API_HOSTNAME = process.env?.ANIWATCH_API_HOSTNAME;
+
+const app = new Hono();
+
+app.use(logger());
app.use(corsConfig);
// CAUTION: For personal deployments, "refrain" from having an env
// named "ANIWATCH_API_HOSTNAME". You may face rate limitting
-// and other issues if you do.
-const ISNT_PERSONAL_DEPLOYMENT = Boolean(process?.env?.ANIWATCH_API_HOSTNAME);
+// or other issues if you do.
+const ISNT_PERSONAL_DEPLOYMENT = Boolean(ANIWATCH_API_HOSTNAME);
if (ISNT_PERSONAL_DEPLOYMENT) {
app.use(ratelimit);
}
-app.use(express.static(resolve("public")));
-app.get("/health", (_, res) => res.sendStatus(200));
-app.use("/anime", animeRouter);
+app.use("/", serveStatic({ root: "public" }));
+app.get("/health", (c) => c.text("OK", { status: 200 }));
+
+app.basePath(BASE_PATH).route("/hianime", hianimeRouter);
+app
+ .basePath(BASE_PATH)
+ .get("/anicrush", (c) => c.text("Anicrush could be implemented in future."));
-app.use(notFoundHandler);
-app.use(errorHandler);
+app.notFound((c) =>
+ c.json({ status: 404, message: "Resource Not Found" }, 404)
+);
+
+app.onError((err, c) => {
+ console.error(err);
+ const res = { status: 500, message: "Internal Server Error" };
+
+ if (err instanceof HiAnimeError) {
+ res.status = err.status;
+ res.message = err.message;
+ }
+
+ return c.json(res, { status: res.status });
+});
// NOTE: this env is "required" for vercel deployments
-if (!Boolean(process?.env?.IS_VERCEL_DEPLOYMENT)) {
- app.listen(PORT, () => {
- console.log(`⚔️ api @ http://localhost:${PORT}`);
- });
+if (!Boolean(process?.env?.ANIWATCH_API_VERCEL_DEPLOYMENT)) {
+ serve({
+ port: PORT,
+ fetch: app.fetch,
+ }).addListener("listening", () =>
+ console.info(
+ "\x1b[1;36m" + `aniwatch-api at http://localhost:${PORT}` + "\x1b[0m"
+ )
+ );
// NOTE: remove the `if` block below for personal deployments
if (ISNT_PERSONAL_DEPLOYMENT) {
+ const interval = 9 * 60 * 1000; // 9mins
+
// don't sleep
- const intervalTime = 9 * 60 * 1000; // 9mins
setInterval(() => {
- console.log("HEALTHCHECK ;)", new Date().toLocaleString());
+ console.log("aniwatch-api HEALTH_CHECK at", new Date().toISOString());
https
- .get(
- new URL("/health", `https://${process.env.ANIWATCH_API_HOSTNAME}`)
- .href
- )
+ .get(`https://${ANIWATCH_API_HOSTNAME}/health`)
.on("error", (err) => {
console.error(err.message);
});
- }, intervalTime);
+ }, interval);
}
}
diff --git a/src/types/anime.ts b/src/types/anime.ts
deleted file mode 100644
index 83a3d1f..0000000
--- a/src/types/anime.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-export interface Anime {
- id: string | null;
- name: string | null;
- jname: string | null;
- poster: string | null;
- duration: string | null;
- type: string | null;
- rating: string | null;
- episodes: {
- sub: number | null;
- dub: number | null;
- };
-}
-
-type CommonAnimeProps = "id" | "name" | "poster";
-
-export interface Top10Anime extends Pick<Anime, CommonAnimeProps | "episodes"> {
- rank: number | null;
- jname: string | null;
-}
-
-export type Top10AnimeTimePeriod = "day" | "week" | "month";
-
-export interface MostPopularAnime
- extends Pick<Anime, CommonAnimeProps | "episodes" | "type"> {
- jname: string | null;
-}
-
-export interface SpotlightAnime
- extends MostPopularAnime,
- Pick<Top10Anime, "rank"> {
- description: string | null;
-}
-
-export interface TrendingAnime
- extends Pick<Anime, CommonAnimeProps>,
- Pick<Top10Anime, "rank"> {}
-
-export interface LatestEpisodeAnime extends Anime {}
-
-export interface TopUpcomingAnime extends Anime {}
-
-export interface TopAiringAnime extends MostPopularAnime {}
-export interface MostFavoriteAnime extends MostPopularAnime {}
-export interface LatestCompletedAnime extends MostPopularAnime {}
-
-export interface AnimeGeneralAboutInfo
- extends Pick<Anime, CommonAnimeProps>,
- Pick<SpotlightAnime, "description"> {
- anilistId: number | null;
- malId: number | null;
- stats: {
- quality: string | null;
- } & Pick<Anime, "duration" | "episodes" | "rating" | "type">;
- promotionalVideos: AnimePromotionalVideo[];
- charactersVoiceActors: AnimeCharactersAndVoiceActors[];
-}
-
-export interface RecommendedAnime extends Anime {}
-
-export interface RelatedAnime extends MostPopularAnime {}
-
-export interface Season extends Pick<Anime, CommonAnimeProps> {
- isCurrent: boolean;
- title: string | null;
-}
-
-export interface AnimePromotionalVideo {
- title: string | undefined;
- source: string | undefined;
- thumbnail: string | undefined;
-}
-
-export interface AnimeCharactersAndVoiceActors {
- character: AnimeCharacter;
- voiceActor: AnimeCharacter;
-}
-
-export interface AnimeCharacter {
- id: string;
- poster: string;
- name: string;
- cast: string;
-}
-
-export interface AnimeSearchSuggestion
- extends Omit<MostPopularAnime, "episodes" | "type"> {
- moreInfo: Array<string>;
-}
-
-export interface AnimeEpisode extends Pick<Season, "title"> {
- episodeId: string | null;
- number: number;
- isFiller: boolean;
-}
-
-export interface SubEpisode {
- serverName: string;
- serverId: number | null;
-}
-export interface DubEpisode extends SubEpisode {}
-export interface RawEpisode extends SubEpisode {}
-
-export type AnimeCategories =
- | "most-favorite"
- | "most-popular"
- | "subbed-anime"
- | "dubbed-anime"
- | "recently-updated"
- | "recently-added"
- | "top-upcoming"
- | "top-airing"
- | "movie"
- | "special"
- | "ova"
- | "ona"
- | "tv"
- | "completed";
-
-export type AnimeServers =
- | "hd-1"
- | "hd-2"
- | "megacloud"
- | "streamsb"
- | "streamtape";
-
-export enum Servers {
- VidStreaming = "hd-1",
- MegaCloud = "megacloud",
- StreamSB = "streamsb",
- StreamTape = "streamtape",
- VidCloud = "hd-2",
- AsianLoad = "asianload",
- GogoCDN = "gogocdn",
- MixDrop = "mixdrop",
- UpCloud = "upcloud",
- VizCloud = "vizcloud",
- MyCloud = "mycloud",
- Filemoon = "filemoon",
-}
diff --git a/src/types/controllers/animeAboutInfo.ts b/src/types/controllers/animeAboutInfo.ts
deleted file mode 100644
index aa65efd..0000000
--- a/src/types/controllers/animeAboutInfo.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export type AnimeAboutInfoQueryParams = {
- id?: string;
-};
diff --git a/src/types/controllers/animeCategory.ts b/src/types/controllers/animeCategory.ts
deleted file mode 100644
index 7de0876..0000000
--- a/src/types/controllers/animeCategory.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export type CategoryAnimePathParams = {
- category?: string;
-};
-
-export type CategoryAnimeQueryParams = {
- page?: string;
-};
diff --git a/src/types/controllers/animeEpisodeSrcs.ts b/src/types/controllers/animeEpisodeSrcs.ts
deleted file mode 100644
index 0e82539..0000000
--- a/src/types/controllers/animeEpisodeSrcs.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { type AnimeServers } from "../anime.js";
-
-export type AnimeEpisodeSrcsQueryParams = {
- id?: string;
- server?: AnimeServers;
- category?: "sub" | "dub";
-};
diff --git a/src/types/controllers/animeEpisodes.ts b/src/types/controllers/animeEpisodes.ts
deleted file mode 100644
index 481eb87..0000000
--- a/src/types/controllers/animeEpisodes.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export type AnimeEpisodePathParams = {
- animeId?: string;
-};
diff --git a/src/types/controllers/animeGenre.ts b/src/types/controllers/animeGenre.ts
deleted file mode 100644
index 038d15e..0000000
--- a/src/types/controllers/animeGenre.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export type GenreAnimePathParams = {
- name?: string;
-};
-
-export type GenreAnimeQueryParams = {
- page?: string;
-};
diff --git a/src/types/controllers/animeProducer.ts b/src/types/controllers/animeProducer.ts
deleted file mode 100644
index 7f8a9a7..0000000
--- a/src/types/controllers/animeProducer.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export type AnimeProducerPathParams = {
- name?: string;
-};
-
-export type AnimeProducerQueryParams = {
- page?: string;
-};
diff --git a/src/types/controllers/animeSearch.ts b/src/types/controllers/animeSearch.ts
deleted file mode 100644
index fccb243..0000000
--- a/src/types/controllers/animeSearch.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export type AnimeSearchQueryParams = {
- q?: string;
- page?: string;
- type?: string;
- status?: string;
- rated?: string;
- score?: string;
- season?: string;
- language?: string;
- start_date?: string;
- end_date?: string;
- sort?: string;
- genres?: string;
-};
-
-export type SearchFilters = Omit<AnimeSearchQueryParams, "q" | "page">;
-
-export type FilterKeys = Partial<
- keyof Omit<SearchFilters, "start_date" | "end_date">
->;
diff --git a/src/types/controllers/animeSearchSuggestion.ts b/src/types/controllers/animeSearchSuggestion.ts
deleted file mode 100644
index 491daa0..0000000
--- a/src/types/controllers/animeSearchSuggestion.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export type AnimeSearchSuggestQueryParams = {
- q?: string;
-};
diff --git a/src/types/controllers/episodeServers.ts b/src/types/controllers/episodeServers.ts
deleted file mode 100644
index d711a40..0000000
--- a/src/types/controllers/episodeServers.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export type EpisodeServersQueryParams = {
- episodeId?: string;
-};
diff --git a/src/types/controllers/estimatedSchedule.ts b/src/types/controllers/estimatedSchedule.ts
deleted file mode 100644
index e732aaa..0000000
--- a/src/types/controllers/estimatedSchedule.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export type EstimatedScheduleQueryParams = {
- date?: string;
-};
diff --git a/src/types/controllers/index.ts b/src/types/controllers/index.ts
deleted file mode 100644
index 14876e2..0000000
--- a/src/types/controllers/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export type * from "./animeGenre.js";
-export type * from "./animeCategory.js";
-export type * from "./animeProducer.js";
-export type * from "./animeSearch.js";
-export type * from "./animeEpisodes.js";
-export type * from "./episodeServers.js";
-export type * from "./animeAboutInfo.js";
-export type * from "./animeEpisodeSrcs.js";
-export type * from "./estimatedSchedule.js";
-export type * from "./animeSearchSuggestion.js";
diff --git a/src/types/extractor.ts b/src/types/extractor.ts
deleted file mode 100644
index 71c6be1..0000000
--- a/src/types/extractor.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export interface Video {
- url: string;
- quality?: string;
- isM3U8?: boolean;
- size?: number;
- [x: string]: unknown;
-}
-
-export interface Subtitle {
- id?: string;
- url: string;
- lang: string;
-}
-
-export interface Intro {
- start: number;
- end: number;
-}
diff --git a/src/types/parsers/animeAboutInfo.ts b/src/types/parsers/animeAboutInfo.ts
deleted file mode 100644
index 274e7c2..0000000
--- a/src/types/parsers/animeAboutInfo.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type {
- Season,
- RelatedAnime,
- RecommendedAnime,
- AnimeGeneralAboutInfo,
-} from "../anime.js";
-import { type HttpError } from "http-errors";
-import { type ScrapedAnimeSearchResult } from "./animeSearch.js";
-
-export interface ScrapedAnimeAboutInfo
- extends Pick<ScrapedAnimeSearchResult, "mostPopularAnimes"> {
- anime: {
- info: AnimeGeneralAboutInfo;
- moreInfo: Record<string, string | string[]>;
- };
- seasons: Array<Season>;
- relatedAnimes: Array<RelatedAnime> | HttpError;
- recommendedAnimes: Array<RecommendedAnime> | HttpError;
-}
diff --git a/src/types/parsers/animeCategory.ts b/src/types/parsers/animeCategory.ts
deleted file mode 100644
index 89c56f0..0000000
--- a/src/types/parsers/animeCategory.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { HttpError } from "http-errors";
-import type { Anime, Top10Anime } from "../anime.js";
-
-export interface ScrapedAnimeCategory {
- animes: Array<Anime> | HttpError;
- genres: Array<string>;
- top10Animes: {
- today: Array<Top10Anime> | HttpError;
- week: Array<Top10Anime> | HttpError;
- month: Array<Top10Anime> | HttpError;
- };
- category: string;
- totalPages: number;
- currentPage: number;
- hasNextPage: boolean;
-}
-
-export type CommonAnimeScrapeTypes =
- | "animes"
- | "totalPages"
- | "hasNextPage"
- | "currentPage";
diff --git a/src/types/parsers/animeEpisodeSrcs.ts b/src/types/parsers/animeEpisodeSrcs.ts
deleted file mode 100644
index ef58ed3..0000000
--- a/src/types/parsers/animeEpisodeSrcs.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { Intro, Subtitle, Video } from "../extractor.js";
-
-export interface ScrapedAnimeEpisodesSources {
- headers?: {
- [k: string]: string;
- };
- intro?: Intro;
- subtitles?: Subtitle[];
- sources: Video[];
- download?: string;
- embedURL?: string;
-}
diff --git a/src/types/parsers/animeEpisodes.ts b/src/types/parsers/animeEpisodes.ts
deleted file mode 100644
index 3587573..0000000
--- a/src/types/parsers/animeEpisodes.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { type AnimeEpisode } from "../anime.js";
-
-export interface ScrapedAnimeEpisodes {
- totalEpisodes: number;
- episodes: Array<AnimeEpisode>;
-}
diff --git a/src/types/parsers/animeGenre.ts b/src/types/parsers/animeGenre.ts
deleted file mode 100644
index 10f8b85..0000000
--- a/src/types/parsers/animeGenre.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import type {
- ScrapedAnimeCategory,
- CommonAnimeScrapeTypes,
-} from "./animeCategory.js";
-import { type ScrapedHomePage } from "./homePage.js";
-
-export interface ScrapedGenreAnime
- extends Pick<ScrapedAnimeCategory, CommonAnimeScrapeTypes | "genres">,
- Pick<ScrapedHomePage, "topAiringAnimes"> {
- genreName: string;
-}
diff --git a/src/types/parsers/animeProducer.ts b/src/types/parsers/animeProducer.ts
deleted file mode 100644
index bcd784e..0000000
--- a/src/types/parsers/animeProducer.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { ScrapedHomePage } from "./homePage.js";
-import type { ScrapedAnimeCategory } from "./animeCategory.js";
-
-export interface ScrapedProducerAnime
- extends Omit<ScrapedAnimeCategory, "genres" | "category">,
- Pick<ScrapedHomePage, "topAiringAnimes"> {
- producerName: string;
-}
diff --git a/src/types/parsers/animeSearch.ts b/src/types/parsers/animeSearch.ts
deleted file mode 100644
index 7e641ea..0000000
--- a/src/types/parsers/animeSearch.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import type {
- ScrapedAnimeCategory,
- CommonAnimeScrapeTypes,
-} from "./animeCategory.js";
-import type { HttpError } from "http-errors";
-import type { MostPopularAnime } from "../anime.js";
-import type { SearchFilters } from "../controllers/animeSearch.js";
-
-export interface ScrapedAnimeSearchResult
- extends Pick<ScrapedAnimeCategory, CommonAnimeScrapeTypes> {
- mostPopularAnimes: Array<MostPopularAnime> | HttpError;
- searchQuery: string;
- searchFilters: SearchFilters;
-}
diff --git a/src/types/parsers/animeSearchSuggestion.ts b/src/types/parsers/animeSearchSuggestion.ts
deleted file mode 100644
index 4c262a4..0000000
--- a/src/types/parsers/animeSearchSuggestion.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { HttpError } from "http-errors";
-import type { AnimeSearchSuggestion } from "../anime.js";
-
-export interface ScrapedAnimeSearchSuggestion {
- suggestions: Array<AnimeSearchSuggestion> | HttpError;
-}
diff --git a/src/types/parsers/episodeServers.ts b/src/types/parsers/episodeServers.ts
deleted file mode 100644
index a003149..0000000
--- a/src/types/parsers/episodeServers.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import type { SubEpisode, DubEpisode, RawEpisode } from "../anime.js";
-
-export interface ScrapedEpisodeServers {
- sub: SubEpisode[];
- dub: DubEpisode[];
- raw: RawEpisode[];
- episodeNo: number;
- episodeId: string;
-}
diff --git a/src/types/parsers/estimatedSchedule.ts b/src/types/parsers/estimatedSchedule.ts
deleted file mode 100644
index 881be86..0000000
--- a/src/types/parsers/estimatedSchedule.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-type EstimatedSchedule = {
- id: string | null;
- time: string | null;
- name: string | null;
- jname: string | null;
- airingTimestamp: number;
- secondsUntilAiring: number;
- episode: number;
-};
-
-export type ScrapedEstimatedSchedule = {
- scheduledAnimes: Array<EstimatedSchedule>;
-};
diff --git a/src/types/parsers/homePage.ts b/src/types/parsers/homePage.ts
deleted file mode 100644
index 23f6284..0000000
--- a/src/types/parsers/homePage.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type {
- TrendingAnime,
- SpotlightAnime,
- TopAiringAnime,
- TopUpcomingAnime,
- LatestEpisodeAnime,
- MostFavoriteAnime,
- MostPopularAnime,
- LatestCompletedAnime,
-} from "../anime.js";
-import type { HttpError } from "http-errors";
-import type { ScrapedAnimeCategory } from "./animeCategory.js";
-
-export interface ScrapedHomePage
- extends Pick<ScrapedAnimeCategory, "genres" | "top10Animes"> {
- spotlightAnimes: Array<SpotlightAnime> | HttpError;
- trendingAnimes: Array<TrendingAnime> | HttpError;
- latestEpisodeAnimes: Array<LatestEpisodeAnime> | HttpError;
- topUpcomingAnimes: Array<TopUpcomingAnime> | HttpError;
- topAiringAnimes: Array<TopAiringAnime> | HttpError;
- mostPopularAnimes: Array<MostPopularAnime> | HttpError;
- mostFavoriteAnimes: Array<MostFavoriteAnime> | HttpError;
- latestCompletedAnimes: Array<LatestCompletedAnime> | HttpError;
-}
diff --git a/src/types/parsers/index.ts b/src/types/parsers/index.ts
deleted file mode 100644
index 057e2fc..0000000
--- a/src/types/parsers/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { ScrapedHomePage } from "./homePage.js";
-import type { ScrapedGenreAnime } from "./animeGenre.js";
-import type { ScrapedAnimeEpisodes } from "./animeEpisodes.js";
-import type { ScrapedAnimeCategory } from "./animeCategory.js";
-import type { ScrapedProducerAnime } from "./animeProducer.js";
-import type { ScrapedEpisodeServers } from "./episodeServers.js";
-import type { ScrapedAnimeAboutInfo } from "./animeAboutInfo.js";
-import type { ScrapedAnimeSearchResult } from "./animeSearch.js";
-import type { ScrapedEstimatedSchedule } from "./estimatedSchedule.js";
-import type { ScrapedAnimeEpisodesSources } from "./animeEpisodeSrcs.js";
-import type { ScrapedAnimeSearchSuggestion } from "./animeSearchSuggestion.js";
-
-export type {
- ScrapedHomePage,
- ScrapedGenreAnime,
- ScrapedAnimeEpisodes,
- ScrapedProducerAnime,
- ScrapedAnimeCategory,
- ScrapedEpisodeServers,
- ScrapedAnimeAboutInfo,
- ScrapedAnimeSearchResult,
- ScrapedEstimatedSchedule,
- ScrapedAnimeEpisodesSources,
- ScrapedAnimeSearchSuggestion,
-};
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
deleted file mode 100644
index de7617f..0000000
--- a/src/utils/constants.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import { config } from "dotenv";
-
-config();
-
-export const ACCEPT_ENCODING_HEADER = "gzip, deflate, br";
-export const USER_AGENT_HEADER =
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4692.71 Safari/537.36";
-export const 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";
-
-// previously aniwatch.to || aniwatchtv.to
-const DOMAIN = process.env.DOMAIN || "hianime.to";
-
-export const SRC_BASE_URL = `https://${DOMAIN}` as const;
-export const SRC_AJAX_URL = `${SRC_BASE_URL}/ajax` as const;
-export const SRC_HOME_URL = `${SRC_BASE_URL}/home` as const;
-export const SRC_SEARCH_URL = `${SRC_BASE_URL}/search` as const;
-
-// <SearchPageFilters>
-export const genresIdMap: Record<string, number> = {
- 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,
-} as const;
-
-export const typeIdMap: Record<string, number> = {
- all: 0,
- movie: 1,
- tv: 2,
- ova: 3,
- ona: 4,
- special: 5,
- music: 6,
-} as const;
-
-export const statusIdMap: Record<string, number> = {
- all: 0,
- "finished-airing": 1,
- "currently-airing": 2,
- "not-yet-aired": 3,
-} as const;
-
-export const ratedIdMap: Record<string, number> = {
- all: 0,
- g: 1,
- pg: 2,
- "pg-13": 3,
- r: 4,
- "r+": 5,
- rx: 6,
-} as const;
-
-export const scoreIdMap: Record<string, number> = {
- all: 0,
- appalling: 1,
- horrible: 2,
- "very-bad": 3,
- bad: 4,
- average: 5,
- fine: 6,
- good: 7,
- "very-good": 8,
- great: 9,
- masterpiece: 10,
-} as const;
-
-export const seasonIdMap: Record<string, number> = {
- all: 0,
- spring: 1,
- summer: 2,
- fall: 3,
- winter: 4,
-} as const;
-
-export const languageIdMap: Record<string, number> = {
- all: 0,
- sub: 1,
- dub: 2,
- "sub-&-dub": 3,
-} as const;
-
-export const sortIdMap: Record<string, string> = {
- 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",
-} as const;
-// </SearchPageFilters>
diff --git a/src/utils/index.ts b/src/utils/index.ts
deleted file mode 100644
index b514c85..0000000
--- a/src/utils/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "./methods.js";
-export * from "./constants.js";
diff --git a/src/utils/methods.ts b/src/utils/methods.ts
deleted file mode 100644
index 6aed9f7..0000000
--- a/src/utils/methods.ts
+++ /dev/null
@@ -1,297 +0,0 @@
-import type {
- Anime,
- Top10Anime,
- MostPopularAnime,
- Top10AnimeTimePeriod,
-} from "../types/anime.js";
-import type { CheerioAPI, SelectorType } from "cheerio";
-import {
- genresIdMap,
- languageIdMap,
- ratedIdMap,
- scoreIdMap,
- seasonIdMap,
- sortIdMap,
- statusIdMap,
- typeIdMap,
-} from "./constants.js";
-import { type FilterKeys } from "../types/controllers/animeSearch.js";
-import createHttpError, { HttpError } from "http-errors";
-
-export const extractAnimes = (
- $: CheerioAPI,
- selector: SelectorType
-): Array<Anime> | HttpError => {
- try {
- const animes: Array<Anime> = [];
-
- $(selector).each((i, 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: any) {
- throw createHttpError.InternalServerError(
- err?.message || "Something went wrong"
- );
- }
-};
-
-export const extractTop10Animes = (
- $: CheerioAPI,
- period: Top10AnimeTimePeriod
-): Array<Top10Anime> | HttpError => {
- try {
- const animes: Array<Top10Anime> = [];
- const selector = `#top-viewed-${period} ul li`;
-
- $(selector).each((i, 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: any) {
- throw createHttpError.InternalServerError(
- err?.message || "Something went wrong"
- );
- }
-};
-
-export const extractMostPopularAnimes = (
- $: CheerioAPI,
- selector: SelectorType
-): Array<MostPopularAnime> | HttpError => {
- try {
- const animes: Array<MostPopularAnime> = [];
-
- $(selector).each((i, 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: any) {
- throw createHttpError.InternalServerError(
- err?.message || "Something went wrong"
- );
- }
-};
-
-export function retrieveServerId(
- $: CheerioAPI,
- index: number,
- category: "sub" | "dub" | "raw"
-) {
- 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: string[]): string | undefined {
- if (genreNames.length < 1) {
- return undefined;
- }
- return genreNames.map((name) => genresIdMap[name]).join(",");
-}
-
-export function getSearchFilterValue(
- key: FilterKeys,
- rawValue: string
-): string | undefined {
- rawValue = rawValue.trim();
- if (!rawValue) return undefined;
-
- switch (key) {
- case "genres": {
- return getGenresFilterVal(rawValue.split(","));
- }
- case "type": {
- const val = typeIdMap[rawValue] ?? 0;
- return val === 0 ? undefined : `${val}`;
- }
- case "status": {
- const val = statusIdMap[rawValue] ?? 0;
- return val === 0 ? undefined : `${val}`;
- }
- case "rated": {
- const val = ratedIdMap[rawValue] ?? 0;
- return val === 0 ? undefined : `${val}`;
- }
- case "score": {
- const val = scoreIdMap[rawValue] ?? 0;
- return val === 0 ? undefined : `${val}`;
- }
- case "season": {
- const val = seasonIdMap[rawValue] ?? 0;
- return val === 0 ? undefined : `${val}`;
- }
- case "language": {
- const val = languageIdMap[rawValue] ?? 0;
- return val === 0 ? undefined : `${val}`;
- }
- case "sort": {
- return sortIdMap[rawValue] ?? undefined;
- }
- default:
- return undefined;
- }
-}
-
-// this fn tackles both start_date and end_date
-export function getSearchDateFilterValue(
- isStartDate: boolean,
- rawValue: string
-): string[] | undefined {
- rawValue = rawValue.trim();
- if (!rawValue) return undefined;
-
- 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 undefined;
- }
-
- // sample return -> [sy=2023, sm=10, sd=11]
- return [
- Number(year) > 0 ? `${dateCategory}y=${year}` : "",
- Number(month) > 0 ? `${dateCategory}m=${month}` : "",
- Number(date) > 0 ? `${dateCategory}d=${date}` : "",
- ].filter((d) => Boolean(d));
-}
-
-export function substringAfter(str: string, toFind: string) {
- const index = str.indexOf(toFind);
- return index == -1 ? "" : str.substring(index + toFind.length);
-}
-
-export function substringBefore(str: string, toFind: string) {
- const index = str.indexOf(toFind);
- return index == -1 ? "" : str.substring(0, index);
-}