├── .gitignore
├── LICENSE
├── README.md
├── dist
├── index.js
└── source
│ ├── kuramanime
│ ├── kuramanime.js
│ └── parser.js
│ ├── kuronime
│ ├── decryptor.js
│ ├── kuronime.js
│ └── parser.js
│ ├── nanime
│ ├── nanime.js
│ └── parser.js
│ ├── otakudesu
│ ├── otakudesu.js
│ └── parser.js
│ └── utils
│ └── types.js
├── package.json
├── src
├── index.ts
└── source
│ ├── kuramanime
│ ├── kuramanime.ts
│ └── parser.ts
│ ├── kuronime
│ ├── decryptor.js
│ ├── kuronime.ts
│ └── parser.ts
│ ├── nanime
│ ├── nanime.ts
│ └── parser.ts
│ ├── otakudesu
│ ├── otakudesu.ts
│ └── parser.ts
│ └── utils
│ └── types.ts
├── tsconfig.json
└── vercel.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | node_modules
3 | yarn.lock
4 | .idea/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Miukyo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Aniyoi API
7 |
8 |
9 | RESTFUL API Streaming Anime Subtitle Indonesia
10 | API ini masih dalam pembuatan jadi mungkin akan rawan error, Jika kalian tertarik untuk membantu bisa dm saya di discord miukyo
11 |
12 |
13 |
14 | # !!HIATUS!! GA TAU BAKAL LANJUT KAPAN, INFO LEBIH LANJUT BISA DM
15 |
16 |
17 | Table of Contents
18 |
19 | - [Installation](#installation)
20 | - [Local](#local)
21 | - [List Source](#list-source)
22 | - [Routes](#routes)
23 | - [Get Recent Episodes](#get-recent-episodes)
24 | - [Get Popular Anime](#get-popular-anime)
25 | - [Get Anime Search](#get-anime-search)
26 | - [Get Genre List](#get-genre-list)
27 | - [Get Genre](#get-genre)
28 | - [Get Season List](#get-season-list)
29 | - [Get Season](#get-season)
30 | - [Get Anime Details](#get-anime-details)
31 | - [Get Streaming URLs](#get-streaming-urls)
32 |
33 | ## Installation
34 |
35 | ### Local
36 |
37 | Run command berikut untuk mengclone repo ini, dan menginstall dependencies:
38 |
39 | ```sh
40 | git clone https://github.com/miukyo/aniyoi-api.git
41 | cd aniyoi-api
42 | npm install #atau yarn install
43 | ```
44 |
45 | Start server dengan command berikut:
46 |
47 | ```sh
48 | npm start #atau yarn start
49 | ```
50 |
51 | Server akan berjalan di http://localhost:3001
52 |
53 | ## List Source
54 |
55 | Beberapa source mungkin tidak support dengan beberapa fitur. Dikarenakan kurangnya informasi pada website asli.
56 |
57 | | Source | Fitur yang bermasalah |
58 | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59 | | Kuronime | - 🟡 Mendapatkan Streaming URL mungkin agak lama karena link url di Enkripsi dan harus di Dekripsi dahulu dari playernya.
- 🔴 Source video selain local tidak di support
|
60 | | Kuramanime | - 🔴 Anime Terbaru tidak menampilkan yang terbaru melainkan campur aduk.
- 🔴 Anime Popular juga sama dengan yang diatas.
- 🔴 Beberapa page hanya memiliki maximum 8 page.
|
61 | | Nanime | - 🔴 Tidak support direct Streaming URL hanya bisa menggunakan embed.
- 🔴 Tidak ada Anime Popular.
|
62 | | Otaku Desu Cancelled | - 🔴 Slug URL Details Anime berbeda dengan Slug URL Episode
|
63 |
64 |
65 | Jika kalian tau website anime subtitle anime yang bagus dan detail mohon kontak saya. Akan saya include di sini, Terima kasih!
66 |
67 | ## Routes
68 |
69 | Contoh dibawah menggunakan [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), kamu juga bisa menggunakan library http lainnya.
70 |
71 | ### Get Recent Episodes
72 |
73 | | Parameter | Description |
74 | | ------------ | ----------------------------------------- |
75 | | `page` (int) | pilih page dari maximum page. Default : 1 |
76 |
77 | ```js
78 | fetch("localhost:3001/{source}/recent")
79 | .then((response) => response.json())
80 | .then((animelist) => console.log(animelist));
81 | ```
82 |
83 | Output >>
84 |
85 | ```ts
86 | {
87 | "list": [
88 | {
89 | "slug": string,
90 | "title": string,
91 | "episode?": number, //(optional)
92 | "cover": string,
93 | "url": string,
94 | },
95 | {...}
96 | ],
97 | "maxPage": number,
98 | "page": number,
99 | }
100 | ```
101 |
102 | ### Get Popular Anime
103 |
104 | | Parameter | Description |
105 | | ------------ | ----------------------------------------- |
106 | | `page` (int) | pilih page dari maximum page. Default : 1 |
107 |
108 | ```js
109 | fetch("localhost:3001/{source}/popular")
110 | .then((response) => response.json())
111 | .then((animelist) => console.log(animelist));
112 | ```
113 |
114 | Output >>
115 |
116 | ```ts
117 | {
118 | "list": [
119 | {
120 | "slug": string,
121 | "title": string,
122 | "cover": string,
123 | "url": string,
124 | },
125 | {...}
126 | ],
127 | "maxPage": number,
128 | "page": number,
129 | }
130 | ```
131 |
132 | ### Get Anime Search
133 |
134 | | Parameter | Description |
135 | | ---------------- | ----------------------------------------- |
136 | | `query` (string) | nama anime yang ingin dicari |
137 | | `page` (int) | pilih page dari maximum page. Default : 1 |
138 |
139 | ```js
140 | fetch("localhost:3001/{source}/search")
141 | .then((response) => response.json())
142 | .then((animelist) => console.log(animelist));
143 | ```
144 |
145 | Output >>
146 |
147 | ```ts
148 | {
149 | "list": [
150 | {
151 | "slug": string,
152 | "title": string,
153 | "cover": string,
154 | "url": string,
155 | },
156 | {...}
157 | ],
158 | "maxPage": number,
159 | "page": number,
160 | }
161 | ```
162 |
163 | ### Get Genre List
164 |
165 | | Parameter | Description |
166 | | --------- | ----------- |
167 | | - | - |
168 |
169 | ```js
170 | fetch("localhost:3001/{source}/genre")
171 | .then((response) => response.json())
172 | .then((animelist) => console.log(animelist));
173 | ```
174 |
175 | Output >>
176 |
177 |
178 | ```ts
179 | [
180 | {
181 | "slug": string,
182 | "title": string,
183 | "url": string,
184 | },
185 | {...},
186 | ]
187 | ```
188 |
189 | ### Get Genre
190 |
191 | | Parameter | Description |
192 | | ---------------------- | ------------------------------------------------------- |
193 | | `page` (int) | pilih page dari maximum page. Default : 1 |
194 | | `:genre-slug` (string) | genre slug dapat didapatkan dalam respon **Genre List** |
195 |
196 |
197 | ```js
198 | fetch("localhost:3001/{source}/genre/{genre-slug}")
199 | .then((response) => response.json())
200 | .then((animelist) => console.log(animelist));
201 | ```
202 |
203 | Output >>
204 |
205 | ```ts
206 | {
207 | "list": [
208 | {
209 | "slug": string,
210 | "title": string,
211 | "cover": string,
212 | "url": string,
213 | },
214 | {...}
215 | ],
216 | "maxPage": number,
217 | "page": number,
218 | }
219 | ```
220 |
221 | ### Get Season List
222 |
223 | | Parameter | Description |
224 | | --------- | ----------- |
225 | | - | - |
226 |
227 | ```js
228 | fetch("localhost:3001/{source}/season")
229 | .then((response) => response.json())
230 | .then((animelist) => console.log(animelist));
231 | ```
232 |
233 | Output >>
234 |
235 |
236 | ```ts
237 | [
238 | {
239 | "slug": string,
240 | "title": string,
241 | "url": string,
242 | },
243 | {...},
244 | ]
245 | ```
246 |
247 | ### Get Season
248 |
249 | | Parameter | Description |
250 | | ----------------------- | --------------------------------------------------------- |
251 | | `page` (int) | pilih page dari maximum page. Default : 1 |
252 | | `:season-slug` (string) | season slug dapat didapatkan dalam respon **Season List** |
253 |
254 | ```js
255 | fetch("localhost:3001/{source}/season/{season-slug}")
256 | .then((response) => response.json())
257 | .then((animelist) => console.log(animelist));
258 | ```
259 |
260 | Output >>
261 |
262 | ```ts
263 | {
264 | "list": [
265 | {
266 | "slug": string,
267 | "title": string,
268 | "cover": string,
269 | "url": string,
270 | },
271 | {...}
272 | ],
273 | "maxPage": number,
274 | "page": number,
275 | }
276 | ```
277 |
278 | ### Get Anime Details
279 |
280 | | Parameter | Description |
281 | | ---------------------- | ------------------------------------------------------------- |
282 | | `:anime-slug` (string) | anime slug dapat didapatkan dalam respon list seperti diatas. |
283 |
284 | ```js
285 | fetch("localhost:3001/{source}/anime/{anime-slug}")
286 | .then((response) => response.json())
287 | .then((animelist) => console.log(animelist));
288 | ```
289 |
290 | Output >>
291 |
292 | ```ts
293 | {
294 | "slug": string,
295 | "title": string,
296 | "titleAlt?": string, //(optional)
297 | "synopsis?": string, //(optional | beberapa ada yang tidak memiliki sinopsis)
298 | "episodeTotal": number, //(anime ongoing di kuronime akan merespon "0")
299 | "episode": number,
300 | "season?": string, //(optional)
301 | "genre?": string[], //(optional)
302 | "cover": string,
303 | "url": string,
304 | }
305 | ```
306 |
307 | ### Get Streaming URLs
308 |
309 |
310 | | Parameter | Description |
311 | | ---------------------- | ---------------------------------------------------------------- |
312 | | `:anime-slug` (string) | anime slug dapat didapatkan dalam respon list seperti diatas. |
313 | | `:anime-episode` (int) | pilih episode anime yang tersedia dalam respon **Anime Details** |
314 |
315 |
316 | ```js
317 | fetch("localhost:3001/{source}/anime/{anime-slug}/{anime-episode}")
318 | .then((response) => response.json())
319 | .then((animelist) => console.log(animelist));
320 | ```
321 |
322 | Output >>
323 |
324 | ```ts
325 | {
326 | "episode": number,
327 | "video": [
328 | {
329 | "quality": string,
330 | "url": string,
331 | },
332 | {...},
333 | ]
334 | }
335 | ```
336 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.app = void 0;
7 | const express_1 = __importDefault(require("express"));
8 | const cors_1 = __importDefault(require("cors"));
9 | const kuramanime_1 = __importDefault(require("./source/kuramanime/kuramanime"));
10 | const nanime_1 = __importDefault(require("./source/nanime/nanime"));
11 | const kuronime_1 = __importDefault(require("./source/kuronime/kuronime"));
12 | const axios_1 = __importDefault(require("axios"));
13 | exports.app = (0, express_1.default)();
14 | axios_1.default.defaults.validateStatus = () => true;
15 | axios_1.default.defaults.headers.common["User-Agent"] =
16 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54";
17 | exports.app.use((0, cors_1.default)());
18 | exports.app.get("/", async (req, res) => {
19 | res.send("ANIYOI API IS UP 🚀");
20 | });
21 | exports.app.use("/kuramanime", kuramanime_1.default);
22 | exports.app.use("/nanime", nanime_1.default);
23 | exports.app.use("/kuronime", kuronime_1.default);
24 | // app.use("/otakudesu", otakudesu); //Url page streaming memakai slug yang berbeda
25 | exports.app.listen(process.env.PORT || 3001, () => {
26 | console.warn("\nReady 🚀");
27 | });
28 |
--------------------------------------------------------------------------------
/dist/source/kuramanime/kuramanime.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const parser_1 = require("./parser");
8 | const app = express_1.default.Router();
9 | app.get("/", async (req, res) => {
10 | return res.status(200).send("Kuramanime Server is ready 🚀");
11 | });
12 | app.get("/recent", async (req, res) => {
13 | try {
14 | const { page } = req.query;
15 | const data = await (0, parser_1.recentRelease)(page);
16 | return res.status(200).json(data);
17 | }
18 | catch (err) {
19 | return res.status(500).json({
20 | error: "Internal Error",
21 | message: err.toString(),
22 | });
23 | }
24 | });
25 | app.get("/search", async (req, res) => {
26 | try {
27 | const { page, query } = req.query;
28 | const data = await (0, parser_1.search)(query, page);
29 | return res.status(200).json(data);
30 | }
31 | catch (err) {
32 | return res.status(500).json({
33 | error: "Internal Error",
34 | message: err.toString(),
35 | });
36 | }
37 | });
38 | app.get("/popular", async (req, res) => {
39 | try {
40 | const { page } = req.query;
41 | const data = await (0, parser_1.popular)(page);
42 | return res.status(200).json(data);
43 | }
44 | catch (err) {
45 | return res.status(500).json({
46 | error: "Internal Error",
47 | message: err.toString(),
48 | });
49 | }
50 | });
51 | app.get("/genre", async (req, res) => {
52 | try {
53 | const { page } = req.query;
54 | const data = await (0, parser_1.genreList)(page);
55 | return res.status(200).json(data);
56 | }
57 | catch (err) {
58 | return res.status(500).json({
59 | error: "Internal Error",
60 | message: err.toString(),
61 | });
62 | }
63 | });
64 | app.get("/genre/:genre", async (req, res) => {
65 | try {
66 | const genreType = req.params.genre;
67 | const { page } = req.query;
68 | const data = await (0, parser_1.genre)(genreType, page);
69 | return res.status(200).json(data);
70 | }
71 | catch (err) {
72 | return res.status(500).json({
73 | error: "Internal Error",
74 | message: err.toString(),
75 | });
76 | }
77 | });
78 | app.get("/season", async (req, res) => {
79 | try {
80 | const { page } = req.query;
81 | const data = await (0, parser_1.seasonList)(page);
82 | return res.status(200).json(data);
83 | }
84 | catch (err) {
85 | return res.status(500).json({
86 | error: "Internal Error",
87 | message: err.toString(),
88 | });
89 | }
90 | });
91 | app.get("/season/:season", async (req, res) => {
92 | try {
93 | const seasonYear = req.params.season;
94 | const { page } = req.query;
95 | const data = await (0, parser_1.season)(seasonYear, page);
96 | return res.status(200).json(data);
97 | }
98 | catch (err) {
99 | return res.status(500).json({
100 | error: "Internal Error",
101 | message: err.toString(),
102 | });
103 | }
104 | });
105 | app.get("/anime/:animeSlug", async (req, res) => {
106 | try {
107 | const slug = req.params.animeSlug;
108 | const data = await (0, parser_1.anime)(slug);
109 | return res.status(200).json(data);
110 | }
111 | catch (err) {
112 | return res.status(500).json({
113 | error: "Internal Error",
114 | message: err.toString(),
115 | });
116 | }
117 | });
118 | app.get("/anime/:animeId/:episodeId", async (req, res) => {
119 | try {
120 | const id = req.params.animeId;
121 | const ep = req.params.episodeId;
122 | const data = await (0, parser_1.animeVideoSource)(id, ep);
123 | return res.status(200).json(data);
124 | }
125 | catch (err) {
126 | return res.status(500).json({
127 | error: "Internal Error",
128 | message: err.toString(),
129 | });
130 | }
131 | });
132 | exports.default = app;
133 |
--------------------------------------------------------------------------------
/dist/source/kuramanime/parser.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.animeVideoSource = exports.anime = exports.season = exports.seasonList = exports.genre = exports.genreList = exports.popular = exports.search = exports.recentRelease = void 0;
7 | const axios_1 = __importDefault(require("axios"));
8 | const cheerio_1 = __importDefault(require("cheerio"));
9 | const BASEURL = "https://kuramanime.net";
10 | const recentRelease = async (page = 1) => {
11 | let list = [];
12 | try {
13 | const base = await axios_1.default.get(`${BASEURL}/anime/ongoing?order_by=latest&page=${page}`);
14 | const $ = cheerio_1.default.load(base.data);
15 | if (!$("#animeList .product__item").html()) {
16 | throw new Error("Page not found, you may request more than the maximum page");
17 | }
18 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
19 | .last()
20 | .html();
21 | $("#animeList .product__item").each((i, el) => {
22 | return list.push({
23 | slug: $(el).find("a").attr("href")?.split("/")[5],
24 | title: $(el).find(".product__item__text>h5>a").text(),
25 | episode: ~~$(el)
26 | .find(".ep span")
27 | .text()
28 | .replace("Ep", "")
29 | .split("/")[0]
30 | .trim(),
31 | cover: $(el).attr("data-setbg"),
32 | url: $(el).find("a").attr("href"),
33 | });
34 | });
35 | return {
36 | page: ~~page,
37 | maxPage: maxPage,
38 | list: list,
39 | };
40 | }
41 | catch (err) {
42 | throw err;
43 | }
44 | };
45 | exports.recentRelease = recentRelease;
46 | const search = async (query, page = 1) => {
47 | let list = [];
48 | try {
49 | const base = await axios_1.default.get(`${BASEURL}/anime?search=${query}&order_by=latest&page=${page}`);
50 | const $ = cheerio_1.default.load(base.data);
51 | if (!$("#animeList .product__item").html()) {
52 | throw new Error("Anime not found,or you may request more than the maximum page");
53 | }
54 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
55 | .last()
56 | .html();
57 | $("#animeList .product__item").each((i, el) => {
58 | list.push({
59 | slug: $(el).find("a").attr("href")?.split("/")[5],
60 | title: $(el).find(".product__item__text>h5>a").text(),
61 | cover: $(el).attr("data-setbg"),
62 | url: $(el).find("a").attr("href"),
63 | });
64 | });
65 | if (list.length == 0) {
66 | throw new Error("Anime not found");
67 | }
68 | return {
69 | page: ~~page,
70 | maxPage: maxPage,
71 | list: list,
72 | };
73 | }
74 | catch (err) {
75 | throw err;
76 | }
77 | };
78 | exports.search = search;
79 | const popular = async (page = 1) => {
80 | let list = [];
81 | try {
82 | const base = await axios_1.default.get(`${BASEURL}/anime/ongoing?order_by=popular&page=${page}`);
83 | const $ = cheerio_1.default.load(base.data);
84 | if (!$("#animeList .product__item").html()) {
85 | throw new Error("Page not found, you may request more than the maximum page");
86 | }
87 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
88 | .last()
89 | .html();
90 | $("#animeList .product__item").each((i, el) => {
91 | list.push({
92 | slug: $(el).find("a").attr("href")?.split("/")[5],
93 | title: $(el).find(".product__item__text>h5>a").text(),
94 | episode: ~~$(el)
95 | .find(".ep span")
96 | .text()
97 | .replace("Ep", "")
98 | .split("/")[0]
99 | .trim(),
100 | cover: $(el).attr("data-setbg"),
101 | url: $(el).find("a").attr("href"),
102 | });
103 | });
104 | return {
105 | page: ~~page,
106 | maxPage: maxPage,
107 | list: list,
108 | };
109 | }
110 | catch (err) {
111 | throw err;
112 | }
113 | };
114 | exports.popular = popular;
115 | const genreList = async (page = 1) => {
116 | let list = [];
117 | try {
118 | const base = await axios_1.default.get(`${BASEURL}/properties/genre?genre_type=all&page=${page}`);
119 | const $ = cheerio_1.default.load(base.data);
120 | if (!$("#animeList .kuramanime__genres li").html()) {
121 | throw new Error("Page not found");
122 | }
123 | $("#animeList .kuramanime__genres li").each((i, el) => {
124 | list.push({
125 | slug: $(el).find("a").attr("href")?.split("/")[5]?.split("?")[0],
126 | title: $(el).find("a").text(),
127 | url: $(el).find("a").attr("href")?.trim(),
128 | });
129 | });
130 | return list;
131 | }
132 | catch (err) {
133 | throw err;
134 | }
135 | };
136 | exports.genreList = genreList;
137 | const genre = async (genre, page = 1) => {
138 | let list = [];
139 | try {
140 | const base = await axios_1.default.get(`${BASEURL}/properties/genre/${genre}?order_by=latest&page=${page}`);
141 | const $ = cheerio_1.default.load(base.data);
142 | if (!$("#animeList .product__item").html()) {
143 | throw new Error("Genre not found, or you may request more than the maximum page");
144 | }
145 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
146 | .last()
147 | .html();
148 | $("#animeList .product__item").each((i, el) => {
149 | list.push({
150 | slug: $(el).find("a").attr("href")?.split("/")[5],
151 | title: $(el).find(".product__item__text>h5>a").text(),
152 | cover: $(el).attr("data-setbg"),
153 | url: $(el).find("a").attr("href"),
154 | });
155 | });
156 | return {
157 | page: ~~page,
158 | maxPage: maxPage,
159 | list: list,
160 | };
161 | }
162 | catch (err) {
163 | throw err;
164 | }
165 | };
166 | exports.genre = genre;
167 | const seasonList = async (page = 1) => {
168 | let list = [];
169 | try {
170 | const base = await axios_1.default.get(`${BASEURL}/properties/season?page=${page}`);
171 | const $ = cheerio_1.default.load(base.data);
172 | if (!$("#animeList .kuramanime__genres li").html()) {
173 | throw new Error("Page not found");
174 | }
175 | $("#animeList .kuramanime__genres li").each((i, el) => {
176 | list.push({
177 | slug: $(el).find("a").attr("href")?.split("/")[5].split("?")[0],
178 | title: $(el).find("a").text(),
179 | url: $(el).find("a").attr("href")?.trim(),
180 | });
181 | });
182 | return list;
183 | }
184 | catch (err) {
185 | throw err;
186 | }
187 | };
188 | exports.seasonList = seasonList;
189 | const season = async (season, page = 1) => {
190 | let list = [];
191 | try {
192 | const base = await axios_1.default.get(`${BASEURL}/properties/season/${season}?order_by=latest&page=${page}`);
193 | const $ = cheerio_1.default.load(base.data);
194 | if (!$("#animeList .product__item").html()) {
195 | throw new Error("Season not found, or you may request more than the maximum page");
196 | }
197 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
198 | .last()
199 | .html();
200 | $("#animeList .product__item").each((i, el) => {
201 | list.push({
202 | slug: $(el).find("a").attr("href")?.split("/")[5],
203 | title: $(el).find(".product__item__text>h5>a").text(),
204 | cover: $(el).attr("data-setbg"),
205 | url: $(el).find("a").attr("href"),
206 | });
207 | });
208 | return {
209 | page: ~~page,
210 | maxPage: maxPage,
211 | list: list,
212 | };
213 | }
214 | catch (err) {
215 | throw err;
216 | }
217 | };
218 | exports.season = season;
219 | const anime = async (slug) => {
220 | try {
221 | const base = await axios_1.default.get(`${BASEURL}/anime/${slug}`);
222 | const $ = cheerio_1.default.load(base.data);
223 | if (!$(".anime__details__widget ul").html()) {
224 | throw new Error("Anime not found");
225 | }
226 | let genre = [];
227 | $(".anime__details__widget ul")
228 | .find('span:contains("Genre:"),span:contains("Tema:")')
229 | .parent()
230 | .find("a")
231 | .each((i, el) => {
232 | genre.push($(el).text().replace(",", "").trim());
233 | });
234 | let episode = [];
235 | let $$ = cheerio_1.default.load($("#episodeListsSection > #episodeLists").attr("data-content"));
236 | $$("a").each((i, el) => {
237 | episode.push($(el).text().trim());
238 | });
239 | return {
240 | slug: $('meta[property="og:url"]').attr("content")?.split("/")[5],
241 | title: $(".anime__details__title > h3").text(),
242 | titleAlt: $(".anime__details__title > span").text(),
243 | synopsis: $("#synopsisField").text(),
244 | episodeTotal: ~~$(".anime__details__widget ul")
245 | .find('span:contains("Total Eps:")')
246 | .next()
247 | .text(),
248 | episode: episode.length,
249 | season: $(".anime__details__widget ul")
250 | .find('span:contains("Musim:")')
251 | .next()
252 | .text(),
253 | genre: genre,
254 | cover: $(".set-bg").attr("data-setbg"),
255 | url: $('meta[property="og:url"]').attr("content"),
256 | };
257 | }
258 | catch (err) {
259 | throw err;
260 | }
261 | };
262 | exports.anime = anime;
263 | const animeVideoSource = async (slug, ep) => {
264 | try {
265 | const url = await axios_1.default.head(`${BASEURL}/anime/${slug}`);
266 | const actualUrl = await url.request.res.responseUrl;
267 | const base = await axios_1.default.get(actualUrl + `/episode/${ep}?activate_stream=1`);
268 | const $ = cheerio_1.default.load(base.data);
269 | let videoSource = [];
270 | if (!$("#animeVideoPlayer video").html()) {
271 | throw new Error("Episode not found");
272 | }
273 | $("#animeVideoPlayer video")
274 | .find("source")
275 | .each((i, el) => {
276 | videoSource.push({
277 | quality: $(el).attr("size") + "p",
278 | url: $(el).attr("src"),
279 | });
280 | });
281 | return {
282 | episode: ~~ep,
283 | video: videoSource,
284 | };
285 | }
286 | catch (err) {
287 | throw err;
288 | }
289 | };
290 | exports.animeVideoSource = animeVideoSource;
291 |
--------------------------------------------------------------------------------
/dist/source/kuronime/decryptor.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | var Chara = [
4 | "",
5 | "split",
6 | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/",
7 | "slice",
8 | "indexOf",
9 | "",
10 | "",
11 | ".",
12 | "pow",
13 | "reduce",
14 | "reverse",
15 | "0",
16 | ];
17 | function charDec(d, e, f) {
18 | var g = Chara[2][Chara[1]](Chara[0]);
19 | var h = g[Chara[3]](0, e);
20 | var i = g[Chara[3]](0, f);
21 | var j = d[Chara[1]](Chara[0])[Chara[10]]()[Chara[9]](function (a, b, c) {
22 | if (h[Chara[4]](b) !== -1)
23 | return (a += h[Chara[4]](b) * Math[Chara[8]](e, c));
24 | }, 0);
25 | var k = Chara[0];
26 | while (j > 0) {
27 | k = i[j % f] + k;
28 | j = (j - (j % f)) / f;
29 | }
30 | return k || Chara[11];
31 | }
32 | function decy(h, u, n, t, e, r) {
33 | r = "";
34 | for (var i = 0, len = h.length; i < len; i++) {
35 | var s = "";
36 | while (h[i] !== n[e]) {
37 | s += h[i];
38 | i++;
39 | }
40 | for (var j = 0; j < n.length; j++)
41 | s = s.replace(new RegExp(n[j], "g"), j);
42 | r += String.fromCharCode(charDec(s, e, 10) - t);
43 | }
44 | return decodeURIComponent(escape(r));
45 | }
46 | exports.default = decy;
47 |
--------------------------------------------------------------------------------
/dist/source/kuronime/kuronime.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const parser_1 = require("./parser");
8 | const app = express_1.default.Router();
9 | app.get("/", async (req, res) => {
10 | return res.status(200).send("Kuronime Server is ready 🚀");
11 | });
12 | app.get("/recent", async (req, res) => {
13 | try {
14 | const { page } = req.query;
15 | const data = await (0, parser_1.recentRelease)(page);
16 | return res.status(200).json(data);
17 | }
18 | catch (err) {
19 | return res.status(500).json({
20 | error: "Internal Error",
21 | message: err.toString(),
22 | });
23 | }
24 | });
25 | app.get("/search", async (req, res) => {
26 | try {
27 | const { page, query } = req.query;
28 | const data = await (0, parser_1.search)(query, page);
29 | return res.status(200).json(data);
30 | }
31 | catch (err) {
32 | return res.status(500).json({
33 | error: "Internal Error",
34 | message: err.toString(),
35 | });
36 | }
37 | });
38 | app.get("/popular", async (req, res) => {
39 | try {
40 | const { page } = req.query;
41 | const data = await (0, parser_1.popular)(page);
42 | return res.status(200).json(data);
43 | }
44 | catch (err) {
45 | return res.status(500).json({
46 | error: "Internal Error",
47 | message: err.toString(),
48 | });
49 | }
50 | });
51 | app.get("/genre", async (req, res) => {
52 | try {
53 | const { page } = req.query;
54 | const data = await (0, parser_1.genreList)(page);
55 | return res.status(200).json(data);
56 | }
57 | catch (err) {
58 | return res.status(500).json({
59 | error: "Internal Error",
60 | message: err.toString(),
61 | });
62 | }
63 | });
64 | app.get("/genre/:genre", async (req, res) => {
65 | try {
66 | const genreType = req.params.genre;
67 | const { page } = req.query;
68 | const data = await (0, parser_1.genre)(genreType, page);
69 | return res.status(200).json(data);
70 | }
71 | catch (err) {
72 | return res.status(500).json({
73 | error: "Internal Error",
74 | message: err.toString(),
75 | });
76 | }
77 | });
78 | app.get("/season", async (req, res) => {
79 | try {
80 | const { page } = req.query;
81 | const data = await (0, parser_1.seasonList)(page);
82 | return res.status(200).json(data);
83 | }
84 | catch (err) {
85 | return res.status(500).json({
86 | error: "Internal Error",
87 | message: err.toString(),
88 | });
89 | }
90 | });
91 | app.get("/season/:season", async (req, res) => {
92 | try {
93 | const seasonYear = req.params.season;
94 | const { page } = req.query;
95 | const data = await (0, parser_1.season)(seasonYear, page);
96 | return res.status(200).json(data);
97 | }
98 | catch (err) {
99 | return res.status(500).json({
100 | error: "Internal Error",
101 | message: err.toString(),
102 | });
103 | }
104 | });
105 | app.get("/anime/:animeSlug", async (req, res) => {
106 | try {
107 | const slug = req.params.animeSlug;
108 | const data = await (0, parser_1.anime)(slug);
109 | return res.status(200).json(data);
110 | }
111 | catch (err) {
112 | return res.status(500).json({
113 | error: "Internal Error",
114 | message: err.toString(),
115 | });
116 | }
117 | });
118 | app.get("/anime/:animeId/:episodeId", async (req, res) => {
119 | try {
120 | const id = req.params.animeId;
121 | const ep = req.params.episodeId;
122 | const data = await (0, parser_1.animeVideoSource)(id, ep);
123 | return res.status(200).json(data);
124 | }
125 | catch (err) {
126 | return res.status(500).json({
127 | error: "Internal Error",
128 | message: err.toString(),
129 | });
130 | }
131 | });
132 | exports.default = app;
133 |
--------------------------------------------------------------------------------
/dist/source/kuronime/parser.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.animeVideoSource = exports.anime = exports.season = exports.seasonList = exports.genre = exports.genreList = exports.popular = exports.search = exports.recentRelease = void 0;
7 | const axios_1 = __importDefault(require("axios"));
8 | const cheerio_1 = __importDefault(require("cheerio"));
9 | const decryptor_js_1 = __importDefault(require("./decryptor.js"));
10 | const BASEURL = "https://45.12.2.2";
11 | const recentRelease = async (page = 1) => {
12 | let list = [];
13 | try {
14 | const base = await axios_1.default.get(`${BASEURL}/page/${page}`);
15 | const $ = cheerio_1.default.load(base.data);
16 | if (!$(".postbody").html()) {
17 | throw new Error("Page not found");
18 | }
19 | let maxPage = ~~$(".postbody>.bixbox")
20 | .first()
21 | .find(".pagination a:not(.prev,.next)")
22 | .last()
23 | .text()
24 | .replace(",", "");
25 | $(".postbody>.bixbox")
26 | .first()
27 | .find(".excstf article")
28 | .each((i, el) => {
29 | return list.push({
30 | slug: $(el)
31 | .find("a")
32 | .attr("href")
33 | ?.split("/")[3]
34 | .replace(/\b(?:nonton-|-episode-[a-zA-Z0-9_]*)\b/gi, ""),
35 | title: $(el).find(".bsuxtt").text(),
36 | episode: ~~$(el).find(".bt .ep").text().replace("Episode", "").trim(),
37 | cover: $(el).find(".limit img[itemprop]").attr("data-src")?.split("?")[0] + "?resize=141,200",
38 | url: $(el)
39 | .find("a")
40 | .attr("href")
41 | ?.replace(/\b(?:nonton-|-episode-[a-zA-Z0-9_]*)\b/gi, ""),
42 | });
43 | });
44 | return {
45 | page: ~~page,
46 | maxPage: maxPage,
47 | list: list,
48 | };
49 | }
50 | catch (err) {
51 | throw err;
52 | }
53 | };
54 | exports.recentRelease = recentRelease;
55 | const search = async (query, page = 1) => {
56 | let list = [];
57 | try {
58 | const base = await axios_1.default.get(`${BASEURL}/anime/page/${page}/?title=${query}&order=update`);
59 | const $ = cheerio_1.default.load(base.data);
60 | if (!$(".postbody").html()) {
61 | throw new Error("Page not found");
62 | }
63 | let maxPage = ~~$(".postbody>.bixbox")
64 | .first()
65 | .find(".pagination a:not(.prev,.next)")
66 | .last()
67 | .text()
68 | .replace(",", "");
69 | $(".postbody>.bixbox")
70 | .first()
71 | .find(".listupd article")
72 | .each((i, el) => {
73 | return list.push({
74 | slug: $(el).find("a").attr("href")?.split("/")[4],
75 | title: $(el).find("a").attr("title"),
76 | cover: $(el).find(".limit img[itemprop]").attr("data-src")?.split("?")[0] + "?resize=141,200",
77 | url: $(el).find("a").attr("href"),
78 | });
79 | });
80 | if (list.length == 0) {
81 | throw new Error("Anime not found");
82 | }
83 | return {
84 | page: ~~page,
85 | maxPage: maxPage,
86 | list: list,
87 | };
88 | }
89 | catch (err) {
90 | throw err;
91 | }
92 | };
93 | exports.search = search;
94 | const popular = async (page = 1) => {
95 | let list = [];
96 | try {
97 | const base = await axios_1.default.get(`${BASEURL}/popular-anime/page/${page}`);
98 | const $ = cheerio_1.default.load(base.data);
99 | if (!$(".postbody").html()) {
100 | throw new Error("Page not found");
101 | }
102 | let maxPage = ~~$(".postbody>.bixbox")
103 | .first()
104 | .find(".pagination a:not(.prev,.next)")
105 | .last()
106 | .text()
107 | .replace(",", "");
108 | $(".postbody>.bixbox")
109 | .first()
110 | .find(".listupd article")
111 | .each((i, el) => {
112 | return list.push({
113 | slug: $(el).find("a").attr("href")?.split("/")[4],
114 | title: $(el).find("a").attr("title"),
115 | cover: $(el).find(".limit img[itemprop]").attr("data-src")?.split("?")[0] + "?resize=141,200",
116 | url: $(el).find("a").attr("href"),
117 | });
118 | });
119 | return {
120 | page: ~~page,
121 | maxPage: maxPage,
122 | list: list,
123 | };
124 | }
125 | catch (err) {
126 | throw err;
127 | }
128 | };
129 | exports.popular = popular;
130 | const genreList = async (page = 1) => {
131 | let list = [];
132 | try {
133 | const base = await axios_1.default.get(`${BASEURL}/genres`);
134 | const $ = cheerio_1.default.load(base.data);
135 | if (!$(".postbody").html()) {
136 | throw new Error("Page not found");
137 | }
138 | $(".postbody>.bixbox")
139 | .first()
140 | .find(".genre li")
141 | .each((i, el) => {
142 | list.push({
143 | slug: $(el).find("a").attr("href")?.split("/")[4],
144 | title: $(el).find("a").text(),
145 | url: $(el).find("a").attr("href"),
146 | });
147 | });
148 | return list;
149 | }
150 | catch (err) {
151 | throw err;
152 | }
153 | };
154 | exports.genreList = genreList;
155 | const genre = async (genre, page = 1) => {
156 | let list = [];
157 | try {
158 | const base = await axios_1.default.get(`${BASEURL}/genres/${genre}/page/${page}`);
159 | const $ = cheerio_1.default.load(base.data);
160 | if (!$(".postbody").html()) {
161 | throw new Error("Page not found");
162 | }
163 | let maxPage = ~~$(".postbody>.bixbox")
164 | .first()
165 | .find(".pagination a:not(.prev,.next)")
166 | .last()
167 | .text()
168 | .replace(",", "");
169 | $(".postbody>.bixbox")
170 | .first()
171 | .find(".listupd article")
172 | .each((i, el) => {
173 | return list.push({
174 | slug: $(el).find("a").attr("href")?.split("/")[4],
175 | title: $(el).find("a").attr("title"),
176 | cover: $(el).find(".limit img[itemprop]").attr("data-src")?.split("?")[0] + "?resize=141,200",
177 | url: $(el).find("a").attr("href"),
178 | });
179 | });
180 | return {
181 | page: ~~page,
182 | maxPage: maxPage,
183 | list: list,
184 | };
185 | }
186 | catch (err) {
187 | throw err;
188 | }
189 | };
190 | exports.genre = genre;
191 | const seasonList = async (page = 1) => {
192 | let list = [];
193 | try {
194 | const base = await axios_1.default.get(`${BASEURL}/season/winter-2023`);
195 | const $ = cheerio_1.default.load(base.data);
196 | if (!$(".postbody").html()) {
197 | throw new Error("Page not found");
198 | }
199 | $(".postbody>.bixbox")
200 | .first()
201 | .find("#season option:not([selected])")
202 | .each((i, el) => {
203 | list.push({
204 | slug: $(el).attr("value"),
205 | title: $(el).text(),
206 | url: `${BASEURL}/season/` + $(el).attr("value"),
207 | });
208 | });
209 | return list;
210 | }
211 | catch (err) {
212 | throw err;
213 | }
214 | };
215 | exports.seasonList = seasonList;
216 | const season = async (season, page = 1) => {
217 | let list = [];
218 | try {
219 | const base = await axios_1.default.get(`${BASEURL}/season/${season}`);
220 | const $ = cheerio_1.default.load(base.data);
221 | if (!$(".postbody").html()) {
222 | throw new Error("Page not found");
223 | }
224 | let maxPage = 0;
225 | $(".postbody>.bixbox")
226 | .first()
227 | .find(".listupd .sebox")
228 | .each((i, el) => {
229 | return list.push({
230 | slug: $(el).find(".tisebox a").attr("href")?.split("/")[4],
231 | title: $(el).find(".tisebox a").text(),
232 | cover: $(el).find(".bigsebox img").attr("data-src")?.split("?")[0] + "?resize=141,200",
233 | url: $(el).find(".tisebox a").attr("href"),
234 | });
235 | });
236 | return {
237 | page: ~~page,
238 | maxPage: maxPage,
239 | list: list,
240 | };
241 | }
242 | catch (err) {
243 | throw err;
244 | }
245 | };
246 | exports.season = season;
247 | const anime = async (slug) => {
248 | try {
249 | const base = await axios_1.default.get(`${BASEURL}/anime/${slug}`);
250 | const $ = cheerio_1.default.load(base.data);
251 | if (!$(".postbody").html()) {
252 | throw new Error("Anime not found");
253 | }
254 | let genre = [];
255 | $(".postbody article .entry-content")
256 | .find(".infodetail ul li:nth-child(2) a")
257 | .each((i, el) => {
258 | genre.push($(el).text());
259 | });
260 | let episode = [];
261 | $(".postbody article .entry-content")
262 | .find(".bixbox.bxcl ul li")
263 | .each((i, el) => {
264 | if (!$(el).find(".lchx a").text().match(/OVA/)) {
265 | episode.push($(el).find(".lchx a").text());
266 | }
267 | });
268 | return {
269 | slug: $('meta[property="og:url"]').attr("content")?.split("/")[4],
270 | title: $(".postbody .info-header h1.entry-title").text(),
271 | titleAlt: $(".postbody .infodetail ul li:first-child").text().replace("Judul:", "").trim(),
272 | synopsis: $(".postbody .main-info .r .conx p").text(),
273 | episodeTotal: ~~$(".postbody .infodetail ul li:nth-child(9)").text().split(":")[1].trim() | 0,
274 | episode: episode.length,
275 | season: $(".postbody .infodetail ul li:nth-child(6)").find("a").text(),
276 | genre: genre,
277 | cover: $(".postbody .main-info .l img").attr("data-src")?.split("?")[0] + "?resize=141,200",
278 | url: $('meta[property="og:url"]').attr("content"),
279 | };
280 | }
281 | catch (err) {
282 | throw err;
283 | }
284 | };
285 | exports.anime = anime;
286 | const animeVideoSource = async (slug, ep) => {
287 | try {
288 | const base = await axios_1.default.get(`${BASEURL}/nonton-${slug}-episode-${ep}`);
289 | const $ = cheerio_1.default.load(base.data);
290 | if (!$(".postbody").html()) {
291 | throw new Error("Episode not found");
292 | }
293 | let embedUrl = $("#tonton.video-content .player-embed")
294 | .find("iframe")
295 | .attr("data-src");
296 | const videoSourceBase = await axios_1.default.get(embedUrl);
297 | const $$ = cheerio_1.default.load(videoSourceBase.data);
298 | if (!!$(".postbody .megavid .video-nav .iconx a").html()) {
299 | let enc = $$("script:not([src])")
300 | .first()
301 | .html()
302 | .match(/\(r\)\)\}(.*)/);
303 | let penc = enc[1].replace(/[\(\)"']/g, "").split(",");
304 | let decrypt = (0, decryptor_js_1.default)(penc[0], parseInt(penc[1]), penc[2], parseInt(penc[3]), parseInt(penc[4]), parseInt(penc[5]));
305 | let getSrcs = JSON.parse(decrypt.match(/srcs\s*=\s*(\[.*\])/)[1]);
306 | let videoSource = [];
307 | const waitSrc = getSrcs.map(async (el, i) => {
308 | const url = await axios_1.default.get(el.file, { maxRedirects: 0 });
309 | let $$$ = cheerio_1.default.load(url.data);
310 | let surl = $$$("a").attr("href");
311 | return {
312 | quality: el.label === "HD" ? "720p" : el.label === "SD" ? "480p" : "Unknown",
313 | url: surl,
314 | };
315 | });
316 | videoSource = await Promise.all(waitSrc);
317 | return {
318 | episode: ~~ep,
319 | video: videoSource,
320 | };
321 | }
322 | else {
323 | throw new Error("Video source are not supported.");
324 | }
325 | }
326 | catch (err) {
327 | throw err;
328 | }
329 | };
330 | exports.animeVideoSource = animeVideoSource;
331 |
--------------------------------------------------------------------------------
/dist/source/nanime/nanime.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const parser_1 = require("./parser");
8 | const app = express_1.default.Router();
9 | app.get("/", async (req, res) => {
10 | return res.status(200).send("Nanime Server is ready 🚀");
11 | });
12 | app.get("/recent", async (req, res) => {
13 | try {
14 | const { page } = req.query;
15 | const data = await (0, parser_1.recentRelease)(page);
16 | return res.status(200).json(data);
17 | }
18 | catch (err) {
19 | return res.status(500).json({
20 | error: "Internal Error",
21 | message: err.toString(),
22 | });
23 | }
24 | });
25 | app.get("/search", async (req, res) => {
26 | try {
27 | const { page, query } = req.query;
28 | const data = await (0, parser_1.search)(query, page);
29 | return res.status(200).json(data);
30 | }
31 | catch (err) {
32 | return res.status(500).json({
33 | error: "Internal Error",
34 | message: err.toString(),
35 | });
36 | }
37 | });
38 | app.get("/popular", async (req, res) => {
39 | return res.status(500).json({
40 | error: "Internal Error",
41 | message: "This request is not available for this server",
42 | });
43 | });
44 | app.get("/genre", async (req, res) => {
45 | try {
46 | const { page } = req.query;
47 | const data = await (0, parser_1.genreList)(page);
48 | return res.status(200).json(data);
49 | }
50 | catch (err) {
51 | return res.status(500).json({
52 | error: "Internal Error",
53 | message: err.toString(),
54 | });
55 | }
56 | });
57 | app.get("/genre/:genre", async (req, res) => {
58 | try {
59 | const genreType = req.params.genre;
60 | const { page } = req.query;
61 | const data = await (0, parser_1.genre)(genreType, page);
62 | return res.status(200).json(data);
63 | }
64 | catch (err) {
65 | return res.status(500).json({
66 | error: "Internal Error",
67 | message: err.toString(),
68 | });
69 | }
70 | });
71 | app.get("/season", async (req, res) => {
72 | try {
73 | const { page } = req.query;
74 | const data = await (0, parser_1.seasonList)(page);
75 | return res.status(200).json(data);
76 | }
77 | catch (err) {
78 | return res.status(500).json({
79 | error: "Internal Error",
80 | message: err.toString(),
81 | });
82 | }
83 | });
84 | app.get("/season/:season", async (req, res) => {
85 | try {
86 | const seasonYear = req.params.season;
87 | const { page } = req.query;
88 | const data = await (0, parser_1.season)(seasonYear, page);
89 | return res.status(200).json(data);
90 | }
91 | catch (err) {
92 | return res.status(500).json({
93 | error: "Internal Error",
94 | message: err.toString(),
95 | });
96 | }
97 | });
98 | app.get("/anime/:animeSlug", async (req, res) => {
99 | try {
100 | const slug = req.params.animeSlug;
101 | const data = await (0, parser_1.anime)(slug);
102 | return res.status(200).json(data);
103 | }
104 | catch (err) {
105 | return res.status(500).json({
106 | error: "Internal Error",
107 | message: err.toString(),
108 | });
109 | }
110 | });
111 | app.get("/anime/:animeId/:episodeId", async (req, res) => {
112 | try {
113 | const id = req.params.animeId;
114 | const ep = req.params.episodeId;
115 | const data = await (0, parser_1.animeVideoSource)(id, ep);
116 | return res.status(200).json(data);
117 | }
118 | catch (err) {
119 | return res.status(500).json({
120 | error: "Internal Error",
121 | message: err.toString(),
122 | });
123 | }
124 | });
125 | exports.default = app;
126 |
--------------------------------------------------------------------------------
/dist/source/nanime/parser.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.animeVideoSource = exports.anime = exports.season = exports.seasonList = exports.genre = exports.genreList = exports.search = exports.recentRelease = void 0;
7 | const axios_1 = __importDefault(require("axios"));
8 | const cheerio_1 = __importDefault(require("cheerio"));
9 | const BASEURL = "https://nanimex1.com";
10 | const recentRelease = async (page = 1) => {
11 | let list = [];
12 | try {
13 | const base = await axios_1.default.get(`${BASEURL}/page/${page}`);
14 | const $ = cheerio_1.default.load(base.data);
15 | let maxPage = ~~$(".box-poster .btn-group .page-numbers:not(.prev,.next)").last().html();
16 | if (!maxPage) {
17 | throw new Error("Page not found, you may request more than the maximum page");
18 | }
19 | $(".box-poster .content-item").each((i, el) => {
20 | return list.push({
21 | slug: $(el).find("a").attr("href")?.split("/")[4],
22 | title: $(el).find("h3").attr("title"),
23 | episode: ~~$(el).find(".episode .btn-danger").text().replace("Episode", "").trim(),
24 | cover: $(el).find(".poster img").attr("data-lazy-src"),
25 | url: $(el).find("a").attr("href"),
26 | });
27 | });
28 | return {
29 | page: ~~page,
30 | maxPage: maxPage,
31 | list: list,
32 | };
33 | }
34 | catch (err) {
35 | throw err;
36 | }
37 | };
38 | exports.recentRelease = recentRelease;
39 | const search = async (query, page = 1) => {
40 | let list = [];
41 | try {
42 | const base = await axios_1.default.get(`${BASEURL}/page/${page}/?s=${query}`);
43 | const $ = cheerio_1.default.load(base.data);
44 | let maxPage = ~~$(".box-poster .btn-group .page-numbers:not(.prev,.next)").last().html();
45 | if (!maxPage) {
46 | throw new Error("Anime not found, or you may request more than the maximum page");
47 | }
48 | $(".box-poster .content-item").each((i, el) => {
49 | if (!!$(el).find('.episode .btn-danger:contains("Episode")').html()) {
50 | return list.push({
51 | slug: $(el).find("a").attr("href")?.split("/")[4],
52 | title: $(el).find("h3").attr("title"),
53 | episode: ~~$(el).find(".episode .btn-danger").text().replace("Episode", "").trim(),
54 | cover: $(el).find(".poster img").attr("src"),
55 | url: $(el).find("a").attr("href"),
56 | });
57 | }
58 | });
59 | if (list.length == 0) {
60 | throw new Error("Anime not found");
61 | }
62 | return {
63 | page: ~~page,
64 | maxPage: maxPage,
65 | list: list,
66 | };
67 | }
68 | catch (err) {
69 | throw err;
70 | }
71 | };
72 | exports.search = search;
73 | const genreList = async (page = 1) => {
74 | let list = [];
75 | try {
76 | const base = await axios_1.default.get(`${BASEURL}`);
77 | const $ = cheerio_1.default.load(base.data);
78 | if (!$(".box-content:last-child .box-primary:nth-child(2) .box-body a").html()) {
79 | throw new Error("Page not found, you may request more than the maximum page");
80 | }
81 | $(".box-content:last-child .box-primary:nth-child(2) .box-body a").each((i, el) => {
82 | list.push({
83 | slug: $(el).attr("href")?.split("/")[4],
84 | title: $(el).text(),
85 | url: $(el).attr("href"),
86 | });
87 | });
88 | return list;
89 | }
90 | catch (err) {
91 | throw err;
92 | }
93 | };
94 | exports.genreList = genreList;
95 | const genre = async (genre, page = 1) => {
96 | let list = [];
97 | try {
98 | const base = await axios_1.default.get(`${BASEURL}/genre/${genre}/page/${page}`);
99 | const $ = cheerio_1.default.load(base.data);
100 | let maxPage = ~~$(".box-poster .btn-group .page-numbers:not(.prev,.next)").last().html();
101 | if (!maxPage) {
102 | throw new Error("Genre not found, you may request more than the maximum page");
103 | }
104 | $(".box-poster .content-item").each((i, el) => {
105 | if (!!$(el).find('.episode .btn-danger:contains("Episode")').html()) {
106 | return list.push({
107 | slug: $(el).find("a").attr("href")?.split("/")[4],
108 | title: $(el).find("h3").attr("title"),
109 | episode: ~~$(el).find(".episode .btn-danger").text().replace("Episode", "").trim(),
110 | cover: $(el).find(".poster img").attr("data-lazy-src"),
111 | url: $(el).find("a").attr("href"),
112 | });
113 | }
114 | });
115 | if (list.length == 0) {
116 | throw new Error("Anime not found");
117 | }
118 | return {
119 | page: ~~page,
120 | maxPage: maxPage,
121 | list: list,
122 | };
123 | }
124 | catch (err) {
125 | throw err;
126 | }
127 | };
128 | exports.genre = genre;
129 | const seasonList = async (page = 1) => {
130 | let list = [];
131 | try {
132 | const base = await axios_1.default.get(`${BASEURL}/properties/season?page=${page}`);
133 | const $ = cheerio_1.default.load(base.data);
134 | if (!$(".box-content:last-child .box-primary:nth-child(2) .box-body a").html()) {
135 | throw new Error("Page not found, or you may request more than the maximum page");
136 | }
137 | $(".box-content:last-child .box-primary:nth-child(3) .box-body a").each((i, el) => {
138 | list.push({
139 | slug: $(el).attr("href")?.split("/")[4],
140 | title: $(el).text(),
141 | url: $(el).attr("href"),
142 | });
143 | });
144 | return list;
145 | }
146 | catch (err) {
147 | throw err;
148 | }
149 | };
150 | exports.seasonList = seasonList;
151 | const season = async (season, page = 1) => {
152 | let list = [];
153 | try {
154 | const base = await axios_1.default.get(`${BASEURL}/year_/${season}/page/${page}`);
155 | const $ = cheerio_1.default.load(base.data);
156 | let maxPage = ~~$(".box-poster .btn-group .page-numbers:not(.prev,.next)").last().html();
157 | if (!maxPage) {
158 | throw new Error("Season not found, you may request more than the maximum page");
159 | }
160 | $(".box-poster .content-item").each((i, el) => {
161 | if (!!$(el).find('.episode .btn-danger:contains("Episode")').html()) {
162 | return list.push({
163 | slug: $(el).find("a").attr("href")?.split("/")[4],
164 | title: $(el).find("h3").attr("title"),
165 | episode: ~~$(el).find(".episode .btn-danger").text().replace("Episode", "").trim(),
166 | cover: $(el).find(".poster img").attr("data-lazy-src"),
167 | url: $(el).find("a").attr("href"),
168 | });
169 | }
170 | });
171 | if (list.length == 0) {
172 | throw new Error("Anime not found");
173 | }
174 | return {
175 | page: ~~page,
176 | maxPage: maxPage,
177 | list: list,
178 | };
179 | }
180 | catch (err) {
181 | throw err;
182 | }
183 | };
184 | exports.season = season;
185 | const anime = async (slug) => {
186 | try {
187 | const base = await axios_1.default.get(`${BASEURL}/anime/${slug}`);
188 | const $ = cheerio_1.default.load(base.data);
189 | if (!$(".box-default .box-body > div:nth-child(3) .episode_list").html()) {
190 | throw new Error("Anime not found");
191 | }
192 | let genre = [];
193 | $(".box-default .box-primary table")
194 | .find('td:contains("Genre")')
195 | .parent()
196 | .find("a")
197 | .each((i, el) => {
198 | genre.push($(el).text());
199 | });
200 | let episode = [];
201 | $(".box-default .box-body > div:nth-child(3) .episode_list table")
202 | .find("tr")
203 | .each((i, el) => {
204 | episode.push($(el).text());
205 | });
206 | return {
207 | slug: $('meta[property="og:url"]').attr("content")?.split("/")[4],
208 | title: $(".box-content .box-body > div:nth-child(2) table tr:nth-child(1) > td:nth-child(2) > a:nth-child(1)").text(),
209 | titleAlt: $(".box-content .box-body > div:nth-child(2) table tr:nth-child(2) > td:nth-child(2)").text(),
210 | synopsis: $(".box-content .box-body .attachment-block.clearfix .attachment-text").text(),
211 | episodeTotal: ~~$(".box-default>.box-body>.box-primary table")
212 | .find('td:contains("Total Episode")')
213 | .parent()
214 | .find("td:last-child")
215 | .text(),
216 | episode: episode.length,
217 | season: $(".box-content .box-body > div:nth-child(2) table tr:nth-child(1) > td:nth-child(2) > a:nth-child(2)").text(),
218 | genre: genre,
219 | cover: $(".box-content .box-body .attachment-block.clearfix .attachment-img").attr("data-lazy-src"),
220 | url: $('meta[property="og:url"]').attr("content"),
221 | };
222 | }
223 | catch (err) {
224 | throw err;
225 | }
226 | };
227 | exports.anime = anime;
228 | const animeVideoSource = async (slug, ep) => {
229 | try {
230 | const formattedEp = ("00" + ep).slice(-3);
231 | const base = await axios_1.default.get(`${BASEURL}/episode/${slug}-episode-${formattedEp}`);
232 | const $ = cheerio_1.default.load(base.data);
233 | if (!$("#change-server > option").html()) {
234 | throw new Error("Episode not found");
235 | }
236 | let videoSource = [];
237 | $("#change-server > option").each((i, el) => {
238 | videoSource.push({
239 | quality: $(el).text().split(" ")[0],
240 | url: $(el).attr("value"),
241 | });
242 | });
243 | return {
244 | episode: ~~ep,
245 | video: videoSource,
246 | };
247 | }
248 | catch (err) {
249 | throw err;
250 | }
251 | };
252 | exports.animeVideoSource = animeVideoSource;
253 |
--------------------------------------------------------------------------------
/dist/source/otakudesu/otakudesu.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | const express_1 = __importDefault(require("express"));
7 | const parser_1 = require("./parser");
8 | const app = express_1.default.Router();
9 | app.get("/", async (req, res) => {
10 | return res.status(200).send("Otakudesu Server is ready 🚀");
11 | });
12 | app.get("/recent", async (req, res) => {
13 | try {
14 | const { page } = req.query;
15 | const data = await (0, parser_1.recentRelease)(page);
16 | return res.status(200).json(data);
17 | }
18 | catch (err) {
19 | return res.status(500).json({
20 | error: "Internal Error",
21 | message: err.toString(),
22 | });
23 | }
24 | });
25 | app.get("/search", async (req, res) => {
26 | try {
27 | const { page, query } = req.query;
28 | const data = await (0, parser_1.search)(query, page);
29 | return res.status(200).json(data);
30 | }
31 | catch (err) {
32 | return res.status(500).json({
33 | error: "Internal Error",
34 | message: err.toString(),
35 | });
36 | }
37 | });
38 | app.get("/popular", async (req, res) => {
39 | return res.status(500).json({
40 | error: "Internal Error",
41 | message: "This request is not available for this server",
42 | });
43 | });
44 | app.get("/genre", async (req, res) => {
45 | try {
46 | const { page } = req.query;
47 | const data = await (0, parser_1.genreList)(page);
48 | return res.status(200).json(data);
49 | }
50 | catch (err) {
51 | return res.status(500).json({
52 | error: "Internal Error",
53 | message: err.toString(),
54 | });
55 | }
56 | });
57 | app.get("/genre/:genre", async (req, res) => {
58 | try {
59 | const genreType = req.params.genre;
60 | const { page } = req.query;
61 | const data = await (0, parser_1.genre)(genreType, page);
62 | return res.status(200).json(data);
63 | }
64 | catch (err) {
65 | return res.status(500).json({
66 | error: "Internal Error",
67 | message: err.toString(),
68 | });
69 | }
70 | });
71 | app.get("/season", async (req, res) => {
72 | return res.status(500).json({
73 | error: "Internal Error",
74 | message: "This request is not available for this server",
75 | });
76 | });
77 | app.get("/season/:season", async (req, res) => {
78 | return res.status(500).json({
79 | error: "Internal Error",
80 | message: "This request is not available for this server",
81 | });
82 | });
83 | app.get("/anime/:animeSlug", async (req, res) => {
84 | try {
85 | const slug = req.params.animeSlug;
86 | const data = await (0, parser_1.anime)(slug);
87 | return res.status(200).json(data);
88 | }
89 | catch (err) {
90 | return res.status(500).json({
91 | error: "Internal Error",
92 | message: err.toString(),
93 | });
94 | }
95 | });
96 | app.get("/anime/:animeId/:episodeId", async (req, res) => {
97 | try {
98 | const id = req.params.animeId;
99 | const ep = req.params.episodeId;
100 | const data = await (0, parser_1.animeVideoSource)(id, ep);
101 | return res.status(200).json(data);
102 | }
103 | catch (err) {
104 | return res.status(500).json({
105 | error: "Internal Error",
106 | message: err.toString(),
107 | });
108 | }
109 | });
110 | exports.default = app;
111 |
--------------------------------------------------------------------------------
/dist/source/otakudesu/parser.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | exports.animeVideoSource = exports.anime = exports.genre = exports.genreList = exports.search = exports.recentRelease = void 0;
7 | const axios_1 = __importDefault(require("axios"));
8 | const cheerio_1 = __importDefault(require("cheerio"));
9 | const BASEURL = "https://otakudesu.ltd";
10 | const recentRelease = async (page = 1) => {
11 | let list = [];
12 | try {
13 | const base = await axios_1.default.get(`${BASEURL}/ongoing-anime/page/${page}`);
14 | const $ = cheerio_1.default.load(base.data);
15 | let maxPage = ~~$(".venutama .pagination .page-numbers:not(.prev,.next)")
16 | .last()
17 | .html();
18 | if (!maxPage) {
19 | throw new Error("Page not found, you may request more than the maximum page");
20 | }
21 | $(".venutama .venz ul>li").each((i, el) => {
22 | return list.push({
23 | slug: $(el).find(".thumb a").attr("href")?.split("/")[4],
24 | title: $(el).find(".thumb h2").text(),
25 | episode: ~~$(el).find(".epz").text().replace("Episode", "").trim(),
26 | cover: $(el).find(".thumb img").attr("src"),
27 | url: $(el).find(".thumb a").attr("href"),
28 | });
29 | });
30 | return {
31 | page: ~~page,
32 | maxPage: maxPage,
33 | list: list,
34 | };
35 | }
36 | catch (err) {
37 | throw err;
38 | }
39 | };
40 | exports.recentRelease = recentRelease;
41 | const search = async (query, page = 1) => {
42 | let list = [];
43 | try {
44 | const base = await axios_1.default.get(`${BASEURL}/?s=${query}&post_type=anime`);
45 | const $ = cheerio_1.default.load(base.data);
46 | let maxPage = 1;
47 | if ($(".venutama .chivsrc li").length < 1) {
48 | throw new Error("Anime not found, or you may request more than the maximum page");
49 | }
50 | $(".venutama .chivsrc li").each((i, el) => {
51 | return list.push({
52 | slug: $(el).find("h2>a").attr("href")?.split("/")[4],
53 | title: $(el)
54 | .find("h2>a")
55 | .text()
56 | .replace(/\s*\(Episode[^\)]*\)|\s*Subtitle Indonesia*|\s*Sub Indo*/g, ""),
57 | cover: $(el).find("img").attr("src"),
58 | url: $(el).find("h2>a").attr("href"),
59 | });
60 | });
61 | if (list.length == 0) {
62 | throw new Error("Anime not found");
63 | }
64 | return {
65 | page: ~~page,
66 | maxPage: maxPage,
67 | list: list,
68 | };
69 | }
70 | catch (err) {
71 | throw err;
72 | }
73 | };
74 | exports.search = search;
75 | const genreList = async (page = 1) => {
76 | let list = [];
77 | try {
78 | const base = await axios_1.default.get(`${BASEURL}/genre-list`);
79 | const $ = cheerio_1.default.load(base.data);
80 | $(".genres li a").each((i, el) => {
81 | list.push({
82 | slug: $(el).attr("href")?.split("/")[2],
83 | title: $(el).text(),
84 | url: $(el).attr("href"),
85 | });
86 | });
87 | return list;
88 | }
89 | catch (err) {
90 | throw err;
91 | }
92 | };
93 | exports.genreList = genreList;
94 | const genre = async (genre, page = 1) => {
95 | let list = [];
96 | try {
97 | const base = await axios_1.default.get(`${BASEURL}/genres/${genre}/page/${page}`);
98 | const $ = cheerio_1.default.load(base.data);
99 | let maxPage = ~~$(".venser .pagination .page-numbers:not(.prev,.next)")
100 | .last()
101 | .html();
102 | if (!maxPage) {
103 | throw new Error("Genre not found, you may request more than the maximum page");
104 | }
105 | $(".venser .col-anime").each((i, el) => {
106 | return list.push({
107 | slug: $(el).find(".col-anime-title a").attr("href")?.split("/")[4],
108 | title: $(el).find(".col-anime-title a").text(),
109 | episode: ~~$(el)
110 | .find(".col-anime-eps")
111 | .text()
112 | .replace(/Eps|Unknown/g, "")
113 | .trim(),
114 | cover: $(el).find(".col-anime-cover img").attr("src"),
115 | url: $(el).find(".col-anime-title a").attr("href"),
116 | });
117 | });
118 | if (list.length == 0) {
119 | throw new Error("Anime not found");
120 | }
121 | return {
122 | page: ~~page,
123 | maxPage: maxPage,
124 | list: list,
125 | };
126 | }
127 | catch (err) {
128 | throw err;
129 | }
130 | };
131 | exports.genre = genre;
132 | const anime = async (slug) => {
133 | try {
134 | const base = await axios_1.default.get(`${BASEURL}/anime/${slug}`, {
135 | maxRedirects: 0,
136 | });
137 | const $ = cheerio_1.default.load(base.data);
138 | if (!base.data) {
139 | throw new Error("Anime not found");
140 | }
141 | let genre = [];
142 | $(".infozingle p:last-child")
143 | .find("a")
144 | .each((i, el) => {
145 | genre.push($(el).text());
146 | });
147 | let episode = [];
148 | $(".episodelist ul li").each((i, el) => {
149 | episode.push($(el).find("span:first-child").text());
150 | });
151 | return {
152 | slug: $('meta[property="og:url"]').attr("content")?.split("/")[4],
153 | title: $(".infozingle p:first-child").text().replace("Judul:", "").trim(),
154 | titleAlt: $(".infozingle p:nth-child(2)")
155 | .text()
156 | .replace("Japanese:", "")
157 | .trim(),
158 | synopsis: "Synopsis not available",
159 | episodeTotal: ~~$(".infozingle p:nth-child(7)")
160 | .text()
161 | .replace(/Total Episode:|Unknown/g, ""),
162 | episode: episode.length,
163 | genre: genre,
164 | cover: $(".venser .fotoanime img").attr("src"),
165 | url: $('meta[property="og:url"]').attr("content"),
166 | };
167 | }
168 | catch (err) {
169 | throw err;
170 | }
171 | };
172 | exports.anime = anime;
173 | const animeVideoSource = async (slug, ep) => {
174 | try {
175 | const formattedEp = ("00" + ep).slice(-3);
176 | const base = await axios_1.default.get(`${BASEURL}/episode/${slug}-episode-${formattedEp}`);
177 | const $ = cheerio_1.default.load(base.data);
178 | if (!$("#change-server > option").html()) {
179 | throw new Error("Episode not found");
180 | }
181 | let videoSource = [];
182 | $("#change-server > option").each((i, el) => {
183 | videoSource.push({
184 | quality: $(el).text().split(" ")[0],
185 | url: $(el).attr("value"),
186 | });
187 | });
188 | return {
189 | episode: ~~ep,
190 | video: videoSource,
191 | };
192 | }
193 | catch (err) {
194 | throw err;
195 | }
196 | };
197 | exports.animeVideoSource = animeVideoSource;
198 |
--------------------------------------------------------------------------------
/dist/source/utils/types.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aniyoi-api",
3 | "version": "1.0.0",
4 | "description": "Anime Scrapper",
5 | "author": "miukyo",
6 | "main": "src/index.ts",
7 | "license": "MIT",
8 | "scripts": {
9 | "start": "node ./dist/index.js",
10 | "build": "rimraf dist && tsc",
11 | "dev": "nodemon ./src/index.ts"
12 | },
13 | "pre-commit": [
14 | "build"
15 | ],
16 | "devDependencies": {
17 | "@types/cheerio": "^0.22.31",
18 | "@types/cors": "^2.8.13",
19 | "@types/express": "^4.17.15",
20 | "@types/node": "^18.11.18",
21 | "nodemon": "^2.0.20",
22 | "pre-commit": "^1.2.2",
23 | "prettier": "^2.8.4",
24 | "rimraf": "^4.1.2",
25 | "ts-node": "^10.9.1"
26 | },
27 | "dependencies": {
28 | "axios": "^1.2.2",
29 | "cheerio": "^1.0.0-rc.12",
30 | "cors": "^2.8.5",
31 | "dotenv": "^16.0.3",
32 | "express": "^4.18.2",
33 | "typescript": "^4.9.4"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import cors from "cors";
3 | import kuramanime from "./source/kuramanime/kuramanime";
4 | import nanime from "./source/nanime/nanime";
5 | import kuronime from "./source/kuronime/kuronime";
6 | import otakudesu from "./source/otakudesu/otakudesu";
7 |
8 | import axios from "axios";
9 |
10 | export const app = express();
11 | axios.defaults.validateStatus = () => true;
12 | axios.defaults.headers.common["User-Agent"] =
13 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54";
14 |
15 | app.use(cors());
16 | app.get("/", async (req, res) => {
17 | res.send("ANIYOI API IS UP 🚀");
18 | });
19 | app.use("/kuramanime", kuramanime);
20 | app.use("/nanime", nanime);
21 | app.use("/kuronime", kuronime);
22 | // app.use("/otakudesu", otakudesu); //Url page streaming memakai slug yang berbeda
23 |
24 | app.listen(process.env.PORT || 3001, () => {
25 | console.warn("\nReady 🚀");
26 | });
27 |
--------------------------------------------------------------------------------
/src/source/kuramanime/kuramanime.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | anime,
4 | animeVideoSource,
5 | genre,
6 | genreList,
7 | popular,
8 | recentRelease,
9 | search,
10 | season,
11 | seasonList,
12 | } from "./parser";
13 |
14 | const app = express.Router();
15 |
16 | app.get("/", async (req: any, res: any) => {
17 | return res.status(200).send("Kuramanime Server is ready 🚀");
18 | });
19 |
20 | app.get("/recent", async (req: any, res: any) => {
21 | try {
22 | const { page } = req.query;
23 | const data = await recentRelease(page);
24 | return res.status(200).json(data);
25 | } catch (err: any) {
26 | return res.status(500).json({
27 | error: "Internal Error",
28 | message: err.toString(),
29 | });
30 | }
31 | });
32 |
33 | app.get("/search", async (req: any, res: any) => {
34 | try {
35 | const { page, query } = req.query;
36 | const data = await search(query, page);
37 | return res.status(200).json(data);
38 | } catch (err: any) {
39 | return res.status(500).json({
40 | error: "Internal Error",
41 | message: err.toString(),
42 | });
43 | }
44 | });
45 |
46 | app.get("/popular", async (req: any, res: any) => {
47 | try {
48 | const { page } = req.query;
49 | const data = await popular(page);
50 | return res.status(200).json(data);
51 | } catch (err: any) {
52 | return res.status(500).json({
53 | error: "Internal Error",
54 | message: err.toString(),
55 | });
56 | }
57 | });
58 |
59 | app.get("/genre", async (req: any, res: any) => {
60 | try {
61 | const { page } = req.query;
62 | const data = await genreList(page);
63 | return res.status(200).json(data);
64 | } catch (err: any) {
65 | return res.status(500).json({
66 | error: "Internal Error",
67 | message: err.toString(),
68 | });
69 | }
70 | });
71 |
72 | app.get("/genre/:genre", async (req: any, res: any) => {
73 | try {
74 | const genreType = req.params.genre;
75 | const { page } = req.query;
76 | const data = await genre(genreType, page);
77 | return res.status(200).json(data);
78 | } catch (err: any) {
79 | return res.status(500).json({
80 | error: "Internal Error",
81 | message: err.toString(),
82 | });
83 | }
84 | });
85 |
86 | app.get("/season", async (req: any, res: any) => {
87 | try {
88 | const { page } = req.query;
89 | const data = await seasonList(page);
90 | return res.status(200).json(data);
91 | } catch (err: any) {
92 | return res.status(500).json({
93 | error: "Internal Error",
94 | message: err.toString(),
95 | });
96 | }
97 | });
98 |
99 | app.get("/season/:season", async (req: any, res: any) => {
100 | try {
101 | const seasonYear = req.params.season;
102 | const { page } = req.query;
103 | const data = await season(seasonYear, page);
104 | return res.status(200).json(data);
105 | } catch (err: any) {
106 | return res.status(500).json({
107 | error: "Internal Error",
108 | message: err.toString(),
109 | });
110 | }
111 | });
112 |
113 | app.get("/anime/:animeSlug", async (req: any, res: any) => {
114 | try {
115 | const slug = req.params.animeSlug;
116 | const data = await anime(slug);
117 | return res.status(200).json(data);
118 | } catch (err: any) {
119 | return res.status(500).json({
120 | error: "Internal Error",
121 | message: err.toString(),
122 | });
123 | }
124 | });
125 |
126 | app.get("/anime/:animeId/:episodeId", async (req: any, res: any) => {
127 | try {
128 | const id = req.params.animeId;
129 | const ep = req.params.episodeId;
130 | const data = await animeVideoSource(id, ep);
131 | return res.status(200).json(data);
132 | } catch (err: any) {
133 | return res.status(500).json({
134 | error: "Internal Error",
135 | message: err.toString(),
136 | });
137 | }
138 | });
139 |
140 | export default app;
141 |
--------------------------------------------------------------------------------
/src/source/kuramanime/parser.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import cheerio from "cheerio";
3 | import type {
4 | AnimeDetails,
5 | Anime,
6 | Genre,
7 | AnimeVideo,
8 | ListAnime,
9 | } from "../utils/types";
10 |
11 | const BASEURL = "https://kuramanime.net";
12 |
13 | export const recentRelease = async (page: number = 1): Promise => {
14 | let list: Anime[] = [];
15 | try {
16 | const base = await axios.get(
17 | `${BASEURL}/anime/ongoing?order_by=latest&page=${page}`
18 | );
19 | const $ = cheerio.load(base.data);
20 | if (!$("#animeList .product__item").html()) {
21 | throw new Error("Page not found, you may request more than the maximum page");
22 | }
23 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
24 | .last()
25 | .html()!;
26 | $("#animeList .product__item").each((i, el) => {
27 | return list.push({
28 | slug: $(el).find("a").attr("href")?.split("/")[5]!,
29 | title: $(el).find(".product__item__text>h5>a").text()!,
30 | episode: ~~$(el)
31 | .find(".ep span")
32 | .text()
33 | .replace("Ep", "")
34 | .split("/")[0]
35 | .trim(),
36 | cover: $(el).attr("data-setbg")!,
37 | url: $(el).find("a").attr("href")!,
38 | });
39 | });
40 | return {
41 | page: ~~page,
42 | maxPage: maxPage,
43 | list: list,
44 | };
45 | } catch (err: any) {
46 | throw err;
47 | }
48 | };
49 |
50 | export const search = async (
51 | query: string,
52 | page: number = 1
53 | ): Promise => {
54 | let list: Anime[] = [];
55 | try {
56 | const base = await axios.get(
57 | `${BASEURL}/anime?search=${query}&order_by=latest&page=${page}`
58 | );
59 | const $ = cheerio.load(base.data);
60 | if (!$("#animeList .product__item").html()) {
61 | throw new Error("Anime not found,or you may request more than the maximum page");
62 | }
63 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
64 | .last()
65 | .html()!;
66 | $("#animeList .product__item").each((i, el) => {
67 | list.push({
68 | slug: $(el).find("a").attr("href")?.split("/")[5]!,
69 | title: $(el).find(".product__item__text>h5>a").text(),
70 | cover: $(el).attr("data-setbg")!,
71 | url: $(el).find("a").attr("href")!,
72 | });
73 | });
74 | if (list.length == 0) {
75 | throw new Error("Anime not found");
76 | }
77 | return {
78 | page: ~~page,
79 | maxPage: maxPage,
80 | list: list,
81 | };
82 | } catch (err: any) {
83 | throw err;
84 | }
85 | };
86 |
87 | export const popular = async (page: number = 1): Promise => {
88 | let list: Anime[] = [];
89 | try {
90 | const base = await axios.get(
91 | `${BASEURL}/anime/ongoing?order_by=popular&page=${page}`
92 | );
93 | const $ = cheerio.load(base.data);
94 | if (!$("#animeList .product__item").html()) {
95 | throw new Error("Page not found, you may request more than the maximum page");
96 | }
97 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
98 | .last()
99 | .html()!;
100 | $("#animeList .product__item").each((i, el) => {
101 | list.push({
102 | slug: $(el).find("a").attr("href")?.split("/")[5]!,
103 | title: $(el).find(".product__item__text>h5>a").text(),
104 | episode: ~~$(el)
105 | .find(".ep span")
106 | .text()
107 | .replace("Ep", "")
108 | .split("/")[0]
109 | .trim(),
110 | cover: $(el).attr("data-setbg")!,
111 | url: $(el).find("a").attr("href")!,
112 | });
113 | });
114 | return {
115 | page: ~~page,
116 | maxPage: maxPage,
117 | list: list,
118 | };
119 | } catch (err: any) {
120 | throw err;
121 | }
122 | };
123 |
124 | export const genreList = async (page: number = 1): Promise => {
125 | let list: Genre[] = [];
126 | try {
127 | const base = await axios.get(
128 | `${BASEURL}/properties/genre?genre_type=all&page=${page}`
129 | );
130 | const $ = cheerio.load(base.data);
131 | if (!$("#animeList .kuramanime__genres li").html()) {
132 | throw new Error("Page not found");
133 | }
134 | $("#animeList .kuramanime__genres li").each((i, el) => {
135 | list.push({
136 | slug: $(el).find("a").attr("href")?.split("/")[5]?.split("?")[0]!,
137 | title: $(el).find("a").text(),
138 | url: $(el).find("a").attr("href")?.trim()!,
139 | });
140 | });
141 | return list;
142 | } catch (err: any) {
143 | throw err;
144 | }
145 | };
146 |
147 | export const genre = async (
148 | genre: string,
149 | page: number = 1
150 | ): Promise => {
151 | let list: Anime[] = [];
152 | try {
153 | const base = await axios.get(
154 | `${BASEURL}/properties/genre/${genre}?order_by=latest&page=${page}`
155 | );
156 | const $ = cheerio.load(base.data);
157 | if (!$("#animeList .product__item").html()) {
158 | throw new Error("Genre not found, or you may request more than the maximum page");
159 | }
160 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
161 | .last()
162 | .html()!;
163 | $("#animeList .product__item").each((i, el) => {
164 | list.push({
165 | slug: $(el).find("a").attr("href")?.split("/")[5]!,
166 | title: $(el).find(".product__item__text>h5>a").text(),
167 | cover: $(el).attr("data-setbg")!,
168 | url: $(el).find("a").attr("href")!,
169 | });
170 | });
171 | return {
172 | page: ~~page,
173 | maxPage: maxPage,
174 | list: list,
175 | };
176 | } catch (err: any) {
177 | throw err;
178 | }
179 | };
180 |
181 | export const seasonList = async (page: number = 1): Promise => {
182 | let list: Genre[] = [];
183 | try {
184 | const base = await axios.get(`${BASEURL}/properties/season?page=${page}`);
185 | const $ = cheerio.load(base.data);
186 | if (!$("#animeList .kuramanime__genres li").html()) {
187 | throw new Error("Page not found");
188 | }
189 | $("#animeList .kuramanime__genres li").each((i, el) => {
190 | list.push({
191 | slug: $(el).find("a").attr("href")?.split("/")[5]!.split("?")[0]!,
192 | title: $(el).find("a").text(),
193 | url: $(el).find("a").attr("href")?.trim()!,
194 | });
195 | });
196 | return list;
197 | } catch (err: any) {
198 | throw err;
199 | }
200 | };
201 |
202 | export const season = async (
203 | season: string,
204 | page: number = 1
205 | ): Promise => {
206 | let list: Anime[] = [];
207 | try {
208 | const base = await axios.get(
209 | `${BASEURL}/properties/season/${season}?order_by=latest&page=${page}`
210 | );
211 | const $ = cheerio.load(base.data);
212 | if (!$("#animeList .product__item").html()) {
213 | throw new Error("Season not found, or you may request more than the maximum page");
214 | }
215 | let maxPage = ~~$("#animeList .product__pagination a:not(:has(i))")
216 | .last()
217 | .html()!;
218 | $("#animeList .product__item").each((i, el) => {
219 | list.push({
220 | slug: $(el).find("a").attr("href")?.split("/")[5]!,
221 | title: $(el).find(".product__item__text>h5>a").text(),
222 | cover: $(el).attr("data-setbg")!,
223 | url: $(el).find("a").attr("href")!,
224 | });
225 | });
226 | return {
227 | page: ~~page,
228 | maxPage: maxPage,
229 | list: list,
230 | };
231 | } catch (err: any) {
232 | throw err;
233 | }
234 | };
235 |
236 | export const anime = async (slug: string): Promise => {
237 | try {
238 | const base = await axios.get(`${BASEURL}/anime/${slug}`);
239 | const $ = cheerio.load(base.data);
240 | if (!$(".anime__details__widget ul").html()) {
241 | throw new Error("Anime not found");
242 | }
243 | let genre: string[] = [];
244 | $(".anime__details__widget ul")
245 | .find('span:contains("Genre:"),span:contains("Tema:")')
246 | .parent()
247 | .find("a")
248 | .each((i, el) => {
249 | genre.push($(el).text().replace(",", "").trim());
250 | });
251 | let episode = [];
252 | let $$ = cheerio.load(
253 | $("#episodeListsSection > #episodeLists").attr("data-content")!
254 | );
255 | $$("a").each((i, el) => {
256 | episode.push($(el).text().trim());
257 | });
258 | return {
259 | slug: $('meta[property="og:url"]').attr("content")?.split("/")[5]!,
260 | title: $(".anime__details__title > h3").text(),
261 | titleAlt: $(".anime__details__title > span").text(),
262 | synopsis: $("#synopsisField").text(),
263 | episodeTotal: ~~$(".anime__details__widget ul")
264 | .find('span:contains("Total Eps:")')
265 | .next()
266 | .text(),
267 | episode: episode.length,
268 | season: $(".anime__details__widget ul")
269 | .find('span:contains("Musim:")')
270 | .next()
271 | .text(),
272 | genre: genre,
273 | cover: $(".set-bg").attr("data-setbg")!,
274 | url: $('meta[property="og:url"]').attr("content")!,
275 | };
276 | } catch (err: any) {
277 | throw err;
278 | }
279 | };
280 |
281 | export const animeVideoSource = async (
282 | slug: string,
283 | ep: number
284 | ): Promise => {
285 | try {
286 | const url = await axios.head(`${BASEURL}/anime/${slug}`);
287 | const actualUrl = await url.request.res.responseUrl;
288 | const base = await axios.get(
289 | actualUrl + `/episode/${ep}?activate_stream=1`
290 | );
291 | const $ = cheerio.load(base.data);
292 | let videoSource: { quality: string; url: string }[] = [];
293 | if (!$("#animeVideoPlayer video").html()) {
294 | throw new Error("Episode not found");
295 | }
296 | $("#animeVideoPlayer video")
297 | .find("source")
298 | .each((i, el) => {
299 | videoSource.push({
300 | quality: $(el).attr("size")! + "p",
301 | url: $(el).attr("src")!,
302 | });
303 | });
304 | return {
305 | episode: ~~ep,
306 | video: videoSource,
307 | };
308 | } catch (err: any) {
309 | throw err;
310 | }
311 | };
312 |
--------------------------------------------------------------------------------
/src/source/kuronime/decryptor.js:
--------------------------------------------------------------------------------
1 | var Chara = [
2 | "",
3 | "split",
4 | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/",
5 | "slice",
6 | "indexOf",
7 | "",
8 | "",
9 | ".",
10 | "pow",
11 | "reduce",
12 | "reverse",
13 | "0",
14 | ];
15 | function charDec(d,e,f) {
16 | var g = Chara[2][Chara[1]](Chara[0]);
17 | var h = g[Chara[3]](0, e);
18 | var i = g[Chara[3]](0, f);
19 | var j = d[Chara[1]](Chara[0])
20 | [Chara[10]]()
21 | [Chara[9]](function (a, b, c) {
22 | if (h[Chara[4]](b) !== -1)
23 | return (a += h[Chara[4]](b) * Math[Chara[8]](e, c));
24 | }, 0);
25 | var k = Chara[0];
26 | while (j > 0) {
27 | k = i[j % f] + k;
28 | j = (j - (j % f)) / f;
29 | }
30 | return k || Chara[11];
31 | }
32 |
33 | export default function decy(h, u, n, t, e, r) {
34 | r = "";
35 | for (var i = 0, len = h.length; i < len; i++) {
36 | var s = "";
37 | while (h[i] !== n[e]) {
38 | s += h[i];
39 | i++;
40 | }
41 | for (var j = 0; j < n.length; j++) s = s.replace(new RegExp(n[j], "g"), j);
42 | r += String.fromCharCode(charDec(s, e, 10) - t);
43 | }
44 | return decodeURIComponent(escape(r));
45 | }
46 |
--------------------------------------------------------------------------------
/src/source/kuronime/kuronime.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | anime,
4 | animeVideoSource,
5 | genre,
6 | genreList,
7 | popular,
8 | recentRelease,
9 | search,
10 | season,
11 | seasonList,
12 | } from "./parser";
13 |
14 | const app = express.Router();
15 |
16 | app.get("/", async (req: any, res: any) => {
17 | return res.status(200).send("Kuronime Server is ready 🚀");
18 | });
19 |
20 | app.get("/recent", async (req: any, res: any) => {
21 | try {
22 | const { page } = req.query;
23 | const data = await recentRelease(page);
24 | return res.status(200).json(data);
25 | } catch (err: any) {
26 | return res.status(500).json({
27 | error: "Internal Error",
28 | message: err.toString(),
29 | });
30 | }
31 | });
32 |
33 | app.get("/search", async (req: any, res: any) => {
34 | try {
35 | const { page, query } = req.query;
36 | const data = await search(query, page);
37 | return res.status(200).json(data);
38 | } catch (err: any) {
39 | return res.status(500).json({
40 | error: "Internal Error",
41 | message: err.toString(),
42 | });
43 | }
44 | });
45 |
46 | app.get("/popular", async (req: any, res: any) => {
47 | try {
48 | const { page } = req.query;
49 | const data = await popular(page);
50 | return res.status(200).json(data);
51 | } catch (err: any) {
52 | return res.status(500).json({
53 | error: "Internal Error",
54 | message: err.toString(),
55 | });
56 | }
57 | });
58 |
59 | app.get("/genre", async (req: any, res: any) => {
60 | try {
61 | const { page } = req.query;
62 | const data = await genreList(page);
63 | return res.status(200).json(data);
64 | } catch (err: any) {
65 | return res.status(500).json({
66 | error: "Internal Error",
67 | message: err.toString(),
68 | });
69 | }
70 | });
71 |
72 | app.get("/genre/:genre", async (req: any, res: any) => {
73 | try {
74 | const genreType = req.params.genre;
75 | const { page } = req.query;
76 | const data = await genre(genreType, page);
77 | return res.status(200).json(data);
78 | } catch (err: any) {
79 | return res.status(500).json({
80 | error: "Internal Error",
81 | message: err.toString(),
82 | });
83 | }
84 | });
85 |
86 | app.get("/season", async (req: any, res: any) => {
87 | try {
88 | const { page } = req.query;
89 | const data = await seasonList(page);
90 | return res.status(200).json(data);
91 | } catch (err: any) {
92 | return res.status(500).json({
93 | error: "Internal Error",
94 | message: err.toString(),
95 | });
96 | }
97 | });
98 |
99 | app.get("/season/:season", async (req: any, res: any) => {
100 | try {
101 | const seasonYear = req.params.season;
102 | const { page } = req.query;
103 | const data = await season(seasonYear, page);
104 | return res.status(200).json(data);
105 | } catch (err: any) {
106 | return res.status(500).json({
107 | error: "Internal Error",
108 | message: err.toString(),
109 | });
110 | }
111 | });
112 |
113 | app.get("/anime/:animeSlug", async (req: any, res: any) => {
114 | try {
115 | const slug = req.params.animeSlug;
116 | const data = await anime(slug);
117 | return res.status(200).json(data);
118 | } catch (err: any) {
119 | return res.status(500).json({
120 | error: "Internal Error",
121 | message: err.toString(),
122 | });
123 | }
124 | });
125 |
126 | app.get("/anime/:animeId/:episodeId", async (req: any, res: any) => {
127 | try {
128 | const id = req.params.animeId;
129 | const ep = req.params.episodeId;
130 | const data = await animeVideoSource(id, ep);
131 | return res.status(200).json(data);
132 | } catch (err: any) {
133 | return res.status(500).json({
134 | error: "Internal Error",
135 | message: err.toString(),
136 | });
137 | }
138 | });
139 |
140 | export default app;
141 |
--------------------------------------------------------------------------------
/src/source/kuronime/parser.ts:
--------------------------------------------------------------------------------
1 | import axios, { Axios } from "axios";
2 | import cheerio from "cheerio";
3 | import type { AnimeDetails, Anime, Genre, AnimeVideo, ListAnime } from "../utils/types";
4 | import decryptor from "./decryptor.js";
5 |
6 | const BASEURL = "https://45.12.2.2";
7 |
8 | export const recentRelease = async (page: number = 1): Promise => {
9 | let list: Anime[] = [];
10 | try {
11 | const base = await axios.get(`${BASEURL}/page/${page}`);
12 | const $ = cheerio.load(base.data);
13 | if (!$(".postbody").html()) {
14 | throw new Error("Page not found");
15 | }
16 | let maxPage = ~~$(".postbody>.bixbox")
17 | .first()
18 | .find(".pagination a:not(.prev,.next)")
19 | .last()
20 | .text()
21 | .replace(",", "");
22 | $(".postbody>.bixbox")
23 | .first()
24 | .find(".excstf article")
25 | .each((i, el) => {
26 | return list.push({
27 | slug: $(el)
28 | .find("a")
29 | .attr("href")
30 | ?.split("/")[3]
31 | .replace(/\b(?:nonton-|-episode-[a-zA-Z0-9_]*)\b/gi, "")!,
32 | title: $(el).find(".bsuxtt").text()!,
33 | episode: ~~$(el).find(".bt .ep").text().replace("Episode", "").trim(),
34 | cover:
35 | $(el).find(".limit img[itemprop]").attr("data-src")?.split("?")[0]! + "?resize=141,200",
36 | url: $(el)
37 | .find("a")
38 | .attr("href")
39 | ?.replace(/\b(?:nonton-|-episode-[a-zA-Z0-9_]*)\b/gi, "")!,
40 | });
41 | });
42 | return {
43 | page: ~~page,
44 | maxPage: maxPage,
45 | list: list,
46 | };
47 | } catch (err: any) {
48 | throw err;
49 | }
50 | };
51 |
52 | export const search = async (query: string, page: number = 1): Promise => {
53 | let list: Anime[] = [];
54 | try {
55 | const base = await axios.get(`${BASEURL}/anime/page/${page}/?title=${query}&order=update`);
56 | const $ = cheerio.load(base.data);
57 | if (!$(".postbody").html()) {
58 | throw new Error("Page not found");
59 | }
60 | let maxPage = ~~$(".postbody>.bixbox")
61 | .first()
62 | .find(".pagination a:not(.prev,.next)")
63 | .last()
64 | .text()
65 | .replace(",", "");
66 | $(".postbody>.bixbox")
67 | .first()
68 | .find(".listupd article")
69 | .each((i, el) => {
70 | return list.push({
71 | slug: $(el).find("a").attr("href")?.split("/")[4]!,
72 | title: $(el).find("a").attr("title")!,
73 | cover:
74 | $(el).find(".limit img[itemprop]").attr("data-src")?.split("?")[0]! + "?resize=141,200",
75 | url: $(el).find("a").attr("href")!,
76 | });
77 | });
78 | if (list.length == 0) {
79 | throw new Error("Anime not found");
80 | }
81 | return {
82 | page: ~~page,
83 | maxPage: maxPage,
84 | list: list,
85 | };
86 | } catch (err: any) {
87 | throw err;
88 | }
89 | };
90 |
91 | export const popular = async (page: number = 1): Promise => {
92 | let list: Anime[] = [];
93 | try {
94 | const base = await axios.get(`${BASEURL}/popular-anime/page/${page}`);
95 | const $ = cheerio.load(base.data);
96 | if (!$(".postbody").html()) {
97 | throw new Error("Page not found");
98 | }
99 | let maxPage = ~~$(".postbody>.bixbox")
100 | .first()
101 | .find(".pagination a:not(.prev,.next)")
102 | .last()
103 | .text()
104 | .replace(",", "");
105 | $(".postbody>.bixbox")
106 | .first()
107 | .find(".listupd article")
108 | .each((i, el) => {
109 | return list.push({
110 | slug: $(el).find("a").attr("href")?.split("/")[4]!,
111 | title: $(el).find("a").attr("title")!,
112 | cover:
113 | $(el).find(".limit img[itemprop]").attr("data-src")?.split("?")[0]! + "?resize=141,200",
114 | url: $(el).find("a").attr("href")!,
115 | });
116 | });
117 | return {
118 | page: ~~page,
119 | maxPage: maxPage,
120 | list: list,
121 | };
122 | } catch (err: any) {
123 | throw err;
124 | }
125 | };
126 |
127 | export const genreList = async (page: number = 1): Promise => {
128 | let list: Genre[] = [];
129 | try {
130 | const base = await axios.get(`${BASEURL}/genres`);
131 | const $ = cheerio.load(base.data);
132 | if (!$(".postbody").html()) {
133 | throw new Error("Page not found");
134 | }
135 | $(".postbody>.bixbox")
136 | .first()
137 | .find(".genre li")
138 | .each((i, el) => {
139 | list.push({
140 | slug: $(el).find("a").attr("href")?.split("/")[4]!,
141 | title: $(el).find("a").text(),
142 | url: $(el).find("a").attr("href")!,
143 | });
144 | });
145 | return list;
146 | } catch (err: any) {
147 | throw err;
148 | }
149 | };
150 |
151 | export const genre = async (genre: string, page: number = 1): Promise => {
152 | let list: Anime[] = [];
153 | try {
154 | const base = await axios.get(`${BASEURL}/genres/${genre}/page/${page}`);
155 | const $ = cheerio.load(base.data);
156 | if (!$(".postbody").html()) {
157 | throw new Error("Page not found");
158 | }
159 | let maxPage = ~~$(".postbody>.bixbox")
160 | .first()
161 | .find(".pagination a:not(.prev,.next)")
162 | .last()
163 | .text()
164 | .replace(",", "");
165 | $(".postbody>.bixbox")
166 | .first()
167 | .find(".listupd article")
168 | .each((i, el) => {
169 | return list.push({
170 | slug: $(el).find("a").attr("href")?.split("/")[4]!,
171 | title: $(el).find("a").attr("title")!,
172 | cover:
173 | $(el).find(".limit img[itemprop]").attr("data-src")?.split("?")[0]! + "?resize=141,200",
174 | url: $(el).find("a").attr("href")!,
175 | });
176 | });
177 | return {
178 | page: ~~page,
179 | maxPage: maxPage,
180 | list: list,
181 | };
182 | } catch (err: any) {
183 | throw err;
184 | }
185 | };
186 |
187 | export const seasonList = async (page: number = 1): Promise => {
188 | let list: Genre[] = [];
189 | try {
190 | const base = await axios.get(`${BASEURL}/season/winter-2023`);
191 | const $ = cheerio.load(base.data);
192 | if (!$(".postbody").html()) {
193 | throw new Error("Page not found");
194 | }
195 | $(".postbody>.bixbox")
196 | .first()
197 | .find("#season option:not([selected])")
198 | .each((i, el) => {
199 | list.push({
200 | slug: $(el).attr("value")!,
201 | title: $(el).text(),
202 | url: `${BASEURL}/season/` + $(el).attr("value")!,
203 | });
204 | });
205 | return list;
206 | } catch (err: any) {
207 | throw err;
208 | }
209 | };
210 |
211 | export const season = async (season: string, page: number = 1): Promise => {
212 | let list: Anime[] = [];
213 | try {
214 | const base = await axios.get(`${BASEURL}/season/${season}`);
215 | const $ = cheerio.load(base.data);
216 | if (!$(".postbody").html()) {
217 | throw new Error("Page not found");
218 | }
219 | let maxPage = 0;
220 | $(".postbody>.bixbox")
221 | .first()
222 | .find(".listupd .sebox")
223 | .each((i, el) => {
224 | return list.push({
225 | slug: $(el).find(".tisebox a").attr("href")?.split("/")[4]!,
226 | title: $(el).find(".tisebox a").text()!,
227 | cover: $(el).find(".bigsebox img").attr("data-src")?.split("?")[0]! + "?resize=141,200",
228 | url: $(el).find(".tisebox a").attr("href")!,
229 | });
230 | });
231 | return {
232 | page: ~~page,
233 | maxPage: maxPage,
234 | list: list,
235 | };
236 | } catch (err: any) {
237 | throw err;
238 | }
239 | };
240 |
241 | export const anime = async (slug: string): Promise => {
242 | try {
243 | const base = await axios.get(`${BASEURL}/anime/${slug}`);
244 | const $ = cheerio.load(base.data);
245 | if (!$(".postbody").html()) {
246 | throw new Error("Anime not found");
247 | }
248 | let genre: string[] = [];
249 | $(".postbody article .entry-content")
250 | .find(".infodetail ul li:nth-child(2) a")
251 | .each((i, el) => {
252 | genre.push($(el).text());
253 | });
254 | let episode: string[] = [];
255 | $(".postbody article .entry-content")
256 | .find(".bixbox.bxcl ul li")
257 | .each((i, el) => {
258 | if (!$(el).find(".lchx a").text().match(/OVA/)) {
259 | episode.push($(el).find(".lchx a").text());
260 | }
261 | });
262 | return {
263 | slug: $('meta[property="og:url"]').attr("content")?.split("/")[4]!,
264 | title: $(".postbody .info-header h1.entry-title").text(),
265 | titleAlt: $(".postbody .infodetail ul li:first-child").text().replace("Judul:", "").trim(),
266 | synopsis: $(".postbody .main-info .r .conx p").text(),
267 | episodeTotal: ~~$(".postbody .infodetail ul li:nth-child(9)").text().split(":")[1].trim() | 0,
268 | episode: episode.length,
269 | season: $(".postbody .infodetail ul li:nth-child(6)").find("a").text(),
270 | genre: genre,
271 | cover: $(".postbody .main-info .l img").attr("data-src")?.split("?")[0]! + "?resize=141,200"!,
272 | url: $('meta[property="og:url"]').attr("content")!,
273 | };
274 | } catch (err: any) {
275 | throw err;
276 | }
277 | };
278 |
279 | export const animeVideoSource = async (slug: string, ep: number): Promise => {
280 | try {
281 | const base = await axios.get(`${BASEURL}/nonton-${slug}-episode-${ep}`);
282 | const $ = cheerio.load(base.data);
283 | if (!$(".postbody").html()) {
284 | throw new Error("Episode not found");
285 | }
286 | let embedUrl: string = $("#tonton.video-content .player-embed")
287 | .find("iframe")
288 | .attr("data-src")!;
289 | const videoSourceBase = await axios.get(embedUrl);
290 | const $$ = cheerio.load(videoSourceBase.data);
291 | if (!!$(".postbody .megavid .video-nav .iconx a").html()) {
292 | let enc = $$("script:not([src])")
293 | .first()
294 | .html()!
295 | .match(/\(r\)\)\}(.*)/)!;
296 | let penc = enc[1].replace(/[\(\)"']/g, "").split(",");
297 | let decrypt = decryptor(
298 | penc[0],
299 | parseInt(penc[1]),
300 | penc[2],
301 | parseInt(penc[3]),
302 | parseInt(penc[4]),
303 | parseInt(penc[5])
304 | );
305 | let getSrcs: { file: string; label: string; type: string }[] = JSON.parse(
306 | decrypt.match(/srcs\s*=\s*(\[.*\])/)![1]
307 | );
308 |
309 | let videoSource: { quality: string; url: string }[] = [];
310 |
311 | const waitSrc: Promise<{ quality: string; url: string }>[] = getSrcs.map(async (el, i) => {
312 | const url = await axios.get(el.file, { maxRedirects: 0 });
313 | let $$$ = cheerio.load(url.data);
314 | let surl = $$$("a").attr("href");
315 | return {
316 | quality: el.label === "HD" ? "720p" : el.label === "SD" ? "480p" : "Unknown",
317 | url: surl!,
318 | };
319 | });
320 | videoSource = await Promise.all(waitSrc);
321 | return {
322 | episode: ~~ep,
323 | video: videoSource,
324 | };
325 | } else {
326 | throw new Error("Video source are not supported.");
327 | }
328 | } catch (err: any) {
329 | throw err;
330 | }
331 | };
332 |
--------------------------------------------------------------------------------
/src/source/nanime/nanime.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | anime,
4 | animeVideoSource,
5 | genre,
6 | genreList,
7 | recentRelease,
8 | search,
9 | season,
10 | seasonList,
11 | } from "./parser";
12 |
13 | const app = express.Router();
14 |
15 | app.get("/", async (req: any, res: any) => {
16 | return res.status(200).send("Nanime Server is ready 🚀");
17 | });
18 |
19 | app.get("/recent", async (req: any, res: any) => {
20 | try {
21 | const { page } = req.query;
22 | const data = await recentRelease(page);
23 | return res.status(200).json(data);
24 | } catch (err: any) {
25 | return res.status(500).json({
26 | error: "Internal Error",
27 | message: err.toString(),
28 | });
29 | }
30 | });
31 |
32 | app.get("/search", async (req: any, res: any) => {
33 | try {
34 | const { page, query } = req.query;
35 | const data = await search(query, page);
36 | return res.status(200).json(data);
37 | } catch (err: any) {
38 | return res.status(500).json({
39 | error: "Internal Error",
40 | message: err.toString(),
41 | });
42 | }
43 | });
44 |
45 | app.get("/popular", async (req: any, res: any) => {
46 | return res.status(500).json({
47 | error: "Internal Error",
48 | message: "This request is not available for this server",
49 | });
50 | });
51 |
52 | app.get("/genre", async (req: any, res: any) => {
53 | try {
54 | const { page } = req.query;
55 | const data = await genreList(page);
56 | return res.status(200).json(data);
57 | } catch (err: any) {
58 | return res.status(500).json({
59 | error: "Internal Error",
60 | message: err.toString(),
61 | });
62 | }
63 | });
64 |
65 | app.get("/genre/:genre", async (req: any, res: any) => {
66 | try {
67 | const genreType = req.params.genre;
68 | const { page } = req.query;
69 | const data = await genre(genreType, page);
70 | return res.status(200).json(data);
71 | } catch (err: any) {
72 | return res.status(500).json({
73 | error: "Internal Error",
74 | message: err.toString(),
75 | });
76 | }
77 | });
78 |
79 | app.get("/season", async (req: any, res: any) => {
80 | try {
81 | const { page } = req.query;
82 | const data = await seasonList(page);
83 | return res.status(200).json(data);
84 | } catch (err: any) {
85 | return res.status(500).json({
86 | error: "Internal Error",
87 | message: err.toString(),
88 | });
89 | }
90 | });
91 |
92 | app.get("/season/:season", async (req: any, res: any) => {
93 | try {
94 | const seasonYear = req.params.season;
95 | const { page } = req.query;
96 | const data = await season(seasonYear, page);
97 | return res.status(200).json(data);
98 | } catch (err: any) {
99 | return res.status(500).json({
100 | error: "Internal Error",
101 | message: err.toString(),
102 | });
103 | }
104 | });
105 |
106 | app.get("/anime/:animeSlug", async (req: any, res: any) => {
107 | try {
108 | const slug = req.params.animeSlug;
109 | const data = await anime(slug);
110 | return res.status(200).json(data);
111 | } catch (err: any) {
112 | return res.status(500).json({
113 | error: "Internal Error",
114 | message: err.toString(),
115 | });
116 | }
117 | });
118 |
119 | app.get("/anime/:animeId/:episodeId", async (req: any, res: any) => {
120 | try {
121 | const id = req.params.animeId;
122 | const ep = req.params.episodeId;
123 | const data = await animeVideoSource(id, ep);
124 | return res.status(200).json(data);
125 | } catch (err: any) {
126 | return res.status(500).json({
127 | error: "Internal Error",
128 | message: err.toString(),
129 | });
130 | }
131 | });
132 |
133 | export default app;
134 |
--------------------------------------------------------------------------------
/src/source/nanime/parser.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import cheerio from "cheerio";
3 | import type { AnimeDetails, Anime, Genre, AnimeVideo, ListAnime } from "../utils/types";
4 |
5 | const BASEURL = "https://nanimex1.com";
6 |
7 | export const recentRelease = async (page: number = 1): Promise => {
8 | let list: Anime[] = [];
9 | try {
10 | const base = await axios.get(`${BASEURL}/page/${page}`);
11 | const $ = cheerio.load(base.data);
12 | let maxPage = ~~$(".box-poster .btn-group .page-numbers:not(.prev,.next)").last().html()!;
13 | if (!maxPage) {
14 | throw new Error("Page not found, you may request more than the maximum page");
15 | }
16 | $(".box-poster .content-item").each((i, el) => {
17 | return list.push({
18 | slug: $(el).find("a").attr("href")?.split("/")[4]!,
19 | title: $(el).find("h3").attr("title")!,
20 | episode: ~~$(el).find(".episode .btn-danger").text().replace("Episode", "").trim(),
21 | cover: $(el).find(".poster img").attr("data-lazy-src")!,
22 | url: $(el).find("a").attr("href")!,
23 | });
24 | });
25 |
26 | return {
27 | page: ~~page,
28 | maxPage: maxPage,
29 | list: list,
30 | };
31 | } catch (err: any) {
32 | throw err;
33 | }
34 | };
35 |
36 | export const search = async (query: string, page: number = 1): Promise => {
37 | let list: Anime[] = [];
38 | try {
39 | const base = await axios.get(`${BASEURL}/page/${page}/?s=${query}`);
40 | const $ = cheerio.load(base.data);
41 | let maxPage = ~~$(".box-poster .btn-group .page-numbers:not(.prev,.next)").last().html()!;
42 | if (!maxPage) {
43 | throw new Error("Anime not found, or you may request more than the maximum page");
44 | }
45 | $(".box-poster .content-item").each((i, el) => {
46 | if (!!$(el).find('.episode .btn-danger:contains("Episode")').html()) {
47 | return list.push({
48 | slug: $(el).find("a").attr("href")?.split("/")[4]!,
49 | title: $(el).find("h3").attr("title")!,
50 | episode: ~~$(el).find(".episode .btn-danger").text().replace("Episode", "").trim(),
51 | cover: $(el).find(".poster img").attr("src")!,
52 | url: $(el).find("a").attr("href")!,
53 | });
54 | }
55 | });
56 | if (list.length == 0) {
57 | throw new Error("Anime not found");
58 | }
59 | return {
60 | page: ~~page,
61 | maxPage: maxPage,
62 | list: list,
63 | };
64 | } catch (err: any) {
65 | throw err;
66 | }
67 | };
68 |
69 | export const genreList = async (page: number = 1): Promise => {
70 | let list: Genre[] = [];
71 | try {
72 | const base = await axios.get(`${BASEURL}`);
73 | const $ = cheerio.load(base.data);
74 | if (!$(".box-content:last-child .box-primary:nth-child(2) .box-body a").html()) {
75 | throw new Error("Page not found, you may request more than the maximum page");
76 | }
77 | $(".box-content:last-child .box-primary:nth-child(2) .box-body a").each((i, el) => {
78 | list.push({
79 | slug: $(el).attr("href")?.split("/")[4]!,
80 | title: $(el).text(),
81 | url: $(el).attr("href")!,
82 | });
83 | });
84 | return list;
85 | } catch (err: any) {
86 | throw err;
87 | }
88 | };
89 |
90 | export const genre = async (genre: string, page: number = 1): Promise => {
91 | let list: Anime[] = [];
92 | try {
93 | const base = await axios.get(`${BASEURL}/genre/${genre}/page/${page}`);
94 | const $ = cheerio.load(base.data);
95 | let maxPage = ~~$(".box-poster .btn-group .page-numbers:not(.prev,.next)").last().html()!;
96 | if (!maxPage) {
97 | throw new Error("Genre not found, you may request more than the maximum page");
98 | }
99 | $(".box-poster .content-item").each((i, el) => {
100 | if (!!$(el).find('.episode .btn-danger:contains("Episode")').html()) {
101 | return list.push({
102 | slug: $(el).find("a").attr("href")?.split("/")[4]!,
103 | title: $(el).find("h3").attr("title")!,
104 | episode: ~~$(el).find(".episode .btn-danger").text().replace("Episode", "").trim(),
105 | cover: $(el).find(".poster img").attr("data-lazy-src")!,
106 | url: $(el).find("a").attr("href")!,
107 | });
108 | }
109 | });
110 | if (list.length == 0) {
111 | throw new Error("Anime not found");
112 | }
113 | return {
114 | page: ~~page,
115 | maxPage: maxPage,
116 | list: list,
117 | };
118 | } catch (err: any) {
119 | throw err;
120 | }
121 | };
122 |
123 | export const seasonList = async (page: number = 1): Promise => {
124 | let list: Genre[] = [];
125 | try {
126 | const base = await axios.get(`${BASEURL}/properties/season?page=${page}`);
127 | const $ = cheerio.load(base.data);
128 | if (!$(".box-content:last-child .box-primary:nth-child(2) .box-body a").html()) {
129 | throw new Error("Page not found, or you may request more than the maximum page");
130 | }
131 | $(".box-content:last-child .box-primary:nth-child(3) .box-body a").each((i, el) => {
132 | list.push({
133 | slug: $(el).attr("href")?.split("/")[4]!,
134 | title: $(el).text(),
135 | url: $(el).attr("href")!,
136 | });
137 | });
138 | return list;
139 | } catch (err: any) {
140 | throw err;
141 | }
142 | };
143 |
144 | export const season = async (season: string, page: number = 1): Promise => {
145 | let list: Anime[] = [];
146 | try {
147 | const base = await axios.get(`${BASEURL}/year_/${season}/page/${page}`);
148 | const $ = cheerio.load(base.data);
149 | let maxPage = ~~$(".box-poster .btn-group .page-numbers:not(.prev,.next)").last().html()!;
150 | if (!maxPage) {
151 | throw new Error("Season not found, you may request more than the maximum page");
152 | }
153 | $(".box-poster .content-item").each((i, el) => {
154 | if (!!$(el).find('.episode .btn-danger:contains("Episode")').html()) {
155 | return list.push({
156 | slug: $(el).find("a").attr("href")?.split("/")[4]!,
157 | title: $(el).find("h3").attr("title")!,
158 | episode: ~~$(el).find(".episode .btn-danger").text().replace("Episode", "").trim(),
159 | cover: $(el).find(".poster img").attr("data-lazy-src")!,
160 | url: $(el).find("a").attr("href")!,
161 | });
162 | }
163 | });
164 | if (list.length == 0) {
165 | throw new Error("Anime not found");
166 | }
167 | return {
168 | page: ~~page,
169 | maxPage: maxPage,
170 | list: list,
171 | };
172 | } catch (err: any) {
173 | throw err;
174 | }
175 | };
176 |
177 | export const anime = async (slug: string): Promise => {
178 | try {
179 | const base = await axios.get(`${BASEURL}/anime/${slug}`);
180 | const $ = cheerio.load(base.data);
181 | if (!$(".box-default .box-body > div:nth-child(3) .episode_list").html()) {
182 | throw new Error("Anime not found");
183 | }
184 | let genre: string[] = [];
185 | $(".box-default .box-primary table")
186 | .find('td:contains("Genre")')
187 | .parent()
188 | .find("a")
189 | .each((i, el) => {
190 | genre.push($(el).text());
191 | });
192 | let episode: string[] = [];
193 | $(".box-default .box-body > div:nth-child(3) .episode_list table")
194 | .find("tr")
195 | .each((i, el) => {
196 | episode.push($(el).text());
197 | });
198 | return {
199 | slug: $('meta[property="og:url"]').attr("content")?.split("/")[4]!,
200 | title: $(
201 | ".box-content .box-body > div:nth-child(2) table tr:nth-child(1) > td:nth-child(2) > a:nth-child(1)"
202 | ).text(),
203 | titleAlt: $(
204 | ".box-content .box-body > div:nth-child(2) table tr:nth-child(2) > td:nth-child(2)"
205 | ).text(),
206 | synopsis: $(".box-content .box-body .attachment-block.clearfix .attachment-text").text(),
207 | episodeTotal: ~~$(".box-default>.box-body>.box-primary table")
208 | .find('td:contains("Total Episode")')
209 | .parent()
210 | .find("td:last-child")
211 | .text(),
212 | episode: episode.length,
213 | season: $(
214 | ".box-content .box-body > div:nth-child(2) table tr:nth-child(1) > td:nth-child(2) > a:nth-child(2)"
215 | ).text(),
216 | genre: genre,
217 | cover: $(".box-content .box-body .attachment-block.clearfix .attachment-img").attr(
218 | "data-lazy-src"
219 | )!,
220 | url: $('meta[property="og:url"]').attr("content")!,
221 | };
222 | } catch (err: any) {
223 | throw err;
224 | }
225 | };
226 |
227 | export const animeVideoSource = async (slug: string, ep: number): Promise => {
228 | try {
229 | const formattedEp = ("00" + ep).slice(-3);
230 | const base = await axios.get(`${BASEURL}/episode/${slug}-episode-${formattedEp}`);
231 | const $ = cheerio.load(base.data);
232 | if (!$("#change-server > option").html()) {
233 | throw new Error("Episode not found");
234 | }
235 | let videoSource: { quality: string; url: string }[] = [];
236 | $("#change-server > option").each((i, el) => {
237 | videoSource.push({
238 | quality: $(el).text().split(" ")[0]!,
239 | url: $(el).attr("value")!,
240 | });
241 | });
242 |
243 | return {
244 | episode: ~~ep,
245 | video: videoSource,
246 | };
247 | } catch (err: any) {
248 | throw err;
249 | }
250 | };
251 |
--------------------------------------------------------------------------------
/src/source/otakudesu/otakudesu.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | anime,
4 | animeVideoSource,
5 | genre,
6 | genreList,
7 | recentRelease,
8 | search,
9 | } from "./parser";
10 |
11 | const app = express.Router();
12 |
13 | app.get("/", async (req: any, res: any) => {
14 | return res.status(200).send("Otakudesu Server is ready 🚀");
15 | });
16 |
17 | app.get("/recent", async (req: any, res: any) => {
18 | try {
19 | const { page } = req.query;
20 | const data = await recentRelease(page);
21 | return res.status(200).json(data);
22 | } catch (err: any) {
23 | return res.status(500).json({
24 | error: "Internal Error",
25 | message: err.toString(),
26 | });
27 | }
28 | });
29 |
30 | app.get("/search", async (req: any, res: any) => {
31 | try {
32 | const { page, query } = req.query;
33 | const data = await search(query, page);
34 | return res.status(200).json(data);
35 | } catch (err: any) {
36 | return res.status(500).json({
37 | error: "Internal Error",
38 | message: err.toString(),
39 | });
40 | }
41 | });
42 |
43 | app.get("/popular", async (req: any, res: any) => {
44 | return res.status(500).json({
45 | error: "Internal Error",
46 | message: "This request is not available for this server",
47 | });
48 | });
49 |
50 | app.get("/genre", async (req: any, res: any) => {
51 | try {
52 | const { page } = req.query;
53 | const data = await genreList(page);
54 | return res.status(200).json(data);
55 | } catch (err: any) {
56 | return res.status(500).json({
57 | error: "Internal Error",
58 | message: err.toString(),
59 | });
60 | }
61 | });
62 |
63 | app.get("/genre/:genre", async (req: any, res: any) => {
64 | try {
65 | const genreType = req.params.genre;
66 | const { page } = req.query;
67 | const data = await genre(genreType, page);
68 | return res.status(200).json(data);
69 | } catch (err: any) {
70 | return res.status(500).json({
71 | error: "Internal Error",
72 | message: err.toString(),
73 | });
74 | }
75 | });
76 |
77 | app.get("/season", async (req: any, res: any) => {
78 | return res.status(500).json({
79 | error: "Internal Error",
80 | message: "This request is not available for this server",
81 | });
82 | });
83 |
84 | app.get("/season/:season", async (req: any, res: any) => {
85 | return res.status(500).json({
86 | error: "Internal Error",
87 | message: "This request is not available for this server",
88 | });
89 | });
90 |
91 | app.get("/anime/:animeSlug", async (req: any, res: any) => {
92 | try {
93 | const slug = req.params.animeSlug;
94 | const data = await anime(slug);
95 | return res.status(200).json(data);
96 | } catch (err: any) {
97 | return res.status(500).json({
98 | error: "Internal Error",
99 | message: err.toString(),
100 | });
101 | }
102 | });
103 |
104 | app.get("/anime/:animeId/:episodeId", async (req: any, res: any) => {
105 | try {
106 | const id = req.params.animeId;
107 | const ep = req.params.episodeId;
108 | const data = await animeVideoSource(id, ep);
109 | return res.status(200).json(data);
110 | } catch (err: any) {
111 | return res.status(500).json({
112 | error: "Internal Error",
113 | message: err.toString(),
114 | });
115 | }
116 | });
117 |
118 | export default app;
119 |
--------------------------------------------------------------------------------
/src/source/otakudesu/parser.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import cheerio from "cheerio";
3 | import type {
4 | AnimeDetails,
5 | Anime,
6 | Genre,
7 | AnimeVideo,
8 | ListAnime,
9 | } from "../utils/types";
10 |
11 | const BASEURL = "https://otakudesu.ltd";
12 |
13 | export const recentRelease = async (page: number = 1): Promise => {
14 | let list: Anime[] = [];
15 | try {
16 | const base = await axios.get(`${BASEURL}/ongoing-anime/page/${page}`);
17 | const $ = cheerio.load(base.data);
18 | let maxPage = ~~$(".venutama .pagination .page-numbers:not(.prev,.next)")
19 | .last()
20 | .html()!;
21 | if (!maxPage) {
22 | throw new Error(
23 | "Page not found, you may request more than the maximum page"
24 | );
25 | }
26 | $(".venutama .venz ul>li").each((i, el) => {
27 | return list.push({
28 | slug: $(el).find(".thumb a").attr("href")?.split("/")[4]!,
29 | title: $(el).find(".thumb h2").text()!,
30 | episode: ~~$(el).find(".epz").text().replace("Episode", "").trim(),
31 | cover: $(el).find(".thumb img").attr("src")!,
32 | url: $(el).find(".thumb a").attr("href")!,
33 | });
34 | });
35 |
36 | return {
37 | page: ~~page,
38 | maxPage: maxPage,
39 | list: list,
40 | };
41 | } catch (err: any) {
42 | throw err;
43 | }
44 | };
45 |
46 | export const search = async (
47 | query: string,
48 | page: number = 1
49 | ): Promise => {
50 | let list: Anime[] = [];
51 | try {
52 | const base = await axios.get(`${BASEURL}/?s=${query}&post_type=anime`);
53 | const $ = cheerio.load(base.data);
54 | let maxPage = 1;
55 | if ($(".venutama .chivsrc li").length < 1) {
56 | throw new Error(
57 | "Anime not found, or you may request more than the maximum page"
58 | );
59 | }
60 | $(".venutama .chivsrc li").each((i, el) => {
61 | return list.push({
62 | slug: $(el).find("h2>a").attr("href")?.split("/")[4]!,
63 | title: $(el)
64 | .find("h2>a")
65 | .text()!
66 | .replace(
67 | /\s*\(Episode[^\)]*\)|\s*Subtitle Indonesia*|\s*Sub Indo*/g,
68 | ""
69 | ),
70 | cover: $(el).find("img").attr("src")!,
71 | url: $(el).find("h2>a").attr("href")!,
72 | });
73 | });
74 | if (list.length == 0) {
75 | throw new Error("Anime not found");
76 | }
77 | return {
78 | page: ~~page,
79 | maxPage: maxPage,
80 | list: list,
81 | };
82 | } catch (err: any) {
83 | throw err;
84 | }
85 | };
86 |
87 | export const genreList = async (page: number = 1): Promise => {
88 | let list: Genre[] = [];
89 | try {
90 | const base = await axios.get(`${BASEURL}/genre-list`);
91 | const $ = cheerio.load(base.data);
92 | $(".genres li a").each((i, el) => {
93 | list.push({
94 | slug: $(el).attr("href")?.split("/")[2]!,
95 | title: $(el).text(),
96 | url: $(el).attr("href")!,
97 | });
98 | });
99 | return list;
100 | } catch (err: any) {
101 | throw err;
102 | }
103 | };
104 |
105 | export const genre = async (
106 | genre: string,
107 | page: number = 1
108 | ): Promise => {
109 | let list: Anime[] = [];
110 | try {
111 | const base = await axios.get(`${BASEURL}/genres/${genre}/page/${page}`);
112 | const $ = cheerio.load(base.data);
113 | let maxPage = ~~$(".venser .pagination .page-numbers:not(.prev,.next)")
114 | .last()
115 | .html()!;
116 | if (!maxPage) {
117 | throw new Error(
118 | "Genre not found, you may request more than the maximum page"
119 | );
120 | }
121 | $(".venser .col-anime").each((i, el) => {
122 | return list.push({
123 | slug: $(el).find(".col-anime-title a").attr("href")?.split("/")[4]!,
124 | title: $(el).find(".col-anime-title a").text()!,
125 | episode: ~~$(el)
126 | .find(".col-anime-eps")
127 | .text()
128 | .replace(/Eps|Unknown/g, "")
129 | .trim(),
130 | cover: $(el).find(".col-anime-cover img").attr("src")!,
131 | url: $(el).find(".col-anime-title a").attr("href")!,
132 | });
133 | });
134 | if (list.length == 0) {
135 | throw new Error("Anime not found");
136 | }
137 | return {
138 | page: ~~page,
139 | maxPage: maxPage,
140 | list: list,
141 | };
142 | } catch (err: any) {
143 | throw err;
144 | }
145 | };
146 |
147 | export const anime = async (slug: string): Promise => {
148 | try {
149 | const base = await axios.get(`${BASEURL}/anime/${slug}`, {
150 | maxRedirects: 0,
151 | });
152 | const $ = cheerio.load(base.data);
153 | if (!base.data) {
154 | throw new Error("Anime not found");
155 | }
156 | let genre: string[] = [];
157 | $(".infozingle p:last-child")
158 | .find("a")
159 | .each((i, el) => {
160 | genre.push($(el).text());
161 | });
162 | let episode: string[] = [];
163 | $(".episodelist ul li").each((i, el) => {
164 | episode.push($(el).find("span:first-child").text());
165 | });
166 | return {
167 | slug: $('meta[property="og:url"]').attr("content")?.split("/")[4]!,
168 | title: $(".infozingle p:first-child").text().replace("Judul:", "").trim(),
169 | titleAlt: $(".infozingle p:nth-child(2)")
170 | .text()
171 | .replace("Japanese:", "")
172 | .trim(),
173 | synopsis: "Synopsis not available",
174 | episodeTotal: ~~$(".infozingle p:nth-child(7)")
175 | .text()
176 | .replace(/Total Episode:|Unknown/g, ""),
177 | episode: episode.length,
178 | genre: genre,
179 | cover: $(".venser .fotoanime img").attr("src")!,
180 | url: $('meta[property="og:url"]').attr("content")!,
181 | };
182 | } catch (err: any) {
183 | throw err;
184 | }
185 | };
186 |
187 | export const animeVideoSource = async (
188 | slug: string,
189 | ep: number
190 | ): Promise => {
191 | try {
192 | const formattedEp = ("00" + ep).slice(-3);
193 | const base = await axios.get(
194 | `${BASEURL}/episode/${slug}-episode-${formattedEp}`
195 | );
196 | const $ = cheerio.load(base.data);
197 | if (!$("#change-server > option").html()) {
198 | throw new Error("Episode not found");
199 | }
200 | let videoSource: { quality: string; url: string }[] = [];
201 | $("#change-server > option").each((i, el) => {
202 | videoSource.push({
203 | quality: $(el).text().split(" ")[0]!,
204 | url: $(el).attr("value")!,
205 | });
206 | });
207 |
208 | return {
209 | episode: ~~ep,
210 | video: videoSource,
211 | };
212 | } catch (err: any) {
213 | throw err;
214 | }
215 | };
216 |
--------------------------------------------------------------------------------
/src/source/utils/types.ts:
--------------------------------------------------------------------------------
1 | export type ListAnime = {
2 | list: Anime[];
3 | maxPage: number;
4 | page: number;
5 | };
6 | export type Anime = {
7 | slug: string;
8 | title: string;
9 | episode?: number;
10 | cover: string;
11 | url: string;
12 | };
13 |
14 | export type AnimeDetails = {
15 | slug: string;
16 | title: string;
17 | titleAlt?: string;
18 | synopsis?: string;
19 | episodeTotal: number;
20 | episode: number;
21 | season?: string;
22 | genre?: string[];
23 | cover: string;
24 | url: string;
25 | };
26 |
27 | export type AnimeVideo = {
28 | episode: number;
29 | video: VideoSource[];
30 | };
31 |
32 | export type VideoSource = {
33 | quality: string;
34 | url: string;
35 | };
36 |
37 | export type Genre = {
38 | slug: string;
39 | title: string;
40 | url: string;
41 | };
42 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "CommonJS",
5 | "moduleResolution": "node",
6 | "baseUrl": "./",
7 | "allowJs": true,
8 | "outDir": "./dist",
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "strict": true,
12 | "skipLibCheck": true
13 | }
14 | }
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | {
5 | "src": "dist/index.js",
6 | "use": "@vercel/node",
7 | "config": { "includeFiles": ["dist/**"] }
8 | }
9 | ],
10 | "routes": [
11 | {
12 | "src": "/(.*)",
13 | "dest": "dist/index.js"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------