commit a6184c63d46b15bf9aaadb9eb31035e2dead4912 Author: Tejas Panchal Date: Sun Mar 1 12:39:28 2026 +0530 feature, not a bug diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ae5f1ed --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Origins you want to allow, use * to allow all origins (not recommended for production) + +ALLOWED_ORIGINS=,,... + +# Port the server will run on [OPTIONAL - default: 4444] +PORT=4444 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ef96e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.vscode +node_modules +.env +dist +package-lock.json +.idea +yarn.lock +pnpm-lock.yaml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a55a3f5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM node:20-alpine + +WORKDIR /app + +# Install system dependencies +RUN apk add --no-cache git netcat-openbsd + +# Copy package files +COPY package.json ./ + +RUN npm install + +# Copy the rest +COPY . . + +# Start app +CMD ["npm", "start"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eec5b20 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 JustAnime + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6859b1d --- /dev/null +++ b/README.md @@ -0,0 +1,1517 @@ +
+ +![Static Badge](https://img.shields.io/badge/node.js-grey?logo=nodedotjs) ![GitHub stars](https://img.shields.io/github/stars/itzzzme/anime-api?logo=github) +![GitHub forks](https://img.shields.io/github/forks/itzzzme/anime-api?logo=github) +![Static Badge](https://img.shields.io/badge/version-1.0.0-blue) + +
+ +## Disclaimer + +1. This `api` does not store any files , it only link to the media which is hosted on 3rd party services. + +2. This `api` is explicitly made for educational purposes only and not for commercial usage. This repo will not be responsible for any misuse of it. + +

+ +

+ +#

HiAnime API

+ +> + +

RestFul API made with Node.js
(Checkout this anime streaming website JustAnime powered by this API)

+ +>

Table of Contents

+ +- [Installation](#installation) + - [Local installation](#local-installation) +- [Deployment](#deployment) + - [Vercel](#Vercel) + - [Render](#Render) +- [Documentation](#documentation) + - [GET Home Info](#get-home-info) + - [GET Top 10 Anime's Info](#get-top-10-animes-info) + - [GET Top Search](#get-top-search) + - [GET Specified Anime's Info](#get-specified-animes-info) + - [GET Random Anime's Info](#get-random-animes-info) + - [GET Categories Info](#get-categories-info) + - [GET Producer's & studio's Anime](#get-anime-of-specific-producers-or-studio) + - [GET Search Result's Info](#get-search-results-info) + - [GET Search Suggestions](#get-search-suggestions) + - [GET Anime Schedule](#get-schedule-of-upcoming-anime) + - [GET Anime's Next Epiosde's Schedule](#get-schedule-of-next-episode-of-anime) + - [GET Anime Qtip Info](#get-qtip-info) + - [GET Anime Characters](#get-characters) + - [GET Character Details](#get-character-details) + - [GET Voice Actor Details](#get-voice-actor-details) + - [GET Anime Stream Info](#get-streaming-info) + - [GET Anime Backup Stream Info](#get-fallback-streaming-info) + - [GET Anime Episodes](#get-animes-episode-list) + - [GET Anime Episode's Available Servers](#get-available-servers-of-anime) +- [Pull Requests](#pull-requests) +- [Reporting Issues](#reporting-issues) +- [Support](#support) + +> # Installation + +## Local installation + +Make sure you have node installed on your device + +1. Run the following code to clone the repository and install all required dependencies + +```bash +$ git clone https://github.com/JustAnimeCore/HiAnime-Api.git +$ cd anime-api +$ npm install +``` + +2. Refer the [.env.example](https://github.com/JustAnimeCore/HiAnime-Api/blob/main/.env.example) file to setup `.env` file + +```bash +# Origins you want to allow + +ALLOWED_ORIGIN=,,... +``` + +3. Start the server + +```bash +$ npm start #or npm run devStart +``` + +> # Deployment + +### Vercel + +Host your own instance of anime-api on vercel + +[![Deploy to Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/JustAnimeCore/HiAnime-Api) + +### Render + +Host your own instance of anime-api on Render. + +[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/JustAnimeCore/HiAnime-Api) + +> # Documentation + +### `GET` Home info + +```bash + GET /api/ +``` + +### Endpoint + +```bash + /api/ +``` + +> #### No parameter required ❌ + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "spotlights": [ + { + "id":string, + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "description": string, + "tvInfo": { + "showType": string, + "duration": string, + "releaseDate": string, + "quality": string, + "episodeInfo": [object] + } + }, + {...} + ], + "trending": [ + { + "id":string, + "data_id": number, + "number": number, + "poster": string, + "title": string, + "japanese_title": string, + } + {...} + ], + "today":[ + "schedule":[ + { + "id":string, + "data_id":number, + "title":string, + "japanese_title":string, + "releaseDate":string, + "time":string, + "episode_no":number, + },{...} + ] + ], + "topAiring":[ + { + "id":string, + "data_id":number, + "poster":string, + "title":string, + "japanese_title":string, + "description":string, + tvInfo:[object] + } + ], + "mostPopular":[ + { + "id":string, + "data_id":number, + "poster":string, + "title":string, + "japanese_title":string, + "description":string, + tvInfo:[object] + }, + "mostFavorite":[ + { + "id":string, + "data_id":number, + "poster":string, + "title":string, + "japanese_title":string, + "description":string, + tvInfo:[object] + } + ], + "latestCompleted":[ + { + "id":string, + "data_id":number, + "poster":string, + "title":string, + "japanese_title":string, + "description":string, + tvInfo:[object] + } + ], + "latestEpisode":[ + { + "id":string, + "data_id":number, + "poster":string, + "title":string, + "japanese_title":string, + "description":string, + tvInfo:[object] + } + ], + "genres":[ + string, + string, + string, + ... + ] + } +} +``` + +### `GET` Top 10 anime's info + +```bash + GET /api/top-ten +``` + +### Endpoint + +```bash + /api/top-ten +``` + +> #### No parameter required ❌ + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/top-ten"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": [ + "topTen":[ + "today":[ + { + "id":string, + "data_id": number, + "number": number, + "name": string, + "poster": string, + "tvInfo": [Object] + }, + {...} + ], + "week":[ + { + "id":string, + "data_id": number, + "number": number, + "name": string, + "poster": string, + "tvInfo": [Object] + }, + {...} + ], + "month":[ + { + "id":string, + "data_id": number, + "number": number, + "name": string, + "poster": string, + "tvInfo": [Object] + }, + {...} + ], + ] + ] +} +``` + +### `GET` Top Search + +```bash + GET /api/top-search +``` + +> #### No parameter required ❌ + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/top-search"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": [ + { + "title": string, + "link": string + }, + {...} + ] +} +``` + +### `GET` Specified anime's info + +```bash + GET /api/info +``` + +### Endpoint + +```bash + /api/info?id={string} +``` + +#### Parameters + +| Parameter | Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :-------: | :------------: | :-------: | :---------: | :---------: | :-----: | +| `id` | `query` | string | anime-id | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/info?id=yami-shibai-9-17879"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "data": { + "adultContent":boolean, + "id":string, + "data_id": number, + "title": string, + "japanese_title": string, + "poster": string, + "showType":string, + "animeInfo": { + "Overview": string, + "Japanese": string, + "Synonyms": string, + "Aired": string, + "Premiered": string, + "Duration": string, + "Status": string, + "MAL Score": string, + "Genres": [Object], + "Studios": string, + "Producers": [Object] + } + }, + "seasons": [ + { + "id":string, + "data_number": number, + "data_id": number, + "season": string, + "title": string, + "japanese_title": string, + "season_poster": string + }, + {...} + ], + + } +} +``` + +### `GET` Random anime's info + +```bash + GET /api/random +``` + +### Endpoint + +```bash + /api/random +``` + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/random"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "data": { + "adultContent":boolean, + "id":string, + "data_id": number, + "title": string, + "japanese_title": string, + "poster": string, + "showType":string, + "animeInfo": { + "Overview": string, + "Japanese": string, + "Synonyms": string, + "Aired": string, + "Premiered": string, + "Duration": string, + "Status": string, + "MAL Score": string, + "Genres": [Object], + "Studios": string, + "Producers": [Object] + } + }, + "related_data":[ + [ + { + "duration": "string", + "data_id": "number", + "id": "string", + "title": "string", + "japanese_title": "string", + "poster": "string", + "tvInfo": { + "dub": "number", + "sub": "number", + "showType": "string", + "eps": "number" + } + },{...} + ] + ], + "recommended_data":[ + [ + { + "duration": "string", + "data_id": "number", + "id": "string", + "title": "string", + "japanese_title": "string", + "poster": "string", + "tvInfo": { + "dub": "number", + "sub": "number", + "showType": "string", + "eps": "number" + } + },{...} + ] + ], + "seasons": [ + { + "id":string, + "data_number": number, + "data_id": number, + "season": string, + "title": string, + "japanese_title": string, + "season_poster": string + }, + {...} + ], + + } +} +``` + +### `GET` Categories info + +```bash + GET /api/ +``` + +### Endpoint + +```bash + /api/{string}?page={number} + #or + /api/{string} +``` + +#### Parameters + +| Parameter | Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :--------: | :------------: | :-------: | :---------: | :---------: | :-----: | +| `category` | `path` | `string` | `Category` | Yes ✔️ | -- | +| `page` | `query` | `number` | `Page-no.` | No ❌ | `1` | + +#### List of Categories + +- top-airing +- most-popular +- most-favorite +- completed +- recently-updated +- recently-added +- top-upcoming +- subbed-anime +- dubbed-anime +- top-upcoming +- genre/action +- genre/adventure +- genre/cars +- genre/comedy +- genre/dementia +- genre/demons +- genre/drama +- genre/ecchi +- genre/fantasy +- genre/game +- genre/harem +- genre/historical +- genre/horror +- genre/isekai +- genre/josei +- genre/kids +- genre/magic +- genre/martial-arts +- genre/mecha +- genre/military +- genre/music +- genre/mystery +- genre/parody +- genre/police +- genre/psychological +- genre/romance +- genre/samurai +- genre/school +- genre/sci-fi +- genre/seinen +- genre/shoujo +- genre/shoujo-ai +- genre/shounen +- genre/shounen-ai +- genre/slice-of-life +- genre/space +- genre/sports +- genre/super-power +- genre/supernatural +- genre/thriller +- genre/vampire +- az-list +- az-list/other +- az-list/0-9 +- az-list/a +- az-list/b +- az-list/c +- az-list/d +- az-list/e +- az-list/f +- az-list/g +- az-list/h +- az-list/i +- az-list/j +- az-list/k +- az-list/l +- az-list/m +- az-list/n +- az-list/o +- az-list/p +- az-list/q +- az-list/r +- az-list/s +- az-list/t +- az-list/u +- az-list/v +- az-list/w +- az-list/x +- az-list/y +- az-list/z +- movie +- special +- ova +- ona +- tv +- music + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/most-popular?page=1"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "totalPages": number, + "data": [ + { + "id":string, + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "description": string, + "tvInfo": { + "showType": string, + "duration": string, + "sub": number, + "dub": number + }, + "adultContent":boolean, + }, + { + "id":string, + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "description": string, + "tvInfo": { + "showType": sting, + "duration": string, + "sub": number, + "dub": number, + "eps": number + }, + "adultContent":boolean, + }, + {...} + ], + "totalPages":number + } +} +``` + +### `GET` Anime of specific producers or studio + +```bash + GET /api// +``` + +### Endpoint + +```bash + /api/producer/{string}?page={number} + #or + /api/producer/{string} +``` + +#### Parameters + +| Parameter | Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :--------: | :------------: | :-------: | :---------: | :---------: | :-----: | +| `producer` | `path` | `string` | `Producer` | Yes ✔️ | -- | +| `page` | `query` | `number` | `Page-no.` | No ❌ | `1` | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/producer/ufotable?page=1"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "totalPages": number, + "data": [ + { + "id":string, + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "description": string, + "tvInfo": { + "showType": string, + "duration": string, + "sub": number, + "dub": number + }, + "adultContent":boolean, + }, + { + "id":string, + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "description": string, + "tvInfo": { + "showType": sting, + "duration": string, + "sub": number, + "dub": number, + "eps": number + }, + "adultContent":boolean, + }, + {...} + ], + "totalPages":number + } +} +``` + +### `GET` Search result's info + +```bash + GET /api/search +``` + +### Endpoint + +```bash + /api/search?keyword={string} +``` + +#### Parameters + +| Parameter | Parameter-Type | Type | Description | Mandatory ? | Default | +| :-------: | :------------: | :------: | :---------: | :---------: | :-----: | +| `keyword` | `query` | `string` | `keyword` | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/search?keyword=one%20punch%20man"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": [ + { + "id":string, + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "tvInfo": [Object] + }, + { + "id":string, + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "tvInfo": [Object] + }, + {...} + ] +} +``` + +### `GET` Search suggestions + +```bash + GET /api/search/suggest +``` + +### Endpoint + +```bash + /api/search/suggest?keyword={string} +``` + +#### Parameters + +| Parameter | Parameter-Type | Type | Description | Mandatory ? | Default | +| :-------: | :------------: | :------: | :---------: | :---------: | :-----: | +| `keyword` | `query` | `string` | `keyword` | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/search/suggest?keyword=demon"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": [ + { + "id":"string", + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "releaseDate": string, + "showType": string, + "duration": string, + }, + { + "id":"string", + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "releaseDate": string, + "showType": string, + "duration": string, + }, + {...} + ] +} +``` + +### `GET` Filter Anime + +```bash +GET /api/filter +``` + +#### Endpoint + +```bash +/api/filter +``` + +#### Parameters + +| Parameter | Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :--------: | :------------: | :-------: | :----------------------------------------------------------- | :---------: | :---------: | +| `type` | `query` | string | Type of anime (e.g., `movie`, `tv`, etc.) | No ❌ | `ALL` | +| `status` | `query` | string | Status of anime (e.g., `finished`, `currently_airing`, etc.) | No ❌ | `ALL` | +| `rated` | `query` | string | Rating of anime (e.g., `G`, `PG`, etc.) | No ❌ | `ALL` | +| `score` | `query` | string | Score rating (e.g., `1` to `10`) | No ❌ | `ALL` | +| `season` | `query` | string | Season of anime (e.g., `spring`, `summer`, etc.) | No ❌ | `ALL` | +| `language` | `query` | string | Language of anime (e.g., `sub`, `dub`) | No ❌ | `ALL` | +| `genres` | `query` | string | Comma-separated list of genres (e.g., `action, comedy`) | No ❌ | `ALL` | +| `sort` | `query` | string | Sorting method (e.g., `default`, `score`, etc.) | No ❌ | `DEFAULT` | +| `page` | `query` | number | Page number for pagination | No ❌ | `1` | +| `sy` | `query` | number | Start year | No ❌ | `undefined` | +| `sm` | `query` | number | Start month | No ❌ | `undefined` | +| `sd` | `query` | number | Start day | No ❌ | `undefined` | +| `ey` | `query` | number | End year | No ❌ | `undefined` | +| `em` | `query` | number | End month | No ❌ | `undefined` | +| `ed` | `query` | number | End day | No ❌ | `undefined` | +| `keyword` | `query` | string | Search keyword | No ❌ | `undefined` | + +#### Example of Request + +```javascript +import axios from "axios"; + +const params = { + type: "2", // TV + status: "1", // Finished + rated: "5", // R+ + sort: "default", + page: 1, +}; + +const resp = await axios.get("/api/filter", { params }); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "totalPages": number, + "data": [ + { + "id": string, + "data_id": number, + "poster": string, + "title": string, + "japanese_title": string, + "description": string, + "tvInfo": { + "showType": string, + "duration": string, + "sub": number, + "dub": number + }, + "adultContent": boolean + }, + {...} + ] + } +} +``` + +### `GET` Anime's episode list + +```bash + GET /api/episodes/ +``` + +### Endpoint + +```bash + /api/episodes/{param} +``` + +#### Parameters + +| Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :------------: | :-------: | :---------: | :---------: | :-----: | +| `param` | string | anime-id | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/episodes/one-piece-100"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": [ + "totalEpisodes":number, + "episodes":[ + { "episode_no": number, + "id": string, + "data_id": number, + "jname": string, + "title": string, + "japanese_title": string + }, + {...} + ] + ] +} +``` + +### `GET` Schedule of upcoming anime + +```bash + GET /api/schedule +``` + +### Endpoint + +```bash + /api/schedule?date={string} +``` + +#### Parameters + +| Parameter | Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :-------: | :------------: | :-------: | :---------: | :---------: | :-----: | +| `date` | query | string | date | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/schedule?date=2024-09-23"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": [ + { + "id":string, + "data_id":number, + "title":string, + "japanese_title":string, + "releaseDate":string, + "time":string, + "episode_no":number + }, + {...} + ] +} +``` + +### `GET` Schedule of next episode of Anime + +```bash + GET /api/schedule/ +``` + +### Endpoint + +```bash + /api/schedule/:id +``` + +#### Parameters + +| Parameter | Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :-------: | :------------: | :-------: | :---------: | :---------: | :-----: | +| `id` | param | string | anime-id | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/schedule/one-piece-100"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success":true, + "results": + { + "nextEpisodeSchedule":"2025-02-08 16:30:00" + } +} +``` + +### `GET` Qtip info + +```bash + GET /api/qtip/ +``` + +### Endpoint + +```bash + /api/qtip/{id} +``` + +#### Parameters + +| Parameter | Data-Type | Description | Mandatory ? | Default | +| :-------: | :-------: | :---------: | :---------: | :-----: | +| `param` | `number` | id | Yes ✔️ | Yes ✔️ | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/qtip/3365"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "title": string, + "rating": double, + "quality": string, + "subCount": number, + "dubCount": number, + "episodeCount": number, + "type": string, + "description": string, + "japaneseTitle": string, + "Synonyms": string, + "airedDate": string, + "status": string, + "genres": [Object], + "watchLink": string + } +} +``` + +### `GET` Characters + +```bash + GET /api/character/list/ +``` + +### Endpoint + +```bash + /api/character/list/{id} +``` + +#### Parameters + +| Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :------------: | :-------: | :---------: | :---------: | :-----: | +| `param` | `string` | anime-id | Yes ✔️ | Yes ✔️ | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/character/list/one-piece-100"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "currentPage": number, + "totalPages": number, + "data": [ + { + "character": { + "id": string, + "poster": string, + "name": string, + "cast": string + }, + "voiceActors": [ + { + "id": string, + "poster": string, + "name": string + }, + { + "id": string, + "poster": string, + "name": string + }, + {...} + ] + },{...} + ] + } +} + +``` + +### `GET` Streaming info + +```bash + GET /api/stream +``` + +### Endpoint + +```bash + /api/stream?id={string}&server={string}&type={string} +``` + +#### Parameters + +| Parameters | Parameter-Type | Type | Mandatory ? | Default | +| :----------------------: | :------------: | :------: | :---------: | :-----: | +| `id` , `server` , `type` | `query` | `string` | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get( + "/api/stream?id=frieren-beyond-journeys-end-18542?ep=107257&server=hd-1&type=sub" +); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "streamingLink": [ + { + "id":number, + "type": "sub", + "link": { + "file":string, + "type":string, + }, + "tracks": [ + { + "file": string, + "label": string, + "kind": string, + "default": boolean + },{...} + ], + "intro": [Object], + "outro": [Object], + "server":string + } + ], + "servers": [ + { + "type":string, + "data_id": number, + "server_id": number, + "server_name": string + }, + {...} + ] + } +} + + +``` +### `GET` Fallback Streaming info + +```bash + GET /api/stream/fallback +``` + +### Endpoint + +```bash + /api/stream/fallback?id={string}&server={string}&type={string} +``` + +#### Parameters + +| Parameters | Parameter-Type | Type | Mandatory ? | Default | +| :----------------------: | :------------: | :------: | :---------: | :-----: | +| `id` , `server` , `type` | `query` | `string` | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get( + "/api/stream/fallback?id=frieren-beyond-journeys-end-18542?ep=107257&server=hd-1&type=sub" +); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "streamingLink": [ + { + "id":number, + "type": "sub", + "link": { + "file":string, + "type":string, + }, + "tracks": [ + { + "file": string, + "label": string, + "kind": string, + "default": boolean + },{...} + ], + "intro": [Object], + "outro": [Object], + "server":string + } + ], + "servers": [ + { + "type":string, + "data_id": number, + "server_id": number, + "server_name": string + }, + {...} + ] + } +} +``` + +### `GET` Available servers of anime + +```bash + GET /api/servers/ +``` + +### Endpoint + +```bash + /api/servers/{id} +``` + +#### Parameters + +| Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :------------: | :-------: | :---------: | :---------: | :-----: | +| `params` | `string` | `keyword` | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get( + "/api/servers/demon-slayer-kimetsu-no-yaiba-hashira-training-arc-19107?ep=124260" +); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": [ + { + "type": string, + "data_id": number, + "server_id": number, + "serverName": string + }, + {...}, + ] +} +``` + +### `GET` Character Details + +```bash + GET /api/character/ +``` + +### Endpoint + +```bash + /api/character/{id} +``` + +#### Parameters + +| Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :------------: | :-------: | :----------: | :---------: | :-----: | +| `params` | `string` | character-id | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/character/asta-340"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "data": [{ + "id": "asta-340", + "name": "Asta", + "profile": "https://cdn.noitatnemucod.net/thumbnail/400x400/100/945515d313d02fdcd33be3085512c550.jpg", + "japaneseName": "アスタ", + "about": { + "description": "Asta is the main protagonist of Black Clover...", + "style": "

Asta is the main protagonist of Black Clover...

" + }, + "voiceActors": [ + { + "name": "Kajiwara, Gakuto", + "profile": "https://example.com/profile.jpg", + "language": "Japanese", + "id": "gakuto-kajiwara-534" + }, + { + "name": "Dallas Reid", + "profile": "https://example.com/profile2.jpg", + "language": "English", + "id": "dallas-reid-892" + } + ], + "animeography": [ + { + "title": "Black Clover", + "id": "2404", + "role": "Main", + "type": "TV", + "poster": "https://example.com/poster.jpg" + }, + { + "title": "Black Clover: Sword of the Wizard King", + "id": "2405", + "role": "Main", + "type": "Movie", + "poster": "https://example.com/poster2.jpg" + } + ] + }] + } +} +``` + +### `GET` Voice Actor Details + +```bash + GET /api/actors/ +``` + +### Endpoint + +```bash + /api/actors/{id} +``` + +#### Parameters + +| Parameter-Type | Data-Type | Description | Mandatory ? | Default | +| :------------: | :-------: | :------------: | :---------: | :-----: | +| `params` | `string` | voice-actor-id | Yes ✔️ | -- | + +#### Example of request + +```javascript +import axios from "axios"; +const resp = await axios.get("/api/actors/gakuto-kajiwara-534"); +console.log(resp.data); +``` + +#### Sample Response + +```javascript +{ + "success": true, + "results": { + "data": [{ + "id": "gakuto-kajiwara-534", + "name": "Kajiwara, Gakuto", + "profile": "https://cdn.noitatnemucod.net/thumbnail/400x400/100/945515d313d02fdcd33be3085512c550.jpg", + "japaneseName": "梶原岳人", + "about": { + "description": "Kajiwara Gakuto is a Japanese voice actor...", + "style": "

Kajiwara Gakuto is a Japanese voice actor...

" + }, + "roles": [ + { + "anime": { + "title": "Black Clover", + "poster": "https://example.com/poster.jpg", + "type": "TV", + "year": "2017", + "id": "black-clover" + }, + "character": { + "name": "Asta", + "profile": "https://example.com/asta.jpg", + "role": "Main" + } + }, + // ... more roles ... + ] + }] + } +} +``` + +> ### Pull Requests + +- Pull requests are welcomed that address bug fixes, improvements, or new features. +- Fork the repository and create a new branch for your changes. +- Ensure your code follows our coding standards. +- Include tests if applicable. +- Describe your changes clearly in the pull request, explaining the problem and solution. + +> ### Reporting Issues + +If you discover any issues or have suggestions for improvement, please open an issue. Provide a clear and concise description of the problem, steps to reproduce it, and any relevant information about your environment. + +> ### Support +> +> If you like the project feel free to drop a star ✨. Your appreciation means a lot. + +

Made by JustAnime +🫰

\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..8847030 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "anime-api", + "version": "1.0.0", + "description": "A restful api to provide all information about anime", + "main": "./server.js", + "scripts": { + "start": "node ./server.js", + "dev": "nodemon server.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Sayan Das", + "license": "MIT", + "dependencies": { + "axios": "^1.11.0", + "bcrypt": "^6.0.0", + "cheerio": "^1.0.0-rc.12", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", + "crypto-js": "^4.2.0", + "dotenv": "^17.2.0", + "express": "^5.2.1", + "image-pixels": "^2.2.2", + "jsonwebtoken": "^9.0.2" + }, + "type": "module", + "devDependencies": { + "nodemon": "^3.1.10" + }, + "repository": { + "type": "git", + "url": "https://github.com/itzzzme/anime-api.git" + } +} diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..c91513e --- /dev/null +++ b/public/404.html @@ -0,0 +1,91 @@ + + + + + + 404 not found! + + + +
+
+ +

404! Page not found :(

+

+ Sorry, the page you're looking for doesn't exist. If you think + something is broken, report a problem. +

+ +
+
+ + diff --git a/public/anya.gif b/public/anya.gif new file mode 100644 index 0000000..6f9f60c Binary files /dev/null and b/public/anya.gif differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..13b5815 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/image.webp b/public/image.webp new file mode 100644 index 0000000..0f05f8c Binary files /dev/null and b/public/image.webp differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..d4f217d --- /dev/null +++ b/public/index.html @@ -0,0 +1,132 @@ + + + + + + + Anime Api + + + + +
+

Welcome to API 🎉

+ +
+ + +
+
+ + diff --git a/public/pic.jpg b/public/pic.jpg new file mode 100644 index 0000000..4ad5deb Binary files /dev/null and b/public/pic.jpg differ diff --git a/public/wlc.gif b/public/wlc.gif new file mode 100644 index 0000000..2b1a891 Binary files /dev/null and b/public/wlc.gif differ diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..38f1b96 --- /dev/null +++ b/render.yaml @@ -0,0 +1,13 @@ +services: + - type: web + name: anime-api + env: node + buildCommand: npm install + startCommand: npm start + branch: main + plan: free + envVars: + - key: NODE_ENV + value: production + - key: PORT + value: 6969 \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..783e83c --- /dev/null +++ b/server.js @@ -0,0 +1,64 @@ +import dotenv from "dotenv"; +import express from "express"; +import cors from "cors"; +import path from "path"; +import fs from "fs"; +import { fileURLToPath } from "url"; +import { dirname } from "path"; +import { createApiRoutes } from "./src/routes/apiRoutes.js"; + +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 4444; +const __filename = fileURLToPath(import.meta.url); +const publicDir = path.join(dirname(__filename), "public"); +const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(","); + +app.use( + cors({ + origin: allowedOrigins?.includes("*") ? "*" : allowedOrigins || [], + methods: ["GET"], + }) +); + +// Custom CORS middleware +app.use((req, res, next) => { + const origin = req.headers.origin; + if ( + !allowedOrigins || + allowedOrigins.includes("*") || + (origin && allowedOrigins.includes(origin)) + ) { + res.setHeader("Access-Control-Allow-Origin", origin || "*"); + res.setHeader("Access-Control-Allow-Methods", "GET"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type"); + return next(); + } + res + .status(403) + .json({ success: false, message: "Forbidden: Origin not allowed" }); +}); + +app.use(express.static(publicDir, { redirect: false })); + +const jsonResponse = (res, data, status = 200) => + res.status(status).json({ success: true, results: data }); + +const jsonError = (res, message = "Internal server error", status = 500) => + res.status(status).json({ success: false, message }); + +createApiRoutes(app, jsonResponse, jsonError); + +app.use((req, res) => { + const filePath = path.join(publicDir, "404.html"); + if (fs.existsSync(filePath)) { + res.status(404).sendFile(filePath); + } else { + res.status(500).send("Error loading 404 page."); + } +}); + +app.listen(PORT, () => { + console.info(`Listening at ${PORT}`); +}); diff --git a/src/configs/dataUrl.js b/src/configs/dataUrl.js new file mode 100644 index 0000000..35baa8a --- /dev/null +++ b/src/configs/dataUrl.js @@ -0,0 +1,2 @@ +export const dataURL = + "data:image/png;base64,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JDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAt8D/ATY93seMmImHAAAAAElFTkSuQmCC"; diff --git a/src/configs/header.config.js b/src/configs/header.config.js new file mode 100644 index 0000000..d76a08b --- /dev/null +++ b/src/configs/header.config.js @@ -0,0 +1,4 @@ +export const DEFAULT_HEADERS = { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", +}; diff --git a/src/configs/player_v1.config.js b/src/configs/player_v1.config.js new file mode 100644 index 0000000..89f9a5d --- /dev/null +++ b/src/configs/player_v1.config.js @@ -0,0 +1,2 @@ +export const PLAYER_SCRIPT_URL = + "https://megacloud.club/js/player/a/e1-player.min.js"; diff --git a/src/configs/player_v2.config.js b/src/configs/player_v2.config.js new file mode 100644 index 0000000..798bf35 --- /dev/null +++ b/src/configs/player_v2.config.js @@ -0,0 +1,2 @@ +export const PLAYER_SCRIPT_URL = + "https://rapid-cloud.co/js/player/prod/e6-player-v2.min.js"; diff --git a/src/controllers/actors.controller.js b/src/controllers/actors.controller.js new file mode 100644 index 0000000..5155216 --- /dev/null +++ b/src/controllers/actors.controller.js @@ -0,0 +1,20 @@ +import extractVoiceActor from "../extractors/actors.extractor.js"; + +const getVoiceActor = async (req, res) => { + const id = req.params.id; + try { + const voiceActorData = await extractVoiceActor(id); + + // Ensure the data is structured correctly + if (!voiceActorData || voiceActorData.results.data.length === 0) { + return res.status(404).json({ error: "No voice actor found." }); + } + + return res.json(voiceActorData); // Return the desired structure + } catch (e) { + console.error(e); + return res.status(500).json({ error: "An error occurred" }); + } +}; + +export default getVoiceActor; diff --git a/src/controllers/animeInfo.controller.js b/src/controllers/animeInfo.controller.js new file mode 100644 index 0000000..ebacaac --- /dev/null +++ b/src/controllers/animeInfo.controller.js @@ -0,0 +1,27 @@ +import extractAnimeInfo from "../extractors/animeInfo.extractor.js"; +import extractSeasons from "../extractors/seasons.extractor.js"; +import { getCachedData, setCachedData } from "../helper/cache.helper.js"; + +export const getAnimeInfo = async (req, res) => { + const { id } = req.query; + // const cacheKey = `animeInfo_${id}`; + + try { + // const cachedResponse = await getCachedData(cacheKey); + // if (cachedResponse && Object.keys(cachedResponse).length > 0) { + // return cachedResponse; + // } + const [seasons, data] = await Promise.all([ + extractSeasons(id), + extractAnimeInfo(id), + ]); + const responseData = { data: data, seasons: seasons }; + // setCachedData(cacheKey, responseData).catch((err) => { + // console.error("Failed to set cache:", err); + // }); + return responseData; + } catch (e) { + console.error(e); + return res.status(500).json({ error: "An error occurred" }); + } +}; diff --git a/src/controllers/category.controller.js b/src/controllers/category.controller.js new file mode 100644 index 0000000..7853b0d --- /dev/null +++ b/src/controllers/category.controller.js @@ -0,0 +1,29 @@ +import { extractor } from "../extractors/category.extractor.js"; +import { getCachedData, setCachedData } from "../helper/cache.helper.js"; + +export const getCategory = async (req, res, routeType) => { + if (routeType === "genre/martial-arts") { + routeType = "genre/marial-arts"; + } + const requestedPage = parseInt(req.query.page) || 1; + // const cacheKey = `${routeType.replace(/\//g, "_")}_page_${requestedPage}`; + try { + // const cachedResponse = await getCachedData(cacheKey); + // if (cachedResponse && Object.keys(cachedResponse).length > 0) + // return cachedResponse; + const { data, totalPages } = await extractor(routeType, requestedPage); + if (requestedPage > totalPages) { + const error = new Error("Requested page exceeds total available pages."); + error.status = 404; + throw error; + } + const responseData = { totalPages: totalPages, data: data }; + // setCachedData(cacheKey, responseData).catch((err) => { + // console.error("Failed to set cache:", err); + // }); + return responseData; + } catch (e) { + console.error(e); + return e; + } +}; diff --git a/src/controllers/characters.controller.js b/src/controllers/characters.controller.js new file mode 100644 index 0000000..efd2128 --- /dev/null +++ b/src/controllers/characters.controller.js @@ -0,0 +1,20 @@ +import extractCharacter from "../extractors/characters.extractor.js"; + +const getCharacter = async (req, res) => { + const id = req.params.id; + try { + const characterData = await extractCharacter(id); + + // Ensure the data is structured correctly + if (!characterData || characterData.results.data.length === 0) { + return res.status(404).json({ error: "Character not found." }); + } + + return res.json(characterData); // Return the desired structure + } catch (e) { + console.error(e); + return res.status(500).json({ error: "An error occurred" }); + } +}; + +export default getCharacter; \ No newline at end of file diff --git a/src/controllers/episodeList.controller.js b/src/controllers/episodeList.controller.js new file mode 100644 index 0000000..e29cf08 --- /dev/null +++ b/src/controllers/episodeList.controller.js @@ -0,0 +1,21 @@ +import extractEpisodesList from "../extractors/episodeList.extractor.js"; +import { getCachedData, setCachedData } from "../helper/cache.helper.js"; + +export const getEpisodes = async (req,res) => { + const { id } = req.params; + // const cacheKey = `episodes_${id}`; + try { + // const cachedResponse = await getCachedData(cacheKey); + // if (cachedResponse && Object.keys(cachedResponse).length > 0) { + // return cachedResponse; + // } + const data = await extractEpisodesList(encodeURIComponent(id)); + // setCachedData(cacheKey, data).catch((err) => { + // console.error("Failed to set cache:", err); + // }); + return data; + } catch (e) { + console.error("Error fetching episodes:", e); + return e; + } +}; diff --git a/src/controllers/filter.controller.js b/src/controllers/filter.controller.js new file mode 100644 index 0000000..c662020 --- /dev/null +++ b/src/controllers/filter.controller.js @@ -0,0 +1,66 @@ +import extractFilterResults from "../extractors/filter.extractor.js"; + +export const filter = async (req) => { + try { + // Extract all possible query parameters + const { + type, + status, + rated, + score, + season, + language, + genres, + sort, + sy, // Start year + sm, // Start month + sd, // Start day + ey, // End year + em, // End month + ed, // End day + keyword, + page = 1 + } = req.query; + + // Convert page to number + const pageNum = parseInt(page); + + // Create params object only with provided values + const params = {}; + if (type) params.type = type; + if (status) params.status = status; + if (rated) params.rated = rated; + if (score) params.score = score; + if (season) params.season = season; + if (language) params.language = language; + if (genres) params.genres = genres; + if (sort) params.sort = sort; + if (sy) params.sy = sy; + if (sm) params.sm = sm; + if (sd) params.sd = sd; + if (ey) params.ey = ey; + if (em) params.em = em; + if (ed) params.ed = ed; + if (keyword) params.keyword = keyword; + if (pageNum > 1) params.page = pageNum; + + // Log params for debugging + // console.log("Controller params:", params); + + const [totalPage, data, currentPage, hasNextPage] = await extractFilterResults(params); + + if (pageNum > totalPage) { + const error = new Error("Requested page exceeds total available pages."); + error.status = 404; + throw error; + } + + return { data, totalPage, currentPage, hasNextPage }; + } catch (e) { + console.error(e); + if (e.status === 404) { + throw e; + } + throw new Error("An error occurred while processing your request."); + } +}; diff --git a/src/controllers/homeInfo.controller.js b/src/controllers/homeInfo.controller.js new file mode 100644 index 0000000..6ede1b8 --- /dev/null +++ b/src/controllers/homeInfo.controller.js @@ -0,0 +1,68 @@ +import getSpotlights from "../extractors/spotlight.extractor.js"; +import getTrending from "../extractors/trending.extractor.js"; +import extractPage from "../helper/extractPages.helper.js"; +import extractTopTen from "../extractors/topten.extractor.js"; +import { routeTypes } from "../routes/category.route.js"; +import extractSchedule from "../extractors/schedule.extractor.js"; +import { getCachedData, setCachedData } from "../helper/cache.helper.js"; + +const genres = routeTypes + .slice(0, 41) + .map((genre) => genre.replace("genre/", "")); + +export const getHomeInfo = async (req,res) => { + // const cacheKey = "homeInfo"; + try { + // const cachedResponse = await getCachedData(cacheKey); + // if (cachedResponse && Object.keys(cachedResponse).length > 0) { + // return cachedResponse; + // } + const [ + spotlights, + trending, + topTen, + schedule, + topAiring, + mostPopular, + mostFavorite, + latestCompleted, + latestEpisode, + topUpcoming, + recentlyAdded, + ] = await Promise.all([ + getSpotlights(), + getTrending(), + extractTopTen(), + extractSchedule(new Date().toISOString().split("T")[0]), + extractPage(1, "top-airing"), + extractPage(1, "most-popular"), + extractPage(1, "most-favorite"), + extractPage(1, "completed"), + extractPage(1, "recently-updated"), + extractPage(1, "top-upcoming"), + extractPage(1, "recently-added"), + ]); + const responseData = { + spotlights, + trending, + topTen, + today: { schedule }, + topAiring: topAiring[0], + mostPopular: mostPopular[0], + mostFavorite: mostFavorite[0], + latestCompleted: latestCompleted[0], + latestEpisode: latestEpisode[0], + topUpcoming: topUpcoming[0], + recentlyAdded: recentlyAdded[0], + genres, + }; + + // setCachedData(cacheKey, responseData).catch((err) => { + // console.error("Failed to set cache:", err); + // }); + return responseData; + } catch (fetchError) { + console.error("Error fetching fresh data:", fetchError); + return fetchError; + } +}; diff --git a/src/controllers/nextEpisodeSchedule.controller.js b/src/controllers/nextEpisodeSchedule.controller.js new file mode 100644 index 0000000..c00f943 --- /dev/null +++ b/src/controllers/nextEpisodeSchedule.controller.js @@ -0,0 +1,12 @@ +import extractNextEpisodeSchedule from "../extractors/getNextEpisodeSchedule.extractor.js"; + +export const getNextEpisodeSchedule = async (req) => { + const { id } = req.params; + try { + const nextEpisodeSchedule = await extractNextEpisodeSchedule(id); + return { nextEpisodeSchedule: nextEpisodeSchedule }; + } catch (e) { + console.error(e); + return e; + } +}; diff --git a/src/controllers/producer.controller.js b/src/controllers/producer.controller.js new file mode 100644 index 0000000..2e7213b --- /dev/null +++ b/src/controllers/producer.controller.js @@ -0,0 +1,32 @@ +import { getCachedData, setCachedData } from "../helper/cache.helper.js"; +import extractPage from "../helper/extractPages.helper.js"; + +export const getProducer = async (req) => { + const { id } = req.params; + const routeType = `producer/${id}`; + const requestedPage = parseInt(req.query.page) || 1; + // const cacheKey = `${routeType.replace(/\//g, "_")}_page_${requestedPage}`; + try { + // const cachedResponse = await getCachedData(cacheKey); + // if (cachedResponse && Object.keys(cachedResponse).length > 0) { + // return cachedResponse; + // } + const [data, totalPages] = await extractPage(requestedPage, routeType); + if (requestedPage > totalPages) { + const error = new Error("Requested page exceeds total available pages."); + error.status = 404; + throw error; + } + const responseData = { totalPages: totalPages, data: data }; + // setCachedData(cacheKey, responseData).catch((err) => { + // console.error("Failed to set cache:", err); + // }); + return { data, totalPages }; + } catch (e) { + console.error(e); + if (e.status === 404) { + throw e; + } + throw new Error("An error occurred while processing your request."); + } +}; diff --git a/src/controllers/qtip.controller.js b/src/controllers/qtip.controller.js new file mode 100644 index 0000000..3ef3434 --- /dev/null +++ b/src/controllers/qtip.controller.js @@ -0,0 +1,12 @@ +import extractQtip from "../extractors/qtip.extractor.js"; + +export const getQtip = async (req) => { + try { + const { id } = req.params; + const data = await extractQtip(id); + return data; + } catch (e) { + console.error(e); + return e; + } +}; diff --git a/src/controllers/random.controller.js b/src/controllers/random.controller.js new file mode 100644 index 0000000..236fb8c --- /dev/null +++ b/src/controllers/random.controller.js @@ -0,0 +1,11 @@ +import extractRandom from "../extractors/random.extractor.js"; + +export const getRandom = async (req,res) => { + try { + const data = await extractRandom(); + return data; + } catch (error) { + console.error("Error getting random anime:", error.message); + return e; + } +}; diff --git a/src/controllers/randomId.controller.js b/src/controllers/randomId.controller.js new file mode 100644 index 0000000..004fd29 --- /dev/null +++ b/src/controllers/randomId.controller.js @@ -0,0 +1,11 @@ +import extractRandomId from "../extractors/randomId.extractor.js"; + +export const getRandomId = async (req,res) => { + try { + const data = await extractRandomId(); + return data; + } catch (error) { + console.error("Error getting random anime ID:", error.message); + return e; + } +}; diff --git a/src/controllers/schedule.controller.js b/src/controllers/schedule.controller.js new file mode 100644 index 0000000..6c74db8 --- /dev/null +++ b/src/controllers/schedule.controller.js @@ -0,0 +1,13 @@ +import extractSchedule from "../extractors/schedule.extractor.js"; + +export const getSchedule = async (req) => { + const date = req.query.date; + const tzOffset = req.query.tzOffset || -330; + try { + const data = await extractSchedule(date, tzOffset); + return data; + } catch (e) { + console.error(e); + return e; + } +}; diff --git a/src/controllers/search.controller.js b/src/controllers/search.controller.js new file mode 100644 index 0000000..71a5789 --- /dev/null +++ b/src/controllers/search.controller.js @@ -0,0 +1,43 @@ +import extractSearchResults from "../extractors/search.extractor.js"; +import convertForeignLanguage from "../helper/foreignInput.helper.js"; + +export const search = async (req) => { + try { + let { keyword, type, status, rated, score, season, language, genres, sort, sy, sm, sd, ey, em, ed } = req.query; + let page = parseInt(req.query.page) || 1; + + // Check if the search keyword is in a foreign language and if it can be converted + keyword = await convertForeignLanguage(keyword); + + const [totalPage, data] = await extractSearchResults({ + keyword: keyword, + type: type, + status: status, + rated: rated, + score: score, + season: season, + language: language, + genres: genres, + sort: sort, + page: page, + sy: sy, + sm: sm, + sd: sd, + ey: ey, + em: em, + ed: ed, + }); + if (page > totalPage) { + const error = new Error("Requested page exceeds total available pages."); + error.status = 404; + throw error; + } + return { data, totalPage }; + } catch (e) { + console.error(e); + if (e.status === 404) { + throw e; + } + throw new Error("An error occurred while processing your request."); + } +}; diff --git a/src/controllers/servers.controller.js b/src/controllers/servers.controller.js new file mode 100644 index 0000000..445c1bb --- /dev/null +++ b/src/controllers/servers.controller.js @@ -0,0 +1,12 @@ +import { extractServers } from "../extractors/streamInfo.extractor.js"; + +export const getServers = async (req) => { + try { + const { ep } = req.query; + const servers = await extractServers(ep); + return servers; + } catch (e) { + console.error(e); + return e; + } +}; diff --git a/src/controllers/streamInfo.controller.js b/src/controllers/streamInfo.controller.js new file mode 100644 index 0000000..5952f78 --- /dev/null +++ b/src/controllers/streamInfo.controller.js @@ -0,0 +1,17 @@ +import { extractStreamingInfo } from "../extractors/streamInfo.extractor.js"; + +export const getStreamInfo = async (req, res, fallback = false) => { + try { + const input = req.query.id; + const server = req.query.server; + const type = req.query.type; + const match = input.match(/ep=(\d+)/); + if (!match) throw new Error("Invalid URL format"); + const finalId = match[1]; + const streamingInfo = await extractStreamingInfo(finalId, server, type, fallback); + return streamingInfo; + } catch (e) { + console.error(e); + return { error: e.message }; + } +}; diff --git a/src/controllers/suggestion.controller.js b/src/controllers/suggestion.controller.js new file mode 100644 index 0000000..f1fe236 --- /dev/null +++ b/src/controllers/suggestion.controller.js @@ -0,0 +1,17 @@ +import getSuggestion from "../extractors/suggestion.extractor.js"; +import convertForeignLanguage from "../helper/foreignInput.helper.js"; + +export const getSuggestions = async (req) => { + let { keyword } = req.query; + + // Check if the search keyword is in a foreign language and if it can be converted + keyword = await convertForeignLanguage(keyword); + + try { + const data = await getSuggestion(encodeURIComponent(keyword)); + return data; + } catch (e) { + console.error(e); + return e; + } +}; diff --git a/src/controllers/topsearch.controller.js b/src/controllers/topsearch.controller.js new file mode 100644 index 0000000..fe920aa --- /dev/null +++ b/src/controllers/topsearch.controller.js @@ -0,0 +1,13 @@ +import extractTopSearch from "../extractors/topsearch.extractor.js"; + +const getTopSearch = async () => { + try { + const data = await extractTopSearch(); + return data; + } catch (e) { + console.error(e); + return e; + } +}; + +export default getTopSearch; diff --git a/src/controllers/topten.controller.js b/src/controllers/topten.controller.js new file mode 100644 index 0000000..7059e08 --- /dev/null +++ b/src/controllers/topten.controller.js @@ -0,0 +1,22 @@ +import extractTopTen from "../extractors/topten.extractor.js"; +import { getCachedData, setCachedData } from "../helper/cache.helper.js"; + +export const getTopTen = async (req,res) => { + // const cacheKey = "topTen"; + try { + // const cachedResponse = await getCachedData(cacheKey); + // if (cachedResponse && Object.keys(cachedResponse).length > 0) { + // return cachedResponse; + // } + const topTen = await extractTopTen(); + // await setCachedData(cacheKey, topTen).catch((err) => { + // console.error("Failed to set cache:", err); + // }); + return topTen; + } catch (e) { + console.error(e); + return c + .status(500) + .json({ success: false, error: "Internal Server Error" }); + } +}; diff --git a/src/controllers/voiceactor.controller.js b/src/controllers/voiceactor.controller.js new file mode 100644 index 0000000..bb2e44c --- /dev/null +++ b/src/controllers/voiceactor.controller.js @@ -0,0 +1,16 @@ +import extractVoiceActor from "../extractors/voiceactor.extractor.js"; + +export const getVoiceActors = async (req, res) => { + const requestedPage = parseInt(req.query.page) || 1; + const id = req.params.id; + try { + const { totalPages, charactersVoiceActors: data } = await extractVoiceActor( + id, + requestedPage + ); + return { currentPage: requestedPage, totalPages, data }; + } catch (e) { + console.error(e); + return e; + } +}; diff --git a/src/controllers/watchlist.controller.js b/src/controllers/watchlist.controller.js new file mode 100644 index 0000000..57e2f9c --- /dev/null +++ b/src/controllers/watchlist.controller.js @@ -0,0 +1,40 @@ +import extractWatchlist from "../extractors/watchlist.extractor.js"; + +export const getWatchlist = async (req, res) => { + const { userId, page = 1 } = req.params; + + try { + const { watchlist, totalPages } = await extractWatchlist(userId, page); + + // Restructuring the response + return res.json({ + success: true, + results: { + totalPages, // Include total pages in the response + data: watchlist.map(item => ({ + id: item.id, + data_id: item.data_id, + poster: item.poster, + title: item.title, + japanese_title: item.japanese_title, + description: item.description, + tvInfo: { + showType: item.tvInfo.showType, + duration: item.tvInfo.duration, + sub: item.tvInfo.sub, + dub: item.tvInfo.dub, + // Include eps if it exists + ...(item.tvInfo.eps && { eps: item.tvInfo.eps }) + }, + adultContent: item.adultContent, + })) + } + }); + } catch (error) { + console.error("Error getting watchlist:", error.message); + + if (!res.headersSent) { + return res.status(500).json({ error: "An error occurred while fetching the watchlist." }); + } + } +}; \ No newline at end of file diff --git a/src/extractors/actors.extractor.js b/src/extractors/actors.extractor.js new file mode 100644 index 0000000..e6e4c0f --- /dev/null +++ b/src/extractors/actors.extractor.js @@ -0,0 +1,91 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +export async function extractVoiceActor(id) { + try { + const response = await axios.get(`https://${v1_base_url}/people/${id}`); + const $ = cheerio.load(response.data); + + // Extract basic information + const name = $(".apw-detail .name").text().trim(); + const japaneseName = $(".apw-detail .sub-name").text().trim(); + + // Extract profile image + const profile = $(".avatar-circle img").attr("src"); // Extracting the profile image URL + + // Extract about information as a full bio description + const bioText = $("#bio .bio").text().trim(); + const bioHtml = $("#bio .bio").html(); // Capture the raw HTML + const about = { + description: bioText, // Store the full bio as a single description + style: bioHtml, // Store the full HTML structure + }; + + // Extract voice acting roles + const roles = []; + $(".bac-list-wrap .bac-item").each((_, element) => { + const animeElement = $(element).find(".per-info.anime-info.ltr"); + const characterElement = $(element).find(".per-info.rtl"); + + const role = { + anime: { + id: animeElement.find(".pi-name a").attr("href")?.split("/").pop(), + title: animeElement.find(".pi-name a").text().trim(), + poster: + animeElement.find(".pi-avatar img").attr("data-src") || + animeElement.find(".pi-avatar img").attr("src"), + type: animeElement + .find(".pi-cast") + .text() + .trim() + .split(",")[0] + .trim(), + year: animeElement + .find(".pi-cast") + .text() + .trim() + .split(",")[1] + ?.trim(), + }, + character: { + id: characterElement + .find(".pi-name a") + .attr("href") + ?.split("/") + .pop(), + name: characterElement.find(".pi-name a").text().trim(), + profile: + characterElement.find(".pi-avatar img").attr("data-src") || + characterElement.find(".pi-avatar img").attr("src"), + role: characterElement.find(".pi-cast").text().trim(), + }, + }; + roles.push(role); + }); + + // Construct the final response + const voiceActorData = { + success: true, + results: { + data: [ + { + id, + name, + profile, + japaneseName, + about, + roles, + }, + ], + }, + }; + + return voiceActorData; + } catch (error) { + console.error("Error extracting voice actor data:", error); + throw new Error("Failed to extract voice actor information"); + } +} + +export default extractVoiceActor; diff --git a/src/extractors/animeInfo.extractor.js b/src/extractors/animeInfo.extractor.js new file mode 100644 index 0000000..9df206f --- /dev/null +++ b/src/extractors/animeInfo.extractor.js @@ -0,0 +1,194 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import formatTitle from "../helper/formatTitle.helper.js"; +import { v1_base_url } from "../utils/base_v1.js"; +import extractRecommendedData from "./recommend.extractor.js"; +import extractRelatedData from "./related.extractor.js"; +import extractPopularData from "./popular.extractor.js"; + +async function extractAnimeInfo(id) { + try { + const [resp, characterData] = await Promise.all([ + axios.get(`https://${v1_base_url}/${id}`), + axios.get( + `https://${v1_base_url}/ajax/character/list/${id.split("-").pop()}` + ), + ]); + const characterHtml = characterData.data?.html || ""; + const $1 = cheerio.load(characterHtml); + const $ = cheerio.load(resp.data); + const data_id = id.split("-").pop(); + const titleElement = $("#ani_detail .film-name"); + const showType = $("#ani_detail .prebreadcrumb ol li") + .eq(1) + .find("a") + .text() + .trim(); + const posterElement = $("#ani_detail .film-poster"); + const tvInfoElement = $("#ani_detail .film-stats"); + const tvInfo = {}; + tvInfoElement.find(".tick-item, span.item").each((_, element) => { + const el = $(element); + const text = el.text().trim(); + if (el.hasClass("tick-quality")) tvInfo.quality = text; + else if (el.hasClass("tick-sub")) tvInfo.sub = text; + else if (el.hasClass("tick-dub")) tvInfo.dub = text; + else if (el.hasClass("tick-eps")) tvInfo.eps = text; + else if (el.hasClass("tick-pg")) tvInfo.rating = text; + else if (el.is("span.item")) { + if (!tvInfo.showType) tvInfo.showType = text; + else if (!tvInfo.duration) tvInfo.duration = text; + } + }); + + const element = $( + "#ani_detail > .ani_detail-stage > .container > .anis-content > .anisc-info-wrap > .anisc-info > .item" + ); + const overviewElement = $("#ani_detail .film-description .text"); + + const title = titleElement.text().trim(); + const japanese_title = titleElement.attr("data-jname"); + const synonyms = $('.item.item-title:has(.item-head:contains("Synonyms")) .name').text().trim(); + const poster = posterElement.find("img").attr("src"); + const syncDataScript = $("#syncData").html(); + let anilistId = null; + let malId = null; + + if (syncDataScript) { + try { + const syncData = JSON.parse(syncDataScript); + anilistId = syncData.anilist_id || null; + malId = syncData.mal_id || null; + } catch (error) { + console.error("Error parsing syncData:", error); + } + } + + const animeInfo = {}; + element.each((_, el) => { + const key = $(el).find(".item-head").text().trim().replace(":", ""); + const value = + key === "Genres" || key === "Producers" + ? $(el) + .find("a") + .map((_, a) => $(a).text().split(" ").join("-").trim()) + .get() + : $(el).find(".name").text().split(" ").join("-").trim(); + animeInfo[key] = value; + }); + + const trailers = []; + $('.block_area-promotions-list .screen-items .item').each((_, element) => { + const el = $(element); + const title = el.attr('data-title'); + const url = el.attr('data-src'); + if (url) { + const fullUrl = url.startsWith('//') ? `https:${url}` : url; + let videoId = null; + const match = fullUrl.match(/\/embed\/([^?&]+)/); + if (match && match[1]) { + videoId = match[1]; + } + trailers.push({ + title: title || null, + url: fullUrl, + thumbnail: videoId ? `https://img.youtube.com/vi/${videoId}/hqdefault.jpg` : null + }); + } + }); + animeInfo.trailers = trailers; + + const season_id = formatTitle(title, data_id); + animeInfo["Overview"] = overviewElement.text().trim(); + animeInfo["tvInfo"] = tvInfo; + + let adultContent = false; + const tickRateText = $(".tick-rate", posterElement).text().trim(); + if (tickRateText.includes("18+")) { + adultContent = true; + } + + const [recommended_data, related_data, popular_data] = await Promise.all([ + extractRecommendedData($), + extractRelatedData($), + extractPopularData($), + ]); + let charactersVoiceActors = []; + if (characterHtml) { + charactersVoiceActors = $1(".bac-list-wrap .bac-item") + .map((index, el) => { + const character = { + id: + $1(el) + .find(".per-info.ltr .pi-avatar") + .attr("href") + ?.split("/")[2] || "", + poster: + $1(el).find(".per-info.ltr .pi-avatar img").attr("data-src") || + "", + name: $1(el).find(".per-info.ltr .pi-detail a").text(), + cast: $1(el).find(".per-info.ltr .pi-detail .pi-cast").text(), + }; + + let voiceActors = []; + const rtlVoiceActors = $1(el).find(".per-info.rtl"); + const xxVoiceActors = $1(el).find( + ".per-info.per-info-xx .pix-list .pi-avatar" + ); + if (rtlVoiceActors.length > 0) { + voiceActors = rtlVoiceActors + .map((_, actorEl) => ({ + id: $1(actorEl).find("a").attr("href")?.split("/").pop() || "", + poster: $1(actorEl).find("img").attr("data-src") || "", + name: + $1(actorEl).find(".pi-detail .pi-name a").text().trim() || "", + })) + .get(); + } else if (xxVoiceActors.length > 0) { + voiceActors = xxVoiceActors + .map((_, actorEl) => ({ + id: $1(actorEl).attr("href")?.split("/").pop() || "", + poster: $1(actorEl).find("img").attr("data-src") || "", + name: $1(actorEl).attr("title") || "", + })) + .get(); + } + if (voiceActors.length === 0) { + voiceActors = $1(el) + .find(".per-info.per-info-xx .pix-list .pi-avatar") + .map((_, actorEl) => ({ + id: $1(actorEl).attr("href")?.split("/")[2] || "", + poster: $1(actorEl).find("img").attr("data-src") || "", + name: $1(actorEl).attr("title") || "", + })) + .get(); + } + + return { character, voiceActors }; + }) + .get(); + } + + return { + adultContent, + data_id, + id: season_id, + anilistId, + malId, + title, + japanese_title, + synonyms, + poster, + showType, + animeInfo, + charactersVoiceActors, + recommended_data, + related_data, + popular_data, + }; + } catch (e) { + console.error("Error extracting anime info:", e); + } +} + +export default extractAnimeInfo; diff --git a/src/extractors/category.extractor.js b/src/extractors/category.extractor.js new file mode 100644 index 0000000..d40e987 --- /dev/null +++ b/src/extractors/category.extractor.js @@ -0,0 +1,14 @@ +import extractPage from "../helper/extractPages.helper.js"; + +export async function extractor(path, page) { + try { + const [data, totalPages] = await extractPage(page, path); + return { data, totalPages }; + } catch (error) { + console.error( + `Error extracting data for ${path} from page ${page}:`, + error.message + ); + throw error; + } +} diff --git a/src/extractors/characters.extractor.js b/src/extractors/characters.extractor.js new file mode 100644 index 0000000..c553bc7 --- /dev/null +++ b/src/extractors/characters.extractor.js @@ -0,0 +1,91 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +export async function extractCharacter(id) { + try { + const response = await axios.get(`https://${v1_base_url}//character/${id}`); + const $ = cheerio.load(response.data); + + // Extract basic information + const name = $(".apw-detail .name").text().trim(); + const japaneseName = $(".apw-detail .sub-name").text().trim(); + + // Extract profile image + const profile = $(".avatar-circle img").attr("src"); + + // Extract about information + const bioText = $("#bio .bio").text().trim(); + const bioHtml = $("#bio .bio").html(); + const about = { + description: bioText, + style: bioHtml, + }; + + // Extract voice actors + const voiceActors = []; + $("#voiactor .per-info").each((_, element) => { + const voiceActorElement = $(element); + + const voiceActor = { + name: voiceActorElement.find(".pi-name a").text().trim(), + profile: voiceActorElement.find(".pi-avatar img").attr("src"), + language: voiceActorElement.find(".pi-cast").text().trim(), + id: voiceActorElement.find(".pi-name a").attr("href")?.split("/").pop(), + }; + + if (voiceActor.name && voiceActor.id) { + voiceActors.push(voiceActor); + } + }); + + // Extract animeography + const animeography = []; + $(".anif-block-ul li").each((_, el) => { + const item = $(el); + const anchor = item.find(".film-name a.dynamic-name"); + + const title = anchor.text().trim(); + const japanese_title = anchor.attr("data-jname")?.trim(); + const id = anchor.attr("href")?.split("/").pop(); + const role = item.find(".fdi-item").first().text().trim(); + const type = item.find(".fdi-item").last().text().trim(); + const poster = item.find(".film-poster img").attr("src"); + + if (title && id) { + animeography.push({ + title, + japanese_title, + id, + role: role.replace(" (Role)", ""), + type, + poster, + }); + } + }); + + const characterData = { + success: true, + results: { + data: [ + { + id, + name, + profile, + japaneseName, + about, + voiceActors, + animeography, + }, + ], + }, + }; + + return characterData; + } catch (error) { + console.error("Error extracting character data:", error); + throw new Error("Failed to extract character information"); + } +} + +export default extractCharacter; diff --git a/src/extractors/episodeList.extractor.js b/src/extractors/episodeList.extractor.js new file mode 100644 index 0000000..94db143 --- /dev/null +++ b/src/extractors/episodeList.extractor.js @@ -0,0 +1,39 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +async function extractEpisodesList(id) { + try { + const showId = id.split("-").pop(); + const response = await axios.get( + `https://${v1_base_url}/ajax/v2/episode/list/${showId}`, + { + headers: { + "X-Requested-With": "XMLHttpRequest", + Referer: `https://${v1_base_url}/watch/${id}`, + }, + } + ); + if (!response.data.html) return []; + const $ = cheerio.load(response.data.html); + const res = { + totalEpisodes: 0, + episodes: [], + }; + res.totalEpisodes = Number($(".detail-infor-content .ss-list a").length); + $(".detail-infor-content .ss-list a").each((_, el) => { + res.episodes.push({ + episode_no: Number($(el).attr("data-number")), + id: $(el)?.attr("href")?.split("/")?.pop() || null, + title: $(el)?.attr("title")?.trim() || null, + japanese_title: $(el).find(".ep-name").attr("data-jname"), + filler: $(el).hasClass("ssl-item-filler"), + }); + }); + return res; + } catch (error) { + console.error(error); + return []; + } +} +export default extractEpisodesList; diff --git a/src/extractors/filter.extractor.js b/src/extractors/filter.extractor.js new file mode 100644 index 0000000..6c9d440 --- /dev/null +++ b/src/extractors/filter.extractor.js @@ -0,0 +1,174 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { DEFAULT_HEADERS } from "../configs/header.config.js"; +import { v1_base_url } from "../utils/base_v1.js"; +import { + FILTER_LANGUAGE_MAP, + GENRE_MAP, + FILTER_TYPES, + FILTER_STATUS, + FILTER_RATED, + FILTER_SCORE, + FILTER_SEASON, + FILTER_SORT, +} from "../routes/filter.maping.js"; + +async function extractFilterResults(params = {}) { + try { + const normalizeParam = (param, mapping) => { + if (!param) return undefined; + + if (typeof param === "string") { + const isAlreadyId = Object.values(mapping).includes(param); + if (isAlreadyId) { + return param; + } + + const key = param.trim().toUpperCase(); + return mapping.hasOwnProperty(key) ? mapping[key] : undefined; + } + return param; + }; + + const typeParam = normalizeParam(params.type, FILTER_TYPES); + const statusParam = normalizeParam(params.status, FILTER_STATUS); + const ratedParam = normalizeParam(params.rated, FILTER_RATED); + const scoreParam = normalizeParam(params.score, FILTER_SCORE); + const seasonParam = normalizeParam(params.season, FILTER_SEASON); + const sortParam = normalizeParam(params.sort, FILTER_SORT); + + let languageParam = params.language; + if (languageParam != null) { + languageParam = String(languageParam).trim().toUpperCase(); + languageParam = FILTER_LANGUAGE_MAP[languageParam] ?? (Object.values(FILTER_LANGUAGE_MAP).includes(languageParam) ? languageParam : undefined); + } + + let genresParam = params.genres; + if (typeof genresParam === "string") { + genresParam = genresParam + .split(",") + .map((genre) => GENRE_MAP[genre.trim().toUpperCase()] || genre.trim()) + .join(","); + } + + const filteredParams = { + type: typeParam, + status: statusParam, + rated: ratedParam, + score: scoreParam, + season: seasonParam, + language: languageParam, + genres: genresParam, + sort: sortParam, + page: params.page || 1, + sy: params.sy || undefined, + sm: params.sm || undefined, + sd: params.sd || undefined, + ey: params.ey || undefined, + em: params.em || undefined, + ed: params.ed || undefined, + keyword: params.keyword || undefined, + }; + + Object.keys(filteredParams).forEach((key) => { + if (filteredParams[key] === undefined) { + delete filteredParams[key]; + } + }); + + const queryParams = new URLSearchParams(filteredParams).toString(); + + let apiUrl = `https://${v1_base_url}/filter?${queryParams}`; + + if (filteredParams.keyword) { + apiUrl = `https://${v1_base_url}/search?${queryParams}`; + } + + const resp = await axios.get(apiUrl, { + headers: { + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", + + "Accept-Encoding": "gzip, deflate, br", + "User-Agent": DEFAULT_HEADERS, + }, + }); + + const $ = cheerio.load(resp.data); + const elements = ".flw-item"; + const result = []; + + $(elements).each((_, el) => { + const $el = $(el); + const href = $el.find(".film-poster-ahref").attr("href"); + const data_id = Number($el.find(".film-poster-ahref").attr("data-id")); + + result.push({ + id: href ? href.slice(1) : null, + data_id: data_id ? `${data_id}` : null, + poster: + $el.find(".film-poster .film-poster-img").attr("data-src") || + $el.find(".film-poster .film-poster-img").attr("src") || + null, + title: $el.find(".film-name .dynamic-name").text().trim(), + japanese_title: + $el.find(".film-name .dynamic-name").attr("data-jname") || null, + tvInfo: { + showType: + $el.find(".fd-infor .fdi-item:first-child").text().trim() || + "Unknown", + duration: $el.find(".fd-infor .fdi-duration").text().trim() || null, + sub: + Number( + $el + .find(".tick-sub") + .text() + .replace(/[^0-9]/g, "") + ) || null, + dub: + Number( + $el + .find(".tick-dub") + .text() + .replace(/[^0-9]/g, "") + ) || null, + eps: + Number( + $el + .find(".tick-eps") + .text() + .replace(/[^0-9]/g, "") + ) || null, + }, + adultContent: $el.find(".tick-rate").text().trim() || null, + }); + }); + + const totalPage = Number( + $('.pre-pagination nav .pagination > .page-item a[title="Last"]') + ?.attr("href") + ?.split("=") + .pop() || + $('.pre-pagination nav .pagination > .page-item a[title="Next"]') + ?.attr("href") + ?.split("=") + .pop() || + $(".pre-pagination nav .pagination > .page-item.active a") + ?.text() + ?.trim() || + 1 + ); + + return [ + parseInt(totalPage, 10), + result.length > 0 ? result : [], + parseInt(params.page, 10) || 1, + parseInt(params.page, 10) < parseInt(totalPage, 10), + ]; + } catch (e) { + console.error("Error fetching data:", e); + throw e; + } +} + +export { extractFilterResults as default }; diff --git a/src/extractors/getNextEpisodeSchedule.extractor.js b/src/extractors/getNextEpisodeSchedule.extractor.js new file mode 100644 index 0000000..a83b57d --- /dev/null +++ b/src/extractors/getNextEpisodeSchedule.extractor.js @@ -0,0 +1,16 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +export default async function extractNextEpisodeSchedule(id) { + try { + const { data } = await axios.get(`https://${v1_base_url}/watch/${id}`); + const $ = cheerio.load(data); + const nextEpisodeSchedule = $( + ".schedule-alert > .alert.small > span:last" + ).attr("data-value"); + return nextEpisodeSchedule; + } catch (error) { + console.error(error); + } +} diff --git a/src/extractors/popular.extractor.js b/src/extractors/popular.extractor.js new file mode 100644 index 0000000..182b01a --- /dev/null +++ b/src/extractors/popular.extractor.js @@ -0,0 +1,49 @@ +export default async function extractPopularData($) { + const popularSection = $('#main-sidebar .block_area:has(.cat-heading:contains("Most Popular"))'); + + const popularElements = popularSection.find(".anif-block-ul .ulclear li"); + + return await Promise.all( + popularElements + .map(async (index, element) => { + const $el = $(element); + const id = $el.find(".film-detail .film-name a").attr("href")?.split("/").pop(); + const data_id = $el.find(".film-poster").attr("data-id"); + const title = $el.find(".film-detail .film-name a").text().trim(); + const japanese_title = $el.find(".film-detail .film-name a").attr("data-jname")?.trim(); + const poster = $el.find(".film-poster img").attr("data-src") || $el.find(".film-poster img").attr("src"); + + // Extract show type like "TV", "Movie", etc. + const showTypeText = $el.find(".tick").text().toLowerCase(); + const showTypeMatch = ["TV", "ONA", "Movie", "OVA", "Special"].find(type => + showTypeText.toLowerCase().includes(type.toLowerCase()) + ); + const tvInfo = { + showType: showTypeMatch || "Unknown" + }; + + // Extract tick items like sub, dub, eps + ["sub", "dub", "eps"].forEach((type) => { + const value = $el.find(`.tick-item.tick-${type}`).text().trim(); + if (value) { + tvInfo[type] = value; + } + }); + + // Adult content check + const tickRateText = $el.find(".film-poster > .tick-rate").text().trim(); + const adultContent = tickRateText.includes("18+"); + + return { + data_id, + id, + title, + japanese_title, + poster, + tvInfo, + adultContent, + }; + }) + .get() + ); +} diff --git a/src/extractors/qtip.extractor.js b/src/extractors/qtip.extractor.js new file mode 100644 index 0000000..60a04f0 --- /dev/null +++ b/src/extractors/qtip.extractor.js @@ -0,0 +1,65 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +export default async function extractQtip(id) { + try { + const { data } = await axios.get( + `https://${v1_base_url}/ajax/movie/qtip/${id}`, + { + headers: { + "x-requested-with": "XMLHttpRequest", + }, + } + ); + const $ = cheerio.load(data); + + const title = $(".pre-qtip-title").text(); + const rating = $(".pqd-li i.fas.fa-star").parent().text().trim(); + const quality = $(".tick-item.tick-quality").text(); + const subCount = $(".tick-item.tick-sub").text().trim(); + const dubCount = $(".tick-item.tick-dub").text().trim(); + const episodeCount = $(".tick-item.tick-eps").text().trim(); + const type = $(".badge.badge-quality").text(); + const description = $(".pre-qtip-description").text().trim(); + const japaneseTitle = $(".pre-qtip-line:contains('Japanese:') .stick-text") + .text() + .trim(); + const airedDate = $(".pre-qtip-line:contains('Aired:') .stick-text") + .text() + .trim(); + const status = $(".pre-qtip-line:contains('Status:') .stick-text") + .text() + .trim(); + const Synonyms = $(".pre-qtip-line:contains('Synonyms:') .stick-text") + .text() + .trim(); + const genres = []; + $(".pre-qtip-line:contains('Genres:') a").each((i, elem) => { + genres.push($(elem).text().trim().split(" ").join("-")); + }); + + const watchLink = $(".pre-qtip-button a.btn.btn-play").attr("href"); + + const extractedData = { + title, + rating, + quality, + subCount, + dubCount, + episodeCount, + type, + description, + japaneseTitle, + Synonyms, + airedDate, + status, + genres, + watchLink, + }; + return extractedData; + } catch (error) { + console.error("Error extracting data:", error); + return error; + } +} diff --git a/src/extractors/random.extractor.js b/src/extractors/random.extractor.js new file mode 100644 index 0000000..0351c4b --- /dev/null +++ b/src/extractors/random.extractor.js @@ -0,0 +1,18 @@ +import axios from "axios"; +import { v1_base_url } from "../utils/base_v1.js"; +import extractAnimeInfo from "./animeInfo.extractor.js"; +import { DEFAULT_HEADERS } from "../configs/header.config.js"; + +const axiosInstance = axios.create({ headers: DEFAULT_HEADERS }); + +export default async function extractRandom() { + try { + const resp = await axiosInstance.get(`https://${v1_base_url}/random`); + const redirectedUrl = resp.request.res.responseUrl; + const id = redirectedUrl.split("/").pop(); + const animeInfo = await extractAnimeInfo(id); + return animeInfo; + } catch (error) { + console.error("Error extracting random anime info:", error); + } +} diff --git a/src/extractors/randomId.extractor.js b/src/extractors/randomId.extractor.js new file mode 100644 index 0000000..69781b7 --- /dev/null +++ b/src/extractors/randomId.extractor.js @@ -0,0 +1,16 @@ +import axios from "axios"; +import { v1_base_url } from "../utils/base_v1.js"; +import { DEFAULT_HEADERS } from "../configs/header.config.js"; + +const axiosInstance = axios.create({ headers: DEFAULT_HEADERS }); + +export default async function extractRandomId() { + try { + const resp = await axiosInstance.get(`https://${v1_base_url}/random`); + const redirectedUrl = resp.request.res.responseUrl; + const id = redirectedUrl.split("/").pop(); + return id; + } catch (error) { + console.error("Error extracting random anime info:", error); + } +} diff --git a/src/extractors/recommend.extractor.js b/src/extractors/recommend.extractor.js new file mode 100644 index 0000000..124cc92 --- /dev/null +++ b/src/extractors/recommend.extractor.js @@ -0,0 +1,65 @@ +export default async function extractRecommendedData($) { + const recommendedElements = $( + "#main-content .block_area_category .tab-content .block_area-content .film_list-wrap .flw-item" + ); + return await Promise.all( + recommendedElements + .map(async (index, element) => { + const id = $(element) + .find(".film-detail .film-name a") + .attr("href") + .split("/") + .pop(); + const data_id = $(element).find(".film-poster a").attr("data-id"); + const title = $(element) + .find(".film-detail .film-name a") + .text() + .trim(); + const japanese_title = $(element) + .find(".film-detail .film-name a") + .attr("data-jname") + .trim(); + const poster = $(element).find(".film-poster img").attr("data-src"); + const $fdiItems = $(".film-detail .fd-infor .fdi-item", element); + const showType = $fdiItems + .filter((_, item) => { + const text = $(item).text().trim().toLowerCase(); + return ["tv", "ona", "movie", "ova", "special"].some((type) => + text.includes(type) + ); + }) + .first(); + + const tvInfo = { + showType: showType ? showType.text().trim() : "Unknown", + duration: $(".film-detail .fd-infor .fdi-duration", element) + .text() + .trim(), + }; + + ["sub", "dub", "eps"].forEach((property) => { + const value = $(`.tick .tick-${property}`, element).text().trim(); + if (value) { + tvInfo[property] = value; + } + }); + let adultContent = false; + const tickRateText = $(".film-poster>.tick-rate", element) + .text() + .trim(); + if (tickRateText.includes("18+")) { + adultContent = true; + } + return { + data_id, + id, + title, + japanese_title, + poster, + tvInfo, + adultContent, + }; + }) + .get() + ); +} diff --git a/src/extractors/related.extractor.js b/src/extractors/related.extractor.js new file mode 100644 index 0000000..eb25ae6 --- /dev/null +++ b/src/extractors/related.extractor.js @@ -0,0 +1,51 @@ +export default async function extractRelatedData($) { + const relatedSection = $('#main-sidebar .block_area:has(.cat-heading:contains("Related Anime"))'); + + const relatedElements = relatedSection.find( + ".anif-block-ul .ulclear li" + ); + + return await Promise.all( + relatedElements + .map(async (index, element) => { + const $el = $(element); + const id = $el.find(".film-detail .film-name a").attr("href")?.split("/").pop(); + const data_id = $el.find(".film-poster").attr("data-id"); + const title = $el.find(".film-detail .film-name a").text().trim(); + const japanese_title = $el.find(".film-detail .film-name a").attr("data-jname")?.trim(); + const poster = $el.find(".film-poster img").attr("data-src") || $el.find(".film-poster img").attr("src"); + + // Extract show type like "TV", "Movie", etc. + const showTypeText = $el.find(".tick").text().toLowerCase(); + const showTypeMatch = ["TV", "ONA", "Movie", "OVA", "Special"].find(type => + showTypeText.toLowerCase().includes(type.toLowerCase()) + ); + const tvInfo = { + showType: showTypeMatch || "Unknown" + }; + + // Extract tick items like sub, dub, eps + ["sub", "dub", "eps"].forEach((type) => { + const value = $el.find(`.tick-item.tick-${type}`).text().trim(); + if (value) { + tvInfo[type] = value; + } + }); + + // Adult content check + const tickRateText = $el.find(".film-poster > .tick-rate").text().trim(); + const adultContent = tickRateText.includes("18+"); + + return { + data_id, + id, + title, + japanese_title, + poster, + tvInfo, + adultContent, + }; + }) + .get() + ); +} diff --git a/src/extractors/schedule.extractor.js b/src/extractors/schedule.extractor.js new file mode 100644 index 0000000..b9d95fc --- /dev/null +++ b/src/extractors/schedule.extractor.js @@ -0,0 +1,51 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +export default async function extractSchedule(date, tzOffset) { + try { + tzOffset = tzOffset ?? -330; + + const resp = await axios.get( + `https://${v1_base_url}/ajax/schedule/list?tzOffset=${tzOffset}&date=${date}` + ); + const $ = cheerio.load(resp.data.html); + const results = []; + + $("li").each((i, element) => { + const id = $(element) + ?.find("a") + .attr("href") + .split("?")[0] + .replace("/", ""); + const data_id = id?.split("-").pop(); + const title = $(element).find(".film-name").text().trim(); + const japanese_title = $(element) + .find(".film-name") + .attr("data-jname") + ?.trim(); + const releaseDate = date; + const time = $(element).find(".time").text().trim(); + const episode_no = $(element) + ?.find(".btn-play") + .text() + .trim() + .split(" ") + .pop(); + results.push({ + id, + data_id, + title, + japanese_title, + releaseDate, + time, + episode_no, + }); + }); + + return results; + } catch (error) { + console.log(error.message); + return []; + } +} diff --git a/src/extractors/search.extractor.js b/src/extractors/search.extractor.js new file mode 100644 index 0000000..805e7f1 --- /dev/null +++ b/src/extractors/search.extractor.js @@ -0,0 +1,184 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { DEFAULT_HEADERS } from "../configs/header.config.js"; +import { v1_base_url } from "../utils/base_v1.js"; +import { + FILTER_LANGUAGE_MAP, + GENRE_MAP, + FILTER_TYPES, + FILTER_STATUS, + FILTER_RATED, + FILTER_SCORE, + FILTER_SEASON, + FILTER_SORT, +} from "../routes/filter.maping.js"; + +async function extractSearchResults(params = {}) { + try { + const normalizeParam = (param, mapping) => { + if (!param) return undefined; + + if (typeof param === "string") { + const isAlreadyId = Object.values(mapping).includes(param); + if (isAlreadyId) { + return param; + } + + const key = param.trim().toUpperCase(); + return mapping.hasOwnProperty(key) ? mapping[key] : undefined; + } + return param; + }; + + const typeParam = normalizeParam(params.type, FILTER_TYPES); + const statusParam = normalizeParam(params.status, FILTER_STATUS); + const ratedParam = normalizeParam(params.rated, FILTER_RATED); + const scoreParam = normalizeParam(params.score, FILTER_SCORE); + const seasonParam = normalizeParam(params.season, FILTER_SEASON); + const sortParam = normalizeParam(params.sort, FILTER_SORT); + + let languageParam = params.language; + if (languageParam != null) { + languageParam = String(languageParam).trim().toUpperCase(); + languageParam = FILTER_LANGUAGE_MAP[languageParam] ?? (Object.values(FILTER_LANGUAGE_MAP).includes(languageParam) ? languageParam : undefined); + } + + let genresParam = params.genres; + if (typeof genresParam === "string") { + genresParam = genresParam + .split(",") + .map((genre) => GENRE_MAP[genre.trim().toUpperCase()] || genre.trim()) + .join(","); + } + + const filteredParams = { + type: typeParam, + status: statusParam, + rated: ratedParam, + score: scoreParam, + season: seasonParam, + language: languageParam, + genres: genresParam, + sort: sortParam, + page: params.page || 1, + sy: params.sy || undefined, + sm: params.sm || undefined, + sd: params.sd || undefined, + ey: params.ey || undefined, + em: params.em || undefined, + ed: params.ed || undefined, + keyword: params.keyword || undefined, + }; + + Object.keys(filteredParams).forEach((key) => { + if (filteredParams[key] === undefined) { + delete filteredParams[key]; + } + }); + + const queryParams = new URLSearchParams(filteredParams).toString(); + + const resp = await axios.get(`https://${v1_base_url}/search?${queryParams}`, { + headers: { + Accept: + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Encoding": "gzip, deflate, br", + "User-Agent": DEFAULT_HEADERS, + }, + }); + + const $ = cheerio.load(resp.data); + const elements = "#main-content .film_list-wrap .flw-item"; + + const totalPage = + Number( + $('.pre-pagination nav .pagination > .page-item a[title="Last"]') + ?.attr("href") + ?.split("=") + .pop() ?? + $('.pre-pagination nav .pagination > .page-item a[title="Next"]') + ?.attr("href") + ?.split("=") + .pop() ?? + $(".pre-pagination nav .pagination > .page-item.active a") + ?.text() + ?.trim() + ) || 1; + + const result = []; + $(elements).each((_, el) => { + const id = + $(el) + .find(".film-detail .film-name .dynamic-name") + ?.attr("href") + ?.slice(1) + .split("?ref=search")[0] || null; + result.push({ + id: id, + data_id: $(el) + .find(".film-poster .film-poster-ahref").attr("data-id"), + title: $(el) + .find(".film-detail .film-name .dynamic-name") + ?.text() + ?.trim(), + japanese_title: + $(el) + .find(".film-detail .film-name .dynamic-name") + ?.attr("data-jname") + ?.trim() || null, + poster: + $(el) + .find(".film-poster .film-poster-img") + ?.attr("data-src") + ?.trim() || null, + duration: + $(el) + .find(".film-detail .fd-infor .fdi-item.fdi-duration") + ?.text() + ?.trim(), + tvInfo: { + showType: + $(el) + .find(".film-detail .fd-infor .fdi-item:nth-of-type(1)") + .text() + .trim() || "Unknown", + rating: $(el).find(".film-poster .tick-rate")?.text()?.trim() || null, + sub: + Number( + $(el) + .find(".film-poster .tick-sub") + ?.text() + ?.trim() + .split(" ") + .pop() + ) || null, + dub: + Number( + $(el) + .find(".film-poster .tick-dub") + ?.text() + ?.trim() + .split(" ") + .pop() + ) || null, + eps: + Number( + $(el) + .find(".film-poster .tick-eps") + ?.text() + ?.trim() + .split(" ") + .pop() + ) || null, + }, + }); + }); + + return [parseInt(totalPage, 10), result.length > 0 ? result : []]; + } catch (e) { + console.error(e); + return e; + } +} + +export default extractSearchResults; diff --git a/src/extractors/seasons.extractor.js b/src/extractors/seasons.extractor.js new file mode 100644 index 0000000..6231082 --- /dev/null +++ b/src/extractors/seasons.extractor.js @@ -0,0 +1,30 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import formatTitle from "../helper/formatTitle.helper.js"; +import { v1_base_url } from "../utils/base_v1.js"; + +async function extractSeasons(id) { + try { + const resp = await axios.get(`https://${v1_base_url}/watch/${id}`); + const $ = cheerio.load(resp.data); + const seasons = $(".anis-watch>.other-season>.inner>.os-list>a") + .map((index, element) => { + const data_number = index; + const data_id = parseInt($(element).attr("href").split("-").pop()); + const season = $(element).find(".title").text().trim(); + const title = $(element).attr("title").trim(); + const id = href.replace(/^\/+/, ""); + const season_poster = $(element) + .find(".season-poster") + .attr("style") + .match(/url\((.*?)\)/)[1]; + return { id, data_number, data_id, season, title, season_poster }; + }) + .get(); + return seasons; + } catch (e) { + console.log(e); + } +} + +export default extractSeasons; diff --git a/src/extractors/spotlight.extractor.js b/src/extractors/spotlight.extractor.js new file mode 100644 index 0000000..61c170d --- /dev/null +++ b/src/extractors/spotlight.extractor.js @@ -0,0 +1,103 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +async function extractSpotlights() { + try { + const resp = await axios.get(`https://${v1_base_url}/home`); + const $ = cheerio.load(resp.data); + + const slideElements = $( + "div.deslide-wrap > div.container > div#slider > div.swiper-wrapper > div.swiper-slide" + ); + + const promises = slideElements + .map(async (ind, ele) => { + const poster = $(ele) + .find( + "div.deslide-item > div.deslide-cover > div.deslide-cover-img > img.film-poster-img" + ) + .attr("data-src"); + const title = $(ele) + .find( + "div.deslide-item > div.deslide-item-content > div.desi-head-title" + ) + .text() + .trim(); + const japanese_title = $(ele) + .find( + "div.deslide-item > div.deslide-item-content > div.desi-head-title" + ) + .attr("data-jname") + .trim(); + const description = $(ele) + .find( + "div.deslide-item > div.deslide-item-content > div.desi-description" + ) + .text() + .trim(); + const id = $(ele) + .find( + ".deslide-item > .deslide-item-content > .desi-buttons > a:eq(0)" + ) + .attr("href") + .split("/") + .pop(); + const data_id = $(ele) + .find( + ".deslide-item > .deslide-item-content > .desi-buttons > a:eq(0)" + ) + .attr("href") + .split("/") + .pop() + .split("-") + .pop(); + const tvInfoMapping = { + 0: "showType", + 1: "duration", + 2: "releaseDate", + 3: "quality", + 4: "episodeInfo", + }; + + const tvInfo = {}; + + await Promise.all( + $(ele) + .find("div.sc-detail > div.scd-item") + .map(async (index, element) => { + const key = tvInfoMapping[index]; + let value = $(element).text().trim().replace(/\n/g, ""); + + const tickContainer = $(element).find(".tick"); + + if (tickContainer.length > 0) { + value = { + sub: tickContainer.find(".tick-sub").text().trim(), + dub: tickContainer.find(".tick-dub").text().trim(), + }; + } + tvInfo[key] = value; + }) + ); + return { + id, + data_id, + poster, + title, + japanese_title, + description, + tvInfo, + }; + }) + .get(); + + const serverData = await Promise.all(promises); + return JSON.parse(JSON.stringify(serverData, null, 2)); + } catch (error) { + console.error("Error fetching data:", error.message); + return error; + } +} + +export default extractSpotlights; diff --git a/src/extractors/streamInfo.extractor.js b/src/extractors/streamInfo.extractor.js new file mode 100644 index 0000000..c7123c7 --- /dev/null +++ b/src/extractors/streamInfo.extractor.js @@ -0,0 +1,68 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; +// import decryptMegacloud from "../parsers/decryptors/megacloud.decryptor.js"; +// import AniplayExtractor from "../parsers/aniplay.parser.js"; +import { decryptSources_v1 } from "../parsers/decryptors/decrypt_v1.decryptor.js"; + +export async function extractServers(id) { + try { + const resp = await axios.get( + `https://${v1_base_url}/ajax/v2/episode/servers?episodeId=${id}` + ); + const $ = cheerio.load(resp.data.html); + const serverData = []; + $(".server-item").each((index, element) => { + const data_id = $(element).attr("data-id"); + const server_id = $(element).attr("data-server-id"); + const type = $(element).attr("data-type"); + + const serverName = $(element).find("a").text().trim(); + serverData.push({ + type, + data_id, + server_id, + serverName, + }); + }); + return serverData; + } catch (error) { + console.log(error); + return []; + } +} + +async function extractStreamingInfo(id, name, type, fallback) { + try { + const servers = await extractServers(id.split("?ep=").pop()); + let requestedServer = servers.filter( + (server) => + server.serverName.toLowerCase() === name.toLowerCase() && + server.type.toLowerCase() === type.toLowerCase() + ); + if (requestedServer.length === 0) { + requestedServer = servers.filter( + (server) => + server.serverName.toLowerCase() === name.toLowerCase() && + server.type.toLowerCase() === "raw" + ); + } + if (requestedServer.length === 0) { + throw new Error( + `No matching server found for name: ${name}, type: ${type}` + ); + } + const streamingLink = await decryptSources_v1( + id, + requestedServer[0].data_id, + name, + type, + fallback + ); + return { streamingLink, servers }; + } catch (error) { + console.error("An error occurred:", error); + return { streamingLink: [], servers: [] }; + } +} +export { extractStreamingInfo }; diff --git a/src/extractors/subtitle.extractor.js b/src/extractors/subtitle.extractor.js new file mode 100644 index 0000000..cdc19e8 --- /dev/null +++ b/src/extractors/subtitle.extractor.js @@ -0,0 +1,19 @@ +import axios from "axios"; +import { v1_base_url } from "../utils/base_v1.js"; +import { provider } from "../utils/provider.js"; + +export async function extractSubtitle(id) { + const resp = await axios.get( + `https://${v1_base_url}/ajax/v2/episode/sources/?id=${id}` + ); + const source = await axios.get( + `${provider}/embed-2/ajax/e-1/getSources?id=${resp.data.link + .split("/") + .pop() + .replace(/\?k=\d?/g, "")}` + ); + const subtitles = source.data.tracks; + const intro = source.data.intro; + const outro = source.data.outro; + return { subtitles, intro, outro }; +} diff --git a/src/extractors/suggestion.extractor.js b/src/extractors/suggestion.extractor.js new file mode 100644 index 0000000..fb3bdad --- /dev/null +++ b/src/extractors/suggestion.extractor.js @@ -0,0 +1,50 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +async function getSuggestions(keyword) { + try { + const resp = await axios.get( + `https://${v1_base_url}/ajax/search/suggest?keyword=${keyword}` + ); + const $ = cheerio.load(resp.data.html); + const results = []; + $(".nav-item") + .not(".nav-bottom") + .each((i, element) => { + const id = $(element).attr("href").split("?")[0].replace("/", ""); + const data_id = id.split("-").pop(); + const poster = $(element).find(".film-poster-img").attr("data-src"); + const title = $(element).find(".film-name").text().trim(); + const japanese_title = $(element).find(".film-name").attr("data-jname").trim(); + const releaseDate = $(element) + .find(".film-infor span") + .first() + .text() + .trim(); + const filmInforHtml = $(element).find(".film-infor").html(); + const showTypeMatch = /<\/i>([^<]+)<\/i>/; + const showType = showTypeMatch.exec(filmInforHtml)?.[1]?.trim() || ""; + const duration = $(element) + .find(".film-infor span") + .last() + .text() + .trim(); + results.push({ + id, + data_id, + poster, + title, + japanese_title, + releaseDate, + showType, + duration, + }); + }); + return results; + } catch (error) { + console.log(error.message); + } +} + +export default getSuggestions; \ No newline at end of file diff --git a/src/extractors/topsearch.extractor.js b/src/extractors/topsearch.extractor.js new file mode 100644 index 0000000..961b5cf --- /dev/null +++ b/src/extractors/topsearch.extractor.js @@ -0,0 +1,21 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +async function extractTopSearch() { + try { + const { data } = await axios.get(`https://${v1_base_url}`); + const $ = cheerio.load(data); + const results = []; + $(".xhashtag a.item").each((_, element) => { + const title = $(element).text().trim(); + const link = $(element).attr("href"); + results.push({ title, link }); + }); + return results; + } catch (error) { + console.error("Error fetching data:", error.message); + } +} + +export default extractTopSearch; diff --git a/src/extractors/topten.extractor.js b/src/extractors/topten.extractor.js new file mode 100644 index 0000000..fa0f5ec --- /dev/null +++ b/src/extractors/topten.extractor.js @@ -0,0 +1,51 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +async function extractTopTen() { + try { + const resp = await axios.get(`https://${v1_base_url}/home`); + const $ = cheerio.load(resp.data); + + const labels = ["today", "week", "month"]; + const result = {}; + + labels.forEach((label, idx) => { + const data = $( + `#main-sidebar .block_area-realtime .block_area-content ul:eq(${idx})>li` + ) + .map((index, element) => { + const number = $(".film-number>span", element).text().trim(); + const title = $(".film-detail>.film-name>a", element).text().trim(); + const poster = $(".film-poster>img", element).attr("data-src"); + const japanese_title = $(".film-detail>.film-name>a", element) + .attr("data-jname") + .trim(); + const data_id = $(".film-poster", element).attr("data-id"); + const id = $(".film-detail>.film-name>a", element) + .attr("href") + .split("/") + .pop(); + const tvInfo = ["sub", "dub", "eps"].reduce((info, property) => { + const value = $(`.tick .tick-${property}`, element).text().trim(); + if (value) { + info[property] = value; + } + return info; + }, {}); + + return { id, data_id, number, title, japanese_title, poster, tvInfo }; + }) + .get(); + + result[label] = data; + }); + + return result; + } catch (error) { + console.error("Error fetching data:", error); + throw error; + } +} + +export default extractTopTen; diff --git a/src/extractors/trending.extractor.js b/src/extractors/trending.extractor.js new file mode 100644 index 0000000..4880307 --- /dev/null +++ b/src/extractors/trending.extractor.js @@ -0,0 +1,35 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +async function fetchAnimeDetails(element) { + const data_id = element.attr("data-id"); + const number = element.find(".number > span").text(); + const poster = element.find("img").attr("data-src"); + const title = element.find(".film-title").text().trim(); + const japanese_title = element.find(".film-title").attr("data-jname").trim(); + const id = element.find("a").attr("href").split("/").pop(); + return { id, data_id, number, poster, title, japanese_title }; +} + +async function extractTrending() { + try { + const resp = await axios.get(`https://${v1_base_url}/home`); + const $ = cheerio.load(resp.data); + + const trendingElements = $("#anime-trending #trending-home .swiper-slide"); + const elementPromises = trendingElements + .map((index, element) => { + return fetchAnimeDetails($(element)); + }) + .get(); + + const trendingData = await Promise.all(elementPromises); + return JSON.parse(JSON.stringify(trendingData)); + } catch (error) { + console.error("Error fetching data:", error.message); + return error; + } +} + +export default extractTrending; diff --git a/src/extractors/voiceactor.extractor.js b/src/extractors/voiceactor.extractor.js new file mode 100644 index 0000000..4da2f15 --- /dev/null +++ b/src/extractors/voiceactor.extractor.js @@ -0,0 +1,78 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +export default async function extractVoiceActor(id, page) { + try { + const resp = await axios.get( + `https://${v1_base_url}/ajax/character/list/${id + .split("-") + .pop()}?page=${page}` + ); + const $ = cheerio.load(resp.data.html); + let totalPages = 1; + const paginationList = $(".pre-pagination nav ul"); + if (paginationList.length) { + const lastPageLink = paginationList.find("li").last().find("a"); + const pageNumber = + lastPageLink.attr("data-url")?.match(/page=(\d+)/)?.[1] || + lastPageLink.text().trim(); + totalPages = parseInt(pageNumber) || totalPages; + } + const charactersVoiceActors = $(".bac-list-wrap .bac-item") + .map((index, el) => { + const character = { + id: + $(el) + .find(".per-info.ltr .pi-avatar") + .attr("href") + ?.split("/")[2] || "", + poster: + $(el).find(".per-info.ltr .pi-avatar img").attr("data-src") || "", + name: $(el).find(".per-info.ltr .pi-detail a").text(), + cast: $(el).find(".per-info.ltr .pi-detail .pi-cast").text(), + }; + + let voiceActors = []; + const rtlVoiceActors = $(el).find(".per-info.rtl"); + const xxVoiceActors = $(el).find( + ".per-info.per-info-xx .pix-list .pi-avatar" + ); + if (rtlVoiceActors.length > 0) { + voiceActors = rtlVoiceActors + .map((_, actorEl) => ({ + id: $(actorEl).find("a").attr("href")?.split("/").pop() || "", + poster: $(actorEl).find("img").attr("data-src") || "", + name: + $(actorEl).find(".pi-detail .pi-name a").text().trim() || "", + })) + .get(); + } else if (xxVoiceActors.length > 0) { + voiceActors = xxVoiceActors + .map((_, actorEl) => ({ + id: $(actorEl).attr("href")?.split("/").pop() || "", + poster: $(actorEl).find("img").attr("data-src") || "", + name: $(actorEl).attr("title") || "", + })) + .get(); + } + if (voiceActors.length === 0) { + voiceActors = $(el) + .find(".per-info.per-info-xx .pix-list .pi-avatar") + .map((_, actorEl) => ({ + id: $(actorEl).attr("href")?.split("/")[2] || "", + poster: $(actorEl).find("img").attr("data-src") || "", + name: $(actorEl).attr("title") || "", + })) + .get(); + } + + return { character, voiceActors }; + }) + .get(); + return { totalPages, charactersVoiceActors }; + } catch (error) { + console.error("Error in extractVoiceActor:", error); + throw new Error("Could not extract voice actors"); + } +} diff --git a/src/extractors/watchlist.extractor.js b/src/extractors/watchlist.extractor.js new file mode 100644 index 0000000..6a1327a --- /dev/null +++ b/src/extractors/watchlist.extractor.js @@ -0,0 +1,63 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +export default async function extractWatchlist(userId, page = 1) { + try { + const url = `https://${v1_base_url}/community/user/${userId}/watch-list?page=${page}`; + const { data } = await axios.get(url); + const $ = cheerio.load(data); + const watchlist = []; + + const totalPages = + Number( + $('.pre-pagination nav .pagination > .page-item a[title="Last"]') + ?.attr("href") + ?.split("=") + .pop() ?? + $('.pre-pagination nav .pagination > .page-item a[title="Next"]') + ?.attr("href") + ?.split("=") + .pop() ?? + $(".pre-pagination nav .pagination > .page-item.active a") + ?.text() + ?.trim() + ) || 1; + + $(".flw-item").each((index, element) => { + const title = $(".film-name a", element).text().trim(); + const poster = $(".film-poster img", element).attr("data-src"); + const duration = $(".fdi-duration", element).text().trim(); + const type = $(".fdi-item", element).first().text().trim(); + const id = $(".film-poster a", element).attr("data-id"); + const subCount = $(".tick-item.tick-sub", element).text().trim(); + const dubCount = $(".tick-item.tick-dub", element).text().trim(); + const link = $(".film-name a", element).attr("href"); + + const animeId = link.split("/").pop(); + + watchlist.push({ + id: animeId, + title, + poster, + duration, + type, + subCount, + dubCount, + link: `https://${v1_base_url}${link}`, + showType: type, + tvInfo: { + showType: type, + duration: duration, + sub: subCount, + dub: dubCount, + }, + }); + }); + + return { watchlist, totalPages }; + } catch (error) { + console.error("Error fetching watchlist:", error.message); + throw error; + } +} diff --git a/src/helper/cache.helper.js b/src/helper/cache.helper.js new file mode 100644 index 0000000..3260fda --- /dev/null +++ b/src/helper/cache.helper.js @@ -0,0 +1,34 @@ +import axios from "axios"; +import dotenv from "dotenv"; + +dotenv.config(); + +const CACHE_SERVER_URL = process.env.CACHE_URL || null; + +export const getCachedData = async (key) => { + try { + if (!CACHE_SERVER_URL) { + console.log(CACHE_SERVER_URL); + return; + } + const response = await axios.get(`${CACHE_SERVER_URL}/${key}`); + return response.data; + } catch (error) { + if (error.response && error.response.status === 404) { + return null; + } + throw error; + } +}; + +export const setCachedData = async (key, value) => { + try { + if (!CACHE_SERVER_URL) { + return; + } + await axios.post(CACHE_SERVER_URL, { key, value }); + } catch (error) { + console.error("Error setting cache data:", error); + throw error; + } +}; diff --git a/src/helper/countPages.helper.js b/src/helper/countPages.helper.js new file mode 100644 index 0000000..23a75fd --- /dev/null +++ b/src/helper/countPages.helper.js @@ -0,0 +1,24 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { DEFAULT_HEADERS } from "../configs/header.config.js"; + +const axiosInstance = axios.create({ headers: DEFAULT_HEADERS }); + +async function countPages(url) { + try { + const { data } = await axiosInstance.get(url); + const $ = cheerio.load(data); + const lastPageHref = $( + ".tab-content .pagination .page-item:last-child a" + ).attr("href"); + const lastPageNumber = lastPageHref + ? parseInt(lastPageHref.split("=").pop()) + : 1; + return lastPageNumber; + } catch (error) { + console.error("Error counting pages:", error.message); + throw error; + } +} + +export default countPages; diff --git a/src/helper/extractPages.helper.js b/src/helper/extractPages.helper.js new file mode 100644 index 0000000..cb98c50 --- /dev/null +++ b/src/helper/extractPages.helper.js @@ -0,0 +1,92 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; +import { DEFAULT_HEADERS } from "../configs/header.config.js"; + +const axiosInstance = axios.create({ headers: DEFAULT_HEADERS }); + +async function extractPage(page, params) { + try { + const resp = await axiosInstance.get(`https://${v1_base_url}/${params}?page=${page}`); + const $ = cheerio.load(resp.data); + const totalPages = + Number( + $('.pre-pagination nav .pagination > .page-item a[title="Last"]') + ?.attr("href") + ?.split("=") + .pop() ?? + $('.pre-pagination nav .pagination > .page-item a[title="Next"]') + ?.attr("href") + ?.split("=") + .pop() ?? + $(".pre-pagination nav .pagination > .page-item.active a") + ?.text() + ?.trim() + ) || 1; + + const contentSelector = params.includes("az-list") + ? ".tab-content" + : "#main-content"; + const data = await Promise.all( + $(`${contentSelector} .film_list-wrap .flw-item`).map( + async (index, element) => { + const $fdiItems = $(".film-detail .fd-infor .fdi-item", element); + const showType = $fdiItems + .filter((_, item) => { + const text = $(item).text().trim().toLowerCase(); + return ["tv", "ona", "movie", "ova", "special", "music"].some((type) => + text.includes(type) + ); + }) + .first(); + const poster = $(".film-poster>img", element).attr("data-src"); + const title = $(".film-detail .film-name", element).text(); + const japanese_title = $(".film-detail>.film-name>a", element).attr( + "data-jname" + ); + const description = $(".film-detail .description", element) + .text() + .trim(); + const data_id = $(".film-poster>a", element).attr("data-id"); + const id = $(".film-poster>a", element).attr("href").split("/").pop(); + const tvInfo = { + showType: showType ? showType.text().trim() : "Unknown", + duration: $(".film-detail .fd-infor .fdi-duration", element) + .text() + .trim(), + }; + let adultContent = false; + const tickRateText = $(".film-poster>.tick-rate", element) + .text() + .trim(); + if (tickRateText.includes("18+")) { + adultContent = true; + } + + ["sub", "dub", "eps"].forEach((property) => { + const value = $(`.tick .tick-${property}`, element).text().trim(); + if (value) { + tvInfo[property] = value; + } + }); + return { + id, + data_id, + poster, + title, + japanese_title, + description, + tvInfo, + adultContent, + }; + } + ) + ); + return [data, parseInt(totalPages, 10)]; + } catch (error) { + console.error(`Error extracting data from page ${page}:`, error.message); + throw error; + } +} + +export default extractPage; diff --git a/src/helper/fetchScript.helper.js b/src/helper/fetchScript.helper.js new file mode 100644 index 0000000..b6c407e --- /dev/null +++ b/src/helper/fetchScript.helper.js @@ -0,0 +1,8 @@ +import axios from "axios"; + +async function fetchScript(url) { + const response = await axios.get(url); + return response.data; +} + +export default fetchScript; diff --git a/src/helper/foreignInput.helper.js b/src/helper/foreignInput.helper.js new file mode 100644 index 0000000..fe8759c --- /dev/null +++ b/src/helper/foreignInput.helper.js @@ -0,0 +1,76 @@ +import axios from "axios"; +// import { getCachedData, setCachedData } from "./cache.helper"; + +async function getEnglishTitleFromAniList(userInput) { + // const cacheKey = `translation:${userInput}`; + + try { + // Check cache + // const cachedValue = await getCachedData(cacheKey); + // if (cachedValue) { + // console.log(`Cache Hit ${userInput} -> ${cachedValue}`) + // } + + const query = ` + query ($search: String) { + Media (search: $search, type: ANIME) { + title { + romaji + english + } + } + } + `; + + const response = await axios.post('https://graphql.anilist.co', { + query, + variables: { search: userInput } + }, { + headers: { 'Content-Type': 'application/json' }, + timeout: 3000 // 3 seconds + }); + + const titles = response.data?.data?.Media?.title; + + if (!titles) { + console.log(`AniList no match found for: ${userInput}`); + return userInput; + } + + const result = titles.english || titles.romaji || userInput; + + // await setCachedData(cacheKey, result); + + return result; + } catch (error) { + console.error("AniList API Error:", error.response?.data || error.message); + throw error; + } +} + +async function convertForeignLanguage(userInput) { + try { + if (!userInput) return ''; + + // If it's only Latin characters, return as-is + if (/^[a-zA-Z\s]+$/.test(userInput)) { + return userInput; + } + + // Detect if it is Japanese, Chinese or Korean + const isForeign = /[\u3040-\u30ff\u3000-\u303f\u4e00-\u9faf\uac00-\ud7af]/.test(userInput); + + if (isForeign) { + const translated = await getEnglishTitleFromAniList(userInput); + + return translated; + } + + return userInput; + } catch (error) { + console.error(`Error converting foreign input ${userInput}:`, error.message); + return userInput; + } +} + +export default convertForeignLanguage; \ No newline at end of file diff --git a/src/helper/formatTitle.helper.js b/src/helper/formatTitle.helper.js new file mode 100644 index 0000000..723b296 --- /dev/null +++ b/src/helper/formatTitle.helper.js @@ -0,0 +1,9 @@ +function formatTitle(title, data_id) { + let formattedTitle = title.replace(/[^\w\s]/g, ""); + formattedTitle = formattedTitle.toLowerCase(); + formattedTitle = formattedTitle.replace(/\s+/g, "-"); + formattedTitle = `${formattedTitle}-${data_id}`; + return formattedTitle; +} + +export default formatTitle; diff --git a/src/helper/getKey.helper.js b/src/helper/getKey.helper.js new file mode 100644 index 0000000..f57b959 --- /dev/null +++ b/src/helper/getKey.helper.js @@ -0,0 +1,34 @@ +class ErrorLoadingException extends Error { + constructor(message) { + super(message); + this.name = "ErrorLoadingException"; + } +} + +function matchingKey(value, script) { + const regex = new RegExp(`,${value}=((?:0x)?([0-9a-fA-F]+))`); + const match = script.match(regex); + if (match) { + return match[2]; + } else { + throw new ErrorLoadingException("Failed to match the key"); + } +} + +function getKeys(script) { + const regex = + /case\s*0x[0-9a-f]+:(?![^;]*=partKey)\s*\w+\s*=\s*(\w+)\s*,\s*\w+\s*=\s*(\w+);/g; + const matches = script.matchAll(regex); + + return Array.from(matches, (match) => { + const matchKey1 = matchingKey(match[1], script); + const matchKey2 = matchingKey(match[2], script); + try { + return [parseInt(matchKey1, 16), parseInt(matchKey2, 16)]; + } catch (e) { + return []; + } + }).filter((pair) => pair.length > 0); +} + +export default getKeys; \ No newline at end of file diff --git a/src/helper/token.helper.js b/src/helper/token.helper.js new file mode 100644 index 0000000..633616d --- /dev/null +++ b/src/helper/token.helper.js @@ -0,0 +1,71 @@ +import axios from 'axios'; +import * as cheerio from 'cheerio'; +import { v1_base_url } from '../utils/base_v1.js'; + +export default async function extractToken(url) { + try { + const { data: html } = await axios.get(url, { + headers: { + Referer: `https://${v1_base_url}/` + } + }); + + const $ = cheerio.load(html); + const results = {}; + + // 1. Meta tag + const meta = $('meta[name="_gg_fb"]').attr('content'); + if (meta) results.meta = meta; + + // 2. Data attribute + const dpi = $('[data-dpi]').attr('data-dpi'); + if (dpi) results.dataDpi = dpi; + + // 3. Nonce from empty script + const nonceScript = $('script[nonce]').filter((i, el) => { + return $(el).text().includes('empty nonce script'); + }).attr('nonce'); + if (nonceScript) results.nonce = nonceScript; + + // 4. JS string assignment: window. = "value"; + const stringAssignRegex = /window\.(\w+)\s*=\s*["']([\w-]+)["']/g; + const stringMatches = [...html.matchAll(stringAssignRegex)]; + for (const [_, key, value] of stringMatches) { + results[`window.${key}`] = value; + } + + // 5. JS object assignment: window. = { ... }; + const objectAssignRegex = /window\.(\w+)\s*=\s*(\{[\s\S]*?\});/g; + const matches = [...html.matchAll(objectAssignRegex)]; + for (const [_, varName, rawObj] of matches) { + try { + const parsedObj = eval('(' + rawObj + ')'); + if (parsedObj && typeof parsedObj === 'object') { + const stringValues = Object.values(parsedObj).filter(val => typeof val === 'string'); + const concatenated = stringValues.join(''); + if (concatenated.length >= 20) { + results[`window.${varName}`] = concatenated; + } + } + } catch (e) { + // Skip invalid object + } + } + + // 6. HTML comment: + $('*').contents().each(function () { + if (this.type === 'comment') { + const match = this.data.trim().match(/^_is_th:([\w-]+)$/); + if (match) { + results.commentToken = match[1].trim(); + } + } + }); + + const token = Object.values(results)[0]; + return token || null; + } catch (err) { + console.error('Error:', err.message); + return null; + } +} \ No newline at end of file diff --git a/src/parsers/aniplay.parser.js b/src/parsers/aniplay.parser.js new file mode 100644 index 0000000..d042371 --- /dev/null +++ b/src/parsers/aniplay.parser.js @@ -0,0 +1,94 @@ +import axios from "axios"; +import { v3_base_url } from "../utils/base_v3.js"; + +const DEFAULT_BASE_URL = `https://${v3_base_url}`; + +class AniplayExtractor { + constructor(baseUrl = DEFAULT_BASE_URL) { + this.baseUrl = baseUrl; + this.keys = null; + this.keysTs = 0; + } + + isCacheValid() { + const now = Math.floor(Date.now() / 1000); + return this.keys && now - this.keysTs < 3600; + } + + async fetchHtml(url) { + const { data } = await axios.get(url); + return data; + } + + async fetchStaticJsUrl() { + const html = await this.fetchHtml(`${this.baseUrl}/anime/watch/1`); + const prefix = "/_next/static/chunks/app/(user)/(media)/"; + const start = html.indexOf(prefix); + if (start === -1) throw new Error("Static chunk path not found in HTML."); + + const slugStart = start + prefix.length; + const slugEnd = html.indexOf('"', slugStart); + const jsSlug = html.slice(slugStart, slugEnd); + return `${this.baseUrl}${prefix}${jsSlug}`; + } + + async extractKeys() { + if (this.isCacheValid()) return this.keys; + + const scriptUrl = await this.fetchStaticJsUrl(); + const script = await this.fetchHtml(scriptUrl); + + const regex = + /\(0,\w+\.createServerReference\)\("([a-f0-9]+)",\w+\.callServer,void 0,\w+\.findSourceMapURL,"(getSources|getEpisodes)"\)/g; + + const matches = script.matchAll(regex); + const keysMap = { baseUrl: this.baseUrl }; + + for (const match of matches) { + const [, hash, fn] = match; + keysMap[fn] = hash; + } + + if (!keysMap["getSources"] || !keysMap["getEpisodes"]) { + throw new Error("Could not extract all required keys."); + } + + this.keys = keysMap; + this.keysTs = Math.floor(Date.now() / 1000); + return keysMap; + } + + async getNextAction() { + const keys = await this.extractKeys(); + return { + watch: keys["getSources"], + info: keys["getEpisodes"], + }; + } + + async fetchEpisode(animeId, ep, host = "hika", type = "sub") { + const nextAction = await this.getNextAction(); + const url = `${this.baseUrl}/anime/watch/${animeId}?host=${host}&ep=${ep}&type=${type}`; + const payload = [ + String(animeId), + host, + `${animeId}/${ep}`, + String(ep), + type, + ]; + + try { + const res = await axios.post(url, payload, { + headers: { + "Next-Action": nextAction.watch, + }, + }); + const dataStr = res.data.split("1:")[1]; + return JSON.parse(dataStr); + } catch (err) { + throw new Error(`Request failed: ${err.message}`); + } + } +} + +export default AniplayExtractor; diff --git a/src/parsers/decryptors/decodedpng.js b/src/parsers/decryptors/decodedpng.js new file mode 100644 index 0000000..5e783d9 --- /dev/null +++ b/src/parsers/decryptors/decodedpng.js @@ -0,0 +1,844 @@ +export const data = new Uint8ClampedArray([ + 246, 246, 246, 255, 226, 234, 236, 255, 113, 170, 187, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 0, + 255, 255, 1, 61, 139, 163, 192, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 130, 180, 196, 254, 242, 243, 244, 254, 246, 246, + 246, 254, 243, 244, 245, 254, 105, 165, 184, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, + 164, 255, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, + 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, + 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 132, 181, + 196, 254, 243, 245, 245, 254, 188, 212, 220, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 255, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, + 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 139, + 164, 254, 63, 142, 165, 254, 217, 230, 233, 254, 132, 181, 196, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 255, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 255, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 177, 206, 216, 254, 119, 174, + 190, 255, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 164, 198, + 210, 255, 119, 174, 190, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, 164, 254, 60, 140, + 164, 255, 60, 140, 165, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, + 165, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, + 165, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, 164, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, 164, 254, 60, 140, + 164, 254, 60, 140, 165, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, + 165, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, + 165, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, + 164, 254, 60, 140, 164, 254, 60, 140, 165, 254, 60, 140, 164, 254, 60, 140, + 164, 254, 60, 140, 165, 254, 60, 140, 164, 254, 60, 140, 164, 254, 60, 140, + 164, 255, 163, 198, 210, 254, 119, 174, 190, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 255, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 255, 60, 139, 164, 254, 163, 198, 210, 254, 119, 174, 190, 254, 60, 139, + 164, 254, 60, 140, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, + 164, 255, 60, 140, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 139, 164, 254, 60, 140, + 164, 255, 60, 139, 164, 254, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, + 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, + 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, + 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 174, + 190, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 131, 180, + 195, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, + 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, + 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, + 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, + 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, + 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, + 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, + 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, + 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, 239, 240, 255, 233, + 239, 240, 255, 233, 239, 240, 255, 218, 230, 234, 255, 143, 187, 200, 255, 66, + 143, 167, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, + 140, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, + 140, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, + 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 173, 190, 255, 60, + 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 139, 185, 199, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 223, 233, 236, + 255, 136, 183, 197, 255, 69, 145, 168, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 140, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, + 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, 184, 199, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 242, 243, 244, 255, 219, 231, 235, 255, 217, 229, 233, 255, + 217, 229, 233, 255, 217, 229, 233, 255, 217, 229, 233, 255, 217, 229, 233, + 255, 217, 229, 233, 255, 217, 229, 233, 255, 217, 229, 233, 255, 217, 229, + 233, 255, 217, 229, 233, 255, 217, 229, 233, 255, 217, 229, 233, 255, 217, + 229, 233, 255, 217, 229, 233, 255, 97, 160, 180, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 139, 185, 199, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 173, 190, + 255, 60, 140, 164, 255, 60, 140, 165, 255, 60, 140, 164, 255, 139, 185, 199, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, + 174, 190, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, + 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, + 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 163, 198, + 210, 255, 119, 174, 190, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, + 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, + 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 140, 164, 255, 60, 140, 164, + 255, 60, 139, 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 140, 164, 255, 163, 198, 210, 255, 119, 173, 190, 255, 60, 139, 164, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 140, 164, 255, 60, 139, 164, + 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, 184, 199, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 139, 185, 199, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 173, 190, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, 185, 199, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 163, 198, 210, 255, 119, + 174, 190, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, + 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 241, 243, 244, 255, 227, 235, 238, 255, 243, 245, 245, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 230, 237, 239, 255, 239, 242, 243, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 233, 239, 241, 255, 235, 239, 241, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 239, 242, 243, 255, 230, 236, 239, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, + 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 163, 198, + 210, 255, 119, 174, 190, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, + 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 216, 230, 233, 255, 94, 160, 179, 255, 66, 143, 166, 255, 99, 161, 182, + 255, 221, 232, 236, 255, 246, 246, 246, 255, 245, 245, 245, 255, 127, 178, + 194, 255, 68, 144, 168, 255, 79, 150, 173, 255, 187, 213, 220, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 154, 193, 205, 255, 73, 147, 170, 255, 72, 146, 169, + 255, 156, 194, 206, 255, 246, 246, 246, 255, 246, 246, 246, 255, 208, 223, + 229, 255, 85, 154, 176, 255, 65, 143, 166, 255, 112, 169, 187, 255, 236, 240, + 242, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 163, 198, 210, + 255, 119, 174, 190, 255, 60, 140, 164, 255, 60, 140, 165, 255, 60, 140, 164, + 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 137, 184, 197, 255, 60, 140, 165, 255, 60, 140, 164, 255, 60, 140, 164, 255, + 88, 156, 177, 255, 210, 226, 230, 255, 133, 181, 196, 255, 60, 140, 164, 255, + 60, 140, 165, 255, 60, 140, 164, 255, 85, 154, 175, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 239, + 242, 243, 255, 64, 143, 166, 255, 60, 140, 165, 255, 60, 140, 164, 255, 60, + 140, 164, 255, 146, 189, 202, 255, 203, 221, 227, 255, 70, 145, 168, 255, 60, + 140, 164, 255, 60, 140, 165, 255, 60, 140, 164, 255, 165, 200, 210, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 140, 164, + 255, 60, 140, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 173, 190, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, 185, 199, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 137, 183, 198, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 67, + 144, 167, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 86, 154, 175, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 237, 241, 242, 255, 64, 142, 166, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, + 255, 64, 141, 166, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 164, 200, 211, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 102, 163, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 139, 184, 199, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 218, 230, 234, 255, 83, 153, 174, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 67, 144, 167, 255, 174, 206, 215, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 153, 193, 205, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 101, 163, 182, 255, + 226, 235, 238, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, + 164, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 163, + 198, 210, 255, 119, 174, 190, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, + 139, 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 213, 227, 231, 255, 85, 154, 175, 255, 60, 140, + 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, + 164, 255, 64, 142, 165, 255, 178, 208, 216, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 242, 243, 244, 255, 148, 189, 203, 255, 60, 139, 164, + 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 140, 164, 255, 97, 160, 180, 255, 231, 238, 240, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, + 173, 190, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, + 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 244, 245, 245, 255, 112, 169, 187, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 67, 143, + 167, 255, 229, 236, 239, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 200, 220, 226, 255, 61, 141, 165, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 140, 186, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 163, 198, 210, 255, 119, 174, 190, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, 185, 199, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 235, 240, 242, 255, + 133, 181, 196, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 86, 155, 176, 255, 221, 232, 236, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 203, + 221, 227, 255, 70, 145, 168, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 150, 191, 204, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 140, + 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 139, 185, 199, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 165, 199, 210, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 115, 171, 188, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 245, 245, 245, 255, 83, 153, 174, 255, 60, + 140, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 61, + 141, 165, 255, 192, 215, 222, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, + 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 140, 164, 255, 60, 140, + 165, 255, 60, 140, 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 131, 181, 196, 255, 60, 140, 165, 255, 60, 140, 164, 255, + 60, 140, 164, 255, 67, 143, 168, 255, 140, 185, 200, 255, 83, 153, 174, 255, + 60, 140, 164, 255, 60, 140, 165, 255, 60, 140, 164, 255, 79, 151, 173, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 231, 238, 240, 255, 60, 140, 164, 255, 60, 140, 165, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 89, 156, 177, 255, 124, 176, 192, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 140, 165, 255, 60, 140, 164, 255, 158, + 195, 207, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, + 183, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 163, 198, + 210, 255, 119, 173, 190, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 181, 208, 217, 255, 68, 144, 167, 255, 60, 139, 164, 255, 64, 141, 166, + 255, 174, 205, 214, 255, 245, 246, 246, 255, 209, 224, 229, 255, 81, 151, 173, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 129, 179, 194, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 245, 246, 246, 255, 104, 165, 184, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 96, 160, 180, 255, 231, 237, 239, 255, 242, 244, 244, 255, 140, 185, 200, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 68, 144, 168, 255, 205, 222, 228, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 163, 183, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 163, 198, 210, 255, 119, 174, + 190, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, 185, + 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 192, 215, 222, 255, 155, 194, 206, 255, 194, 216, 223, 255, 245, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 220, 231, 235, 255, 160, + 197, 208, 255, 177, 207, 215, 255, 238, 242, 242, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 227, 236, 237, 255, 169, 202, 212, 255, 169, 202, 212, 255, + 226, 234, 237, 255, 246, 246, 246, 255, 246, 246, 246, 255, 242, 244, 244, + 255, 183, 210, 219, 255, 155, 194, 206, 255, 205, 222, 228, 255, 245, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 163, 198, 210, + 255, 119, 174, 190, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, + 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 102, 164, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, + 163, 198, 210, 255, 119, 173, 190, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 139, 164, 255, 139, 184, 199, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 140, 164, 255, + 60, 140, 164, 255, 60, 139, 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 215, 229, 233, 255, 222, 233, 236, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 223, 233, 236, 255, 213, 227, 232, 255, 245, 245, 245, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 140, + 164, 255, 60, 140, 165, 255, 60, 140, 164, 255, 139, 185, 199, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 242, 244, 244, 255, 111, 169, 186, 255, 60, 140, 165, 255, 64, 142, + 166, 255, 132, 181, 196, 255, 216, 229, 233, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 222, 233, 236, 255, 164, 199, 210, 255, 97, 161, 180, 255, 73, + 147, 170, 255, 106, 166, 184, 255, 178, 207, 216, 255, 235, 239, 242, 255, + 246, 246, 246, 255, 241, 243, 244, 255, 201, 221, 226, 255, 117, 172, 189, + 255, 61, 141, 165, 255, 61, 140, 164, 255, 138, 185, 198, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 140, 164, 255, 60, 140, 164, + 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 173, 190, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, 185, 199, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 196, 217, 224, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 74, 147, 171, 255, 132, 181, 195, 255, 150, 191, 204, + 255, 77, 149, 172, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 95, 160, 180, 255, 158, 196, 207, + 255, 120, 173, 191, 255, 61, 140, 165, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 63, 141, 165, 255, 230, 237, 239, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 102, 163, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 202, 221, 226, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 67, 144, 167, 255, 236, 241, 242, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, + 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 163, 198, 210, + 255, 119, 174, 190, 255, 60, 140, 164, 255, 60, 140, 165, 255, 60, 140, 164, + 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 136, 183, + 198, 255, 64, 142, 166, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 140, + 165, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 140, + 165, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 140, 165, 255, 60, 140, + 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 140, 165, 255, 60, 140, + 164, 255, 60, 140, 164, 255, 60, 140, 165, 255, 71, 145, 168, 255, 153, 192, + 205, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 140, + 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 173, + 190, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, 185, + 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 206, + 224, 229, 255, 118, 172, 190, 255, 64, 141, 165, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 95, + 160, 180, 255, 143, 187, 200, 255, 87, 155, 176, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 63, + 141, 165, 255, 134, 181, 196, 255, 225, 235, 237, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 102, 163, 183, 255, 60, 139, 164, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, + 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, 184, 199, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 212, 227, 231, 255, 129, 179, 194, 255, 89, 157, 177, + 255, 85, 154, 175, 255, 111, 169, 187, 255, 183, 210, 219, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 243, 245, 245, 255, 172, 203, 213, 255, 106, 165, + 184, 255, 83, 153, 174, 255, 93, 159, 179, 255, 142, 187, 201, 255, 221, 231, + 235, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 163, 198, 210, 255, 119, 174, 190, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 139, 185, 199, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 244, 245, 246, + 255, 244, 245, 245, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 243, 244, 245, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, 139, 164, + 255, 60, 139, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, 174, 190, + 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 139, 185, 199, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 164, 183, 255, 60, + 140, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 163, 198, 210, 255, 119, + 174, 190, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 139, + 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 102, 163, + 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 163, 198, + 210, 255, 119, 174, 190, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, + 164, 255, 139, 185, 199, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, + 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, + 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, + 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, + 255, 102, 164, 183, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, + 255, 163, 198, 210, 255, 129, 180, 195, 255, 60, 140, 164, 255, 60, 140, 165, + 255, 60, 140, 164, 255, 93, 158, 178, 255, 158, 196, 207, 255, 159, 197, 207, + 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, + 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, + 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, + 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, + 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, + 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, + 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, + 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, + 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, + 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, + 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, + 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, + 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, + 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 159, 197, 207, 255, 156, + 194, 207, 255, 69, 144, 168, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, + 140, 164, 255, 169, 202, 212, 255, 176, 206, 215, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 203, 221, 227, 255, 234, 239, 240, 255, 84, + 153, 175, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 107, 167, 185, 255, 241, 243, 244, 255, 246, + 246, 246, 255, 203, 221, 227, 255, 85, 154, 176, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, + 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, + 140, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, + 139, 164, 255, 60, 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, + 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, + 139, 164, 255, 60, 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, + 140, 164, 255, 60, 140, 164, 255, 60, 139, 164, 255, 60, 139, 164, 255, 60, + 140, 164, 255, 60, 139, 164, 255, 92, 157, 179, 255, 222, 232, 236, 255, 246, + 246, 246, 255, +]); diff --git a/src/parsers/decryptors/decryptAllServers.decryptor.js b/src/parsers/decryptors/decryptAllServers.decryptor.js new file mode 100644 index 0000000..f1e6ab3 --- /dev/null +++ b/src/parsers/decryptors/decryptAllServers.decryptor.js @@ -0,0 +1,22 @@ +// this is kept as backup in case megacloud's architecture rollback to it's previous architecture + + +// import decryptMegacloud from "./megacloud.decryptor.js"; + +// export async function decryptAllServers(data) { +// const promises = data.map(async (server) => { +// try { +// if ( +// server.type === "sub" || +// server.type === "dub" || +// server.type === "raw" +// ) { +// return await decryptMegacloud(server.id, server.name, server.type); +// } +// } catch (error) { +// console.error(`Error decrypting server ${server.id}:`, error); +// return null; +// } +// }); +// return Promise.all(promises); +// } diff --git a/src/parsers/decryptors/decrypt_v1.decryptor.js b/src/parsers/decryptors/decrypt_v1.decryptor.js new file mode 100644 index 0000000..6721c37 --- /dev/null +++ b/src/parsers/decryptors/decrypt_v1.decryptor.js @@ -0,0 +1,152 @@ +import axios from "axios"; +import CryptoJS from "crypto-js"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../../utils/base_v1.js"; +import { v4_base_url } from "../../utils/base_v4.js"; +import { fallback_1, fallback_2 } from "../../utils/fallback.js"; + +function fetch_key(data) { + let key = null; + + const xyMatch = data.match(/window\._xy_ws\s*=\s*["']([^"']+)["']/); + if (xyMatch) { + key = xyMatch[1]; + } + + if (!key) { + const lkMatch = data.match(/window\._lk_db\s*=\s*\{([^}]+)\}/); + + if (lkMatch) { + key = [...lkMatch[1].matchAll(/:\s*["']([^"']+)["']/g)] + .map((v) => v[1]) + .join(""); + } + } + + if (!key) { + const nonceMatch = data.match(/nonce\s*=\s*["']([^"']+)["']/); + if (nonceMatch) { + key = nonceMatch[1]; + } + } + + if (!key) { + const dpiMatch = data.match(/data-dpi\s*=\s*["']([^"']+)["']/); + if (dpiMatch) { + key = dpiMatch[1]; + } + } + + if (!key) { + const metaMatch = data.match( + /]*name\s*=\s*["']_gg_fb["'][^>]*content\s*=\s*["']([^"']+)["']/i, + ); + if (metaMatch) { + key = metaMatch[1]; + } + } + + if (!key) { + const isThMatch = data.match(/_is_th\s*:\s*([A-Za-z0-9]+)/); + if (isThMatch) { + key = isThMatch[1]; + } + } + + return key; +} + +export async function decryptSources_v1(epID, id, name, type, fallback) { + try { + let decryptedSources = null; + let iframeURL = null; + + if (fallback) { + const fallback_server = ["hd-1", "hd-3"].includes(name.toLowerCase()) + ? fallback_1 + : fallback_2; + + iframeURL = `https://${fallback_server}/stream/s-2/${epID}/${type}`; + + const { data } = await axios.get( + `https://${fallback_server}/stream/s-2/${epID}/${type}`, + { + headers: { + Referer: `https://${fallback_server}/`, + }, + }, + ); + + const $ = cheerio.load(data); + const dataId = $("#megaplay-player").attr("data-id"); + const { data: decryptedData } = await axios.get( + `https://${fallback_server}/stream/getSources?id=${dataId}`, + { + headers: { + "X-Requested-With": "XMLHttpRequest", + }, + }, + ); + decryptedSources = decryptedData; + } else { + const { data: sourcesData } = await axios.get( + `https://${v1_base_url}/ajax/v2/episode/sources?id=${id}`, + ); + + const ajaxLink = sourcesData?.link; + if (!ajaxLink) throw new Error("Missing link in sourcesData"); + console.log(ajaxLink); + const sourceIdMatch = /\/([^/?]+)\?/.exec(ajaxLink); + const sourceId = sourceIdMatch?.[1]; + if (!sourceId) throw new Error("Unable to extract sourceId from link"); + const new_url = `https://megacloud.blog/embed-2/v3/e-1/${sourceId}?k=1`; + const { data: stream_data } = await axios.post( + "https://megacloud.zenime.site/get-sources", + { + embedUrl: new_url, + }, + { + headers: { + "Content-Type": "application/json", + }, + }, + ); + + decryptedSources = stream_data; + // const baseUrlMatch = ajaxLink.match( + // /^(https?:\/\/[^\/]+(?:\/[^\/]+){3})/, + // ); + // if (!baseUrlMatch) throw new Error("Could not extract base URL"); + // const baseUrl = baseUrlMatch[1]; + + // iframeURL = `${baseUrl}/${sourceId}?k=1&autoPlay=0&oa=0&asi=1`; + + // const { data: rawSourceData } = await axios.get( + // `${baseUrl}/getSources?id=${sourceId}`, + // ); + // decryptedSources = rawSourceData; + } + + return { + id, + type, + link: { + file: fallback + ? (decryptedSources?.sources?.file ?? "") + : (decryptedSources?.sources?.[0].file ?? ""), + type: "hls", + }, + tracks: decryptedSources.tracks ?? [], + intro: decryptedSources.intro ?? null, + outro: decryptedSources.outro ?? null, + iframe: iframeURL, + server: name, + }; + } catch (error) { + console.error( + `Error during decryptSources_v1(${id}, epID=${epID}, server=${name}):`, + error.message, + ); + return null; + } +} diff --git a/src/parsers/decryptors/decrypt_v2.decryptor.js b/src/parsers/decryptors/decrypt_v2.decryptor.js new file mode 100644 index 0000000..1e347aa --- /dev/null +++ b/src/parsers/decryptors/decrypt_v2.decryptor.js @@ -0,0 +1,68 @@ +// this is kept as backup in case megacloud's architecture rollback to it's previous architecture + + +// import axios from "axios"; +// import CryptoJS from "crypto-js"; +// import { v2_base_url } from "../../utils/base_v2.js"; +// import fetchScript from "../../helper/fetchScript.helper.js"; +// import getKeys from "../../helper/getKey.helper.js"; +// import { PLAYER_SCRIPT_URL } from "../../configs/player_v2.config.js"; + +// async function decryptSources_v2(id, name, type) { +// try { +// const [{ data: sourcesData }, decryptKey_v2] = await Promise.all([ +// axios.get(`https://${v2_base_url}/ajax/episode/sources?id=${id}`), +// getKeys(await fetchScript(PLAYER_SCRIPT_URL)), +// ]); +// const ajaxResp = sourcesData.link; +// const [hostname] = /^(https?:\/\/(?:www\.)?[^\/\?]+)/.exec(ajaxResp) || []; +// const [_, sourceId] = /\/([^\/\?]+)\?/.exec(ajaxResp) || []; +// const { data: source } = await axios.get( +// `${hostname}/ajax/embed-6-v2/getSources?id=${sourceId}` +// ); + +// if (source.encrypted === true) { +// const sourcesArray = source.sources.split(""); +// let extractedKey = ""; +// let currentIndex = 0; + +// for (const index of decryptKey_v2) { +// const start = index[0] + currentIndex; +// const end = start + index[1]; + +// for (let i = start; i < end; i++) { +// extractedKey += sourcesArray[i]; +// sourcesArray[i] = ""; +// } +// currentIndex += index[1]; +// } + +// const decrypted = CryptoJS.AES.decrypt( +// sourcesArray.join(""), +// extractedKey +// ).toString(CryptoJS.enc.Utf8); +// const decryptedSources = JSON.parse(decrypted); +// source.sources = null; +// source.sources = { +// file: decryptedSources[0].file, +// type: "hls", +// }; +// } +// if (source.hasOwnProperty("server")) { +// delete source.server; +// } +// return { +// id: id, +// type: type, +// link: source.sources, +// tracks: source.tracks, +// intro: source.intro, +// outro: source.outro, +// server: name, +// }; +// } catch (error) { +// console.error("Error during decryption:", error); +// } +// } + +// export { decryptSources_v2 }; diff --git a/src/parsers/decryptors/megacloud.decryptor.js b/src/parsers/decryptors/megacloud.decryptor.js new file mode 100644 index 0000000..54385ce --- /dev/null +++ b/src/parsers/decryptors/megacloud.decryptor.js @@ -0,0 +1,811 @@ +//inspired from https://github.com/drblgn/rabbit_wasm + +import util from "util"; +import pixels from "image-pixels"; +import cryptoJs from "crypto-js"; +import axios from "axios"; +const user_agent = + "Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0"; +import { webcrypto } from "crypto"; +const crypto = webcrypto; +import { dataURL } from "../../configs/dataUrl.js"; +// import { v1_base_url } from "../../utils/base_v1.js"; +import { v4_base_url } from "../../utils/base_v4.js"; + +let wasm; +let arr = new Array(128).fill(void 0); +const dateNow = Date.now(); +let content; +let referrer = ""; + +function isDetached(buffer) { + if (buffer.byteLength === 0) { + const formatted = util.format(buffer); + return formatted.includes("detached"); + } + return false; +} + +const meta = { + content: content, +}; + +const image_data = { + height: 50, + width: 65, + data: new Uint8ClampedArray(), +}; + +const canvas = { + baseUrl: "", + width: 0, + height: 0, + style: { + style: { + display: "inline", + }, + }, + context2d: {}, +}; + +const fake_window = { + localStorage: { + setItem: function (item, value) { + fake_window.localStorage[item] = value; + }, + }, + navigator: { + webdriver: false, + userAgent: user_agent, + }, + length: 0, + document: { + cookie: "", + }, + origin: "", + location: { + href: "", + origin: "", + }, + performance: { + timeOrigin: dateNow, + }, + xrax: "", + c: false, + G: "", + z: function (a) { + return [ + (4278190080 & a) >> 24, + (16711680 & a) >> 16, + (65280 & a) >> 8, + 255 & a, + ]; + }, + crypto: crypto, + msCrypto: crypto, + browser_version: 1676800512, +}; + +const nodeList = { + image: { + src: "", + height: 50, + width: 65, + complete: true, + }, + context2d: {}, + length: 1, +}; + +function get(index) { + return arr[index]; +} + +arr.push(void 0, null, true, false); + +let size = 0; +let memoryBuff; + +function getMemBuff() { + return (memoryBuff = + null !== memoryBuff && 0 !== memoryBuff.byteLength + ? memoryBuff + : new Uint8Array(wasm.memory.buffer)); +} + +const encoder = new TextEncoder(); +const encode = function (text, array) { + return encoder.encodeInto(text, array); +}; + +function parse(text, func, func2) { + if (void 0 === func2) { + var encoded = encoder.encode(text); + const parsedIndex = func(encoded.length, 1) >>> 0; + return ( + getMemBuff() + .subarray(parsedIndex, parsedIndex + encoded.length) + .set(encoded), + (size = encoded.length), + parsedIndex + ); + } + let len = text.length; + let parsedLen = func(len, 1) >>> 0; + var new_arr = getMemBuff(); + let i = 0; + for (; i < len; i++) { + var char = text.charCodeAt(i); + if (127 < char) { + break; + } + new_arr[parsedLen + i] = char; + } + return ( + i !== len && + (0 !== i && (text = text.slice(i)), + (parsedLen = func2(parsedLen, len, (len = i + 3 * text.length), 1) >>> 0), + (encoded = getMemBuff().subarray(parsedLen + i, parsedLen + len)), + (i += encode(text, encoded).written), + (parsedLen = func2(parsedLen, len, i, 1) >>> 0)), + (size = i), + parsedLen + ); +} + +let dataView; + +function isNull(test) { + return null == test; +} + +function getDataView() { + return (dataView = + dataView === null || + isDetached(dataView.buffer) || + dataView.buffer !== wasm.memory.buffer + ? new DataView(wasm.memory.buffer) + : dataView); +} + +let pointer = arr.length; + +function shift(QP) { + QP < 132 || ((arr[QP] = pointer), (pointer = QP)); +} + +function shiftGet(QP) { + var Qn = get(QP); + return shift(QP), Qn; +} + +const decoder = new TextDecoder("utf-8", { + fatal: true, + ignoreBOM: true, +}); + +function decodeSub(index, offset) { + return ( + (index >>>= 0), decoder.decode(getMemBuff().subarray(index, index + offset)) + ); +} + +function addToStack(item) { + pointer === arr.length && arr.push(arr.length + 1); + var Qn = pointer; + return (pointer = arr[Qn]), (arr[Qn] = item), Qn; +} + +function args(QP, Qn, QT, func) { + const Qx = { + a: QP, + b: Qn, + cnt: 1, + dtor: QT, + }; + return ( + (QP = (...Qw) => { + Qx.cnt++; + try { + return func(Qx.a, Qx.b, ...Qw); + } finally { + 0 == --Qx.cnt && + (wasm.__wbindgen_export_2.get(Qx.dtor)(Qx.a, Qx.b), (Qx.a = 0)); + } + }), + ((QP.original = Qx), QP) + ); +} + +function export3(QP, Qn) { + return shiftGet(wasm.__wbindgen_export_3(QP, Qn)); +} + +function export4(Qy, QO, QX) { + wasm.__wbindgen_export_4(Qy, QO, addToStack(QX)); +} + +function export5(QP, Qn) { + wasm.__wbindgen_export_5(QP, Qn); +} + +function applyToWindow(func, args) { + try { + return func.apply(fake_window, args); + } catch (error) { + wasm.__wbindgen_export_6(addToStack(error)); + } +} + +function Qj(QP, Qn) { + return ( + (Qn = Qn(+QP.length, 1) >>> 0), + (getMemBuff().set(QP, Qn), (size = QP.length), Qn) + ); +} + +async function QN(QP, Qn) { + let QT, Qt; + return "function" == typeof Response && QP instanceof Response + ? ((QT = await QP.arrayBuffer()), + (Qt = await WebAssembly.instantiate(QT, Qn)), + Object.assign(Qt, { bytes: QT })) + : (Qt = await WebAssembly.instantiate(QP, Qn)) instanceof + WebAssembly.Instance + ? { + instance: Qt, + module: QP, + } + : Qt; +} + +function initWasm() { + const wasmObj = { + wbg: { + __wbindgen_is_function: function (index) { + return typeof get(index) == "function"; + }, + __wbindgen_is_string: function (index) { + return typeof get(index) == "string"; + }, + __wbindgen_is_object: function (index) { + let object = get(index); + return typeof object == "object" && object !== null; + }, + __wbindgen_number_get: function (offset, index) { + let number = get(index); + getDataView().setFloat64(offset + 8, isNull(number) ? 0 : number, true); + getDataView().setInt32(offset, isNull(number) ? 0 : 1, true); + }, + __wbindgen_string_get: function (offset, index) { + let str = get(index); + let val = parse( + str, + wasm.__wbindgen_export_0, + wasm.__wbindgen_export_1 + ); + getDataView().setInt32(offset + 4, size, true); + getDataView().setInt32(offset, val, true); + }, + __wbindgen_object_drop_ref: function (index) { + shiftGet(index); + }, + __wbindgen_cb_drop: function (index) { + let org = shiftGet(index).original; + return 1 == org.cnt-- && !(org.a = 0); + }, + __wbindgen_string_new: function (index, offset) { + return addToStack(decodeSub(index, offset)); + }, + __wbindgen_is_null: function (index) { + return null === get(index); + }, + __wbindgen_is_undefined: function (index) { + return void 0 === get(index); + }, + __wbindgen_boolean_get: function (index) { + let bool = get(index); + return "boolean" == typeof bool ? (bool ? 1 : 0) : 2; + }, + __wbg_instanceof_CanvasRenderingContext2d_4ec30ddd3f29f8f9: function () { + return true; + }, + __wbg_subarray_adc418253d76e2f1: function (index, num1, num2) { + return addToStack(get(index).subarray(num1 >>> 0, num2 >>> 0)); + }, + __wbg_randomFillSync_5c9c955aa56b6049: function () {}, + __wbg_getRandomValues_3aa56aa6edec874c: function () { + return applyToWindow(function (index1, index2) { + get(index1).getRandomValues(get(index2)); + }, arguments); + }, + __wbg_msCrypto_eb05e62b530a1508: function (index) { + return addToStack(get(index).msCrypto); + }, + __wbg_toString_6eb7c1f755c00453: function (index) { + let fakestr = "[object Storage]"; + return addToStack(fakestr); + }, + __wbg_toString_139023ab33acec36: function (index) { + return addToStack(get(index).toString()); + }, + __wbg_require_cca90b1a94a0255b: function () { + return applyToWindow(function () { + return addToStack(module.require); + }, arguments); + }, + __wbg_crypto_1d1f22824a6a080c: function (index) { + return addToStack(get(index).crypto); + }, + __wbg_process_4a72847cc503995b: function (index) { + return addToStack(get(index).process); + }, + __wbg_versions_f686565e586dd935: function (index) { + return addToStack(get(index).versions); + }, + __wbg_node_104a2ff8d6ea03a2: function (index) { + return addToStack(get(index).node); + }, + __wbg_localStorage_3d538af21ea07fcc: function () { + return applyToWindow(function (index) { + let data = fake_window.localStorage; + if (isNull(data)) { + return 0; + } else { + return addToStack(data); + } + }, arguments); + }, + __wbg_setfillStyle_59f426135f52910f: function () {}, + __wbg_setshadowBlur_229c56539d02f401: function () {}, + __wbg_setshadowColor_340d5290cdc4ae9d: function () {}, + __wbg_setfont_16d6e31e06a420a5: function () {}, + __wbg_settextBaseline_c3266d3bd4a6695c: function () {}, + __wbg_drawImage_cb13768a1bdc04bd: function () {}, + __wbg_getImageData_66269d289f37d3c7: function () { + return applyToWindow(function () { + return addToStack(image_data); + }, arguments); + }, + __wbg_rect_2fa1df87ef638738: function () {}, + __wbg_fillRect_4dd28e628381d240: function () {}, + __wbg_fillText_07e5da9e41652f20: function () {}, + __wbg_setProperty_5144ddce66bbde41: function () {}, + __wbg_createElement_03cf347ddad1c8c0: function () { + return applyToWindow(function (index, decodeIndex, decodeIndexOffset) { + return addToStack(canvas); + }, arguments); + }, + __wbg_querySelector_118a0639aa1f51cd: function () { + return applyToWindow(function (index, decodeIndex, decodeOffset) { + return addToStack(meta); + }, arguments); + }, + __wbg_querySelectorAll_50c79cd4f7573825: function () { + return applyToWindow(function () { + return addToStack(nodeList); + }, arguments); + }, + __wbg_getAttribute_706ae88bd37410fa: function ( + offset, + index, + decodeIndex, + decodeOffset + ) { + let attr = meta.content; + let todo = isNull(attr) + ? 0 + : parse(attr, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1); + getDataView().setInt32(offset + 4, size, true); + getDataView().setInt32(offset, todo, true); + }, + __wbg_target_6795373f170fd786: function (index) { + let target = get(index).target; + return isNull(target) ? 0 : addToStack(target); + }, + __wbg_addEventListener_f984e99465a6a7f4: function () {}, + __wbg_instanceof_HtmlCanvasElement_1e81f71f630e46bc: function () { + return true; + }, + __wbg_setwidth_233645b297bb3318: function (index, set) { + get(index).width = set >>> 0; + }, + __wbg_setheight_fcb491cf54e3527c: function (index, set) { + get(index).height = set >>> 0; + }, + __wbg_getContext_dfc91ab0837db1d1: function () { + return applyToWindow(function (index) { + return addToStack(get(index).context2d); + }, arguments); + }, + __wbg_toDataURL_97b108dd1a4b7454: function () { + return applyToWindow(function (offset, index) { + let _dataUrl = parse( + dataURL, + wasm.__wbindgen_export_0, + wasm.__wbindgen_export_1 + ); + getDataView().setInt32(offset + 4, size, true); + getDataView().setInt32(offset, _dataUrl, true); + }, arguments); + }, + __wbg_instanceof_HtmlDocument_1100f8a983ca79f9: function () { + return true; + }, + __wbg_style_ca229e3326b3c3fb: function (index) { + addToStack(get(index).style); + }, + __wbg_instanceof_HtmlImageElement_9c82d4e3651a8533: function () { + return true; + }, + __wbg_src_87a0e38af6229364: function (offset, index) { + let _src = parse( + get(index).src, + wasm.__wbindgen_export_0, + wasm.__wbindgen_export_1 + ); + getDataView().setInt32(offset + 4, size, true); + getDataView().setInt32(offset, _src, true); + }, + __wbg_width_e1a38bdd483e1283: function (index) { + return get(index).width; + }, + __wbg_height_e4cc2294187313c9: function (index) { + return get(index).height; + }, + __wbg_complete_1162c2697406af11: function (index) { + return get(index).complete; + }, + __wbg_data_d34dc554f90b8652: function (offset, index) { + var _data = Qj(get(index).data, wasm.__wbindgen_export_0); + getDataView().setInt32(offset + 4, size, true); + getDataView().setInt32(offset, _data, true); + }, + __wbg_origin_305402044aa148ce: function () { + return applyToWindow(function (offset, index) { + let _origin = parse( + get(index).origin, + wasm.__wbindgen_export_0, + wasm.__wbindgen_export_1 + ); + getDataView().setInt32(offset + 4, size, true); + getDataView().setInt32(offset, _origin, true); + }, arguments); + }, + __wbg_length_8a9352f7b7360c37: function (index) { + return get(index).length; + }, + __wbg_get_c30ae0782d86747f: function (index) { + let _image = get(index).image; + return isNull(_image) ? 0 : addToStack(_image); + }, + __wbg_timeOrigin_f462952854d802ec: function (index) { + return get(index).timeOrigin; + }, + __wbg_instanceof_Window_cee7a886d55e7df5: function () { + return true; + }, + __wbg_document_eb7fd66bde3ee213: function (index) { + let _document = get(index).document; + return isNull(_document) ? 0 : addToStack(_document); + }, + __wbg_location_b17760ac7977a47a: function (index) { + return addToStack(get(index).location); + }, + __wbg_performance_4ca1873776fdb3d2: function (index) { + let _performance = get(index).performance; + return isNull(_performance) ? 0 : addToStack(_performance); + }, + __wbg_origin_e1f8acdeb3a39a2b: function (offset, index) { + let _origin = parse( + get(index).origin, + wasm.__wbindgen_export_0, + wasm.__wbindgen_export_1 + ); + getDataView().setInt32(offset + 4, size, true); + getDataView().setInt32(offset, _origin, true); + }, + __wbg_get_8986951b1ee310e0: function (index, decode1, decode2) { + let data = get(index)[decodeSub(decode1, decode2)]; + return isNull(data) ? 0 : addToStack(data); + }, + __wbg_setTimeout_6ed7182ebad5d297: function () { + return applyToWindow(function () { + return 7; + }, arguments); + }, + __wbg_self_05040bd9523805b9: function () { + return applyToWindow(function () { + return addToStack(fake_window); + }, arguments); + }, + __wbg_window_adc720039f2cb14f: function () { + return applyToWindow(function () { + return addToStack(fake_window); + }, arguments); + }, + __wbg_globalThis_622105db80c1457d: function () { + return applyToWindow(function () { + return addToStack(fake_window); + }, arguments); + }, + __wbg_global_f56b013ed9bcf359: function () { + return applyToWindow(function () { + return addToStack(fake_window); + }, arguments); + }, + __wbg_newnoargs_cfecb3965268594c: function (index, offset) { + return addToStack(new Function(decodeSub(index, offset))); + }, + __wbindgen_object_clone_ref: function (index) { + return addToStack(get(index)); + }, + __wbg_eval_c824e170787ad184: function () { + return applyToWindow(function (index, offset) { + let fake_str = "fake_" + decodeSub(index, offset); + let ev = eval(fake_str); + return addToStack(ev); + }, arguments); + }, + __wbg_call_3f093dd26d5569f8: function () { + return applyToWindow(function (index, index2) { + return addToStack(get(index).call(get(index2))); + }, arguments); + }, + __wbg_call_67f2111acd2dfdb6: function () { + return applyToWindow(function (index, index2, index3) { + return addToStack(get(index).call(get(index2), get(index3))); + }, arguments); + }, + __wbg_set_961700853a212a39: function () { + return applyToWindow(function (index, index2, index3) { + return Reflect.set(get(index), get(index2), get(index3)); + }, arguments); + }, + __wbg_buffer_b914fb8b50ebbc3e: function (index) { + return addToStack(get(index).buffer); + }, + __wbg_newwithbyteoffsetandlength_0de9ee56e9f6ee6e: function ( + index, + val, + val2 + ) { + return addToStack(new Uint8Array(get(index), val >>> 0, val2 >>> 0)); + }, + __wbg_newwithlength_0d03cef43b68a530: function (length) { + return addToStack(new Uint8Array(length >>> 0)); + }, + __wbg_new_b1f2d6842d615181: function (index) { + return addToStack(new Uint8Array(get(index))); + }, + __wbg_buffer_67e624f5a0ab2319: function (index) { + return addToStack(get(index).buffer); + }, + __wbg_length_21c4b0ae73cba59d: function (index) { + return get(index).length; + }, + __wbg_set_7d988c98e6ced92d: function (index, index2, val) { + get(index).set(get(index2), val >>> 0); + }, + __wbindgen_debug_string: function () {}, + __wbindgen_throw: function (index, offset) { + throw new Error(decodeSub(index, offset)); + }, + __wbindgen_memory: function () { + return addToStack(wasm.memory); + }, + __wbindgen_closure_wrapper117: function (Qn, QT) { + return addToStack(args(Qn, QT, 2, export3)); + }, + __wbindgen_closure_wrapper119: function (Qn, QT) { + return addToStack(args(Qn, QT, 2, export4)); + }, + __wbindgen_closure_wrapper121: function (Qn, QT) { + return addToStack(args(Qn, QT, 2, export5)); + }, + __wbindgen_closure_wrapper123: function (Qn, QT) { + let test = addToStack(args(Qn, QT, 9, export4)); + return test; + }, + }, + }; + return wasmObj; +} + +function assignWasm(resp) { + wasm = resp.exports; + (dataView = null), (memoryBuff = null), wasm; +} + +function QZ(QP) { + let Qn; + return ( + (Qn = initWasm()), + QP instanceof WebAssembly.Module || (QP = new WebAssembly.Module(QP)), + assignWasm(new WebAssembly.Instance(QP, Qn)) + ); +} + +async function loadWasm(url) { + let mod, buffer; + return ( + (mod = initWasm()), + ({ + instance: url, + module: mod, + bytes: buffer, + } = ((url = fetch(url)), void 0, await QN(await url, mod))), + assignWasm(url), + buffer + ); +} + +const grootLoader = { + groot: function () { + wasm.groot(); + }, +}; + +let wasmLoader = Object.assign(loadWasm, { initSync: QZ }, grootLoader); + +const V = async (url) => { + let Q0 = await wasmLoader(url); + fake_window.bytes = Q0; + try { + wasmLoader.groot(); + } catch (error) { + console.error("error: ", error); + } + fake_window.jwt_plugin(Q0); + return fake_window.navigate(); +}; + +const getMeta = async (url) => { + let resp = await fetch(url, { + headers: { + UserAgent: user_agent, + Referrer: referrer, + }, + }); + let txt = await resp.text(); + let regx = /name="j_crt" content="[A-Za-z0-9]*/g; + let match = txt.match(regx)[0]; + let content = match.slice(match.lastIndexOf('"') + 1); + meta.content = content + "=="; +}; + +const i = (a, P) => { + try { + for (let Q0 = 0; Q0 < a.length; Q0++) { + a[Q0] = a[Q0] ^ P[Q0 % P.length]; + } + } catch (Q1) { + return null; + } +}; + +const M = (a, P) => { + try { + var Q0 = cryptoJs.AES.decrypt(a, P); + return JSON.parse(Q0.toString(cryptoJs.enc.Utf8)); + } catch (Q1) { + var Q0 = cryptoJs.AES.decrypt(a, P); + } + return []; +}; + +function z(a) { + return [ + (a & 4278190080) >> 24, + (a & 16711680) >> 16, + (a & 65280) >> 8, + a & 255, + ]; +} + +const decryptSource = async (embed_url) => { + referrer = embed_url.includes("mega") + ? `https://${v4_base_url}` + : new URL(embed_url).origin; + // let regx = /([A-Z])\w+/; + let xrax = embed_url.split("/").pop().split("?").shift(); + // regx = /https:\/\/[a-zA-Z0-9.]*/; + // let base_url = embed_url.match(regx)[0]; + const base_url = new URL(embed_url).origin; + nodeList.image.src = base_url + "/images/image.png?v=0.0.9"; + let data = new Uint8ClampedArray((await pixels(nodeList.image.src)).data); + image_data.data = data; + let test = embed_url.split("/"); + let browser_version = 1676800512; + canvas.baseUrl = base_url; + fake_window.origin = base_url; + fake_window.location.origin = base_url; + fake_window.location.href = embed_url; + fake_window.xrax = xrax; + fake_window.G = xrax; + + await getMeta(embed_url); + + let Q5 = await V(base_url + "/images/loading.png?v=0.0.9"); + + let getSourcesUrl = ""; + + if (base_url.includes("mega")) { + getSourcesUrl = + base_url + + "/" + + test[3] + + "/ajax/" + + test[4] + + "/getSources?id=" + + fake_window.pid + + "&v=" + + fake_window.localStorage.kversion + + "&h=" + + fake_window.localStorage.kid + + "&b=" + + browser_version; + } else { + getSourcesUrl = + base_url + + "/ajax/" + + test[3] + + // "/" + + // test[4] + + "/getSources?id=" + + fake_window.pid + + "&v=" + + fake_window.localStorage.kversion + + "&h=" + + fake_window.localStorage.kid + + "&b=" + + browser_version; + } + + let { data: resp } = await axios.get(getSourcesUrl, { + headers: { + "User-Agent": user_agent, + Referrer: embed_url + "&autoPlay=1&oa=0&asi=1", + "Accept-Language": "en,bn;q=0.9,en-US;q=0.8", + "sec-ch-ua": + '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', + "sec-ch-ua-mobile": "?1", + "sec-ch-ua-platform": '"Android"', + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Site": "same-origin", + "X-Requested-With": "XMLHttpRequest", + "Sec-Fetch-Mode": "cors", + }, + }); + + let Q3 = fake_window.localStorage.kversion; + let Q1 = z(Q3); + Q5 = new Uint8Array(Q5); + let Q8 = resp.t != 0 ? (i(Q5, Q1), Q5) : ((Q8 = resp.k), i(Q8, Q1), Q8); + let str = btoa(String.fromCharCode.apply(null, new Uint8Array(Q8))); + var decryptedSource = M(resp.sources, str); + resp.sources = decryptedSource; + return resp; +}; + +export default async function decryptMegacloud(id, name, type) { + try { + const { data: sourcesData } = await axios.get( + // `https://${v1_base_url}/ajax/v2/episode/sources?id=${id}` + `https://${v4_base_url}/ajax/episode/sources?id=${id}` + ); + const source = await decryptSource(sourcesData.link); + return { + id: id, + type: type, + link: source.sources[0], + tracks: source.tracks, + intro: source.intro, + outro: source.outro, + server: name, + iframe: sourcesData.link, + }; + } catch (error) { + console.error("Error during decryption:", error); + } +} diff --git a/src/parsers/idFetch_v1.parser.js b/src/parsers/idFetch_v1.parser.js new file mode 100644 index 0000000..c90e56b --- /dev/null +++ b/src/parsers/idFetch_v1.parser.js @@ -0,0 +1,29 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v1_base_url } from "../utils/base_v1.js"; + +export async function fetchServerData_v1(id) { + try { + const { data } = await axios.get( + `https://${v1_base_url}/ajax/v2/episode/servers?episodeId=${id}` + ); + const $ = cheerio.load(data.html); + + const serverData = $("div.ps_-block > div.ps__-list > div.server-item") + .filter((_, ele) => { + const name = $(ele).find("a.btn").text(); + return name === "HD-1" || name === "HD-2"; + }) + .map((_, ele) => ({ + name: $(ele).find("a.btn").text(), + id: $(ele).attr("data-id"), + type: $(ele).attr("data-type"), + })) + .get(); + + return serverData; + } catch (error) { + console.error("Error fetching server data:", error); + return []; + } +} diff --git a/src/parsers/idFetch_v2.parser.js b/src/parsers/idFetch_v2.parser.js new file mode 100644 index 0000000..9cdc2e5 --- /dev/null +++ b/src/parsers/idFetch_v2.parser.js @@ -0,0 +1,29 @@ +import axios from "axios"; +import * as cheerio from "cheerio"; +import { v2_base_url } from "../utils/base_v2.js"; + +export async function fetchServerData_v2(id) { + try { + const { data } = await axios.get( + `https://${v2_base_url}/ajax/episode/servers?episodeId=${id}` + ); + const $ = cheerio.load(data.html); + + const serverData = $("div.ps_-block > div.ps__-list > div.server-item") + .filter((_, ele) => { + const name = $(ele).find("a.btn").text().trim(); + return name === "Vidcloud"; + }) + .map((_, ele) => ({ + name: $(ele).find("a.btn").text().trim(), + id: $(ele).attr("data-id"), + type: $(ele).attr("data-type"), + })) + .get(); + + return serverData; + } catch (error) { + console.error("Error fetching server data:", error); + return []; + } +} diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js new file mode 100644 index 0000000..31d74c4 --- /dev/null +++ b/src/routes/apiRoutes.js @@ -0,0 +1,89 @@ +import * as homeInfoController from "../controllers/homeInfo.controller.js"; +import * as categoryController from "../controllers/category.controller.js"; +import * as topTenController from "../controllers/topten.controller.js"; +import * as animeInfoController from "../controllers/animeInfo.controller.js"; +import * as streamController from "../controllers/streamInfo.controller.js"; +import * as searchController from "../controllers/search.controller.js"; +import * as episodeListController from "../controllers/episodeList.controller.js"; +import * as suggestionsController from "../controllers/suggestion.controller.js"; +import * as scheduleController from "../controllers/schedule.controller.js"; +import * as serversController from "../controllers/servers.controller.js"; +import * as randomController from "../controllers/random.controller.js"; +import * as qtipController from "../controllers/qtip.controller.js"; +import * as randomIdController from "../controllers/randomId.controller.js"; +import * as producerController from "../controllers/producer.controller.js"; +import * as characterListController from "../controllers/voiceactor.controller.js"; +import * as nextEpisodeScheduleController from "../controllers/nextEpisodeSchedule.controller.js"; +import { routeTypes } from "./category.route.js"; +import { getWatchlist } from "../controllers/watchlist.controller.js"; +import getVoiceActors from "../controllers/actors.controller.js"; +import getCharacter from "../controllers/characters.controller.js"; +import * as filterController from "../controllers/filter.controller.js"; +import getTopSearch from "../controllers/topsearch.controller.js"; + +export const createApiRoutes = (app, jsonResponse, jsonError) => { + const createRoute = (path, controllerMethod) => { + app.get(path, async (req, res) => { + try { + const data = await controllerMethod(req, res); + if (!res.headersSent) { + return jsonResponse(res, data); + } + } catch (err) { + console.error(`Error in route ${path}:`, err); + if (!res.headersSent) { + return jsonError(res, err.message || "Internal server error"); + } + } + }); + }; + + ["/api", "/api/"].forEach((route) => { + app.get(route, async (req, res) => { + try { + const data = await homeInfoController.getHomeInfo(req, res); + if (!res.headersSent) { + return jsonResponse(res, data); + } + } catch (err) { + console.error("Error in home route:", err); + if (!res.headersSent) { + return jsonError(res, err.message || "Internal server error"); + } + } + }); + }); + + routeTypes.forEach((routeType) => + createRoute(`/api/${routeType}`, (req, res) => + categoryController.getCategory(req, res, routeType) + ) + ); + + createRoute("/api/top-ten", topTenController.getTopTen); + createRoute("/api/info", animeInfoController.getAnimeInfo); + createRoute("/api/episodes/:id", episodeListController.getEpisodes); + createRoute("/api/servers/:id", serversController.getServers); + createRoute("/api/stream", (req, res) => streamController.getStreamInfo(req, res, false)); + createRoute("/api/stream/fallback", (req, res) => streamController.getStreamInfo(req, res, true)); + createRoute("/api/search", searchController.search); + createRoute("/api/filter", filterController.filter); + createRoute("/api/search/suggest", suggestionsController.getSuggestions); + createRoute("/api/schedule", scheduleController.getSchedule); + createRoute( + "/api/schedule/:id", + nextEpisodeScheduleController.getNextEpisodeSchedule + ); + createRoute("/api/random", randomController.getRandom); + createRoute("/api/random/id", randomIdController.getRandomId); + createRoute("/api/qtip/:id", qtipController.getQtip); + createRoute("/api/producer/:id", producerController.getProducer); + createRoute( + "/api/character/list/:id", + characterListController.getVoiceActors + ); + createRoute("/api/watchlist/:userId{/:page}", getWatchlist); + createRoute("/api/actors/:id", getVoiceActors); + createRoute("/api/character/:id", getCharacter); + createRoute("/api/top-search", getTopSearch); +}; diff --git a/src/routes/category.route.js b/src/routes/category.route.js new file mode 100644 index 0000000..d5078c6 --- /dev/null +++ b/src/routes/category.route.js @@ -0,0 +1,87 @@ +export const routeTypes = [ + "genre/action", + "genre/adventure", + "genre/cars", + "genre/comedy", + "genre/dementia", + "genre/demons", + "genre/drama", + "genre/ecchi", + "genre/fantasy", + "genre/game", + "genre/harem", + "genre/historical", + "genre/horror", + "genre/isekai", + "genre/josei", + "genre/kids", + "genre/magic", + "genre/martial-arts", + "genre/mecha", + "genre/military", + "genre/music", + "genre/mystery", + "genre/parody", + "genre/police", + "genre/psychological", + "genre/romance", + "genre/samurai", + "genre/school", + "genre/sci-fi", + "genre/seinen", + "genre/shoujo", + "genre/shoujo-ai", + "genre/shounen", + "genre/shounen-ai", + "genre/slice-of-life", + "genre/space", + "genre/sports", + "genre/super-power", + "genre/supernatural", + "genre/thriller", + "genre/vampire", + "top-airing", + "most-popular", + "most-favorite", + "completed", + "recently-updated", + "recently-added", + "top-upcoming", + "subbed-anime", + "dubbed-anime", + "movie", + "special", + "ova", + "ona", + "tv", + "music", + "az-list", + "az-list/other", + "az-list/0-9", + "az-list/a", + "az-list/b", + "az-list/c", + "az-list/d", + "az-list/e", + "az-list/f", + "az-list/g", + "az-list/h", + "az-list/i", + "az-list/j", + "az-list/k", + "az-list/l", + "az-list/m", + "az-list/n", + "az-list/o", + "az-list/p", + "az-list/q", + "az-list/r", + "az-list/s", + "az-list/t", + "az-list/u", + "az-list/v", + "az-list/w", + "az-list/x", + "az-list/y", + "az-list/z", +]; diff --git a/src/routes/filter.maping.js b/src/routes/filter.maping.js new file mode 100644 index 0000000..a5af9d6 --- /dev/null +++ b/src/routes/filter.maping.js @@ -0,0 +1,129 @@ +const FILTER_LANGUAGE_MAP = { + ALL: '', + SUB: '1', + DUB: '2', + SUB_DUB: '3' +}; + +const GENRE_MAP = { + ACTION: '1', + ADVENTURE: '2', + CARS: '3', + COMEDY: '4', + DEMENTIA: '5', + DEMONS: '6', + DRAMA: '8', + ECCHI: '9', + FANTASY: '10', + GAME: '11', + HAREM: '35', + HISTORICAL: '13', + HORROR: '14', + ISEKAI: '44', + JOSEI: '43', + KIDS: '15', + MAGIC: '16', + MARTIAL_ARTS: '17', + MECHA: '18', + MILITARY: '38', + MUSIC: '19', + MYSTERY: '7', + PARODY: '20', + POLICE: '39', + PSYCHOLOGICAL: '40', + ROMANCE: '22', + SAMURAI: '21', + SCHOOL: '23', + SCI_FI: '24', + SEINEN: '42', + SHOUJO: '25', + SHOUJO_AI: '26', + SHOUNEN: '27', + SHOUNEN_AI: '28', + SLICE_OF_LIFE: '36', + SPACE: '29', + SPORTS: '30', + SUPER_POWER: '31', + SUPERNATURAL: '37', + THRILLER: '41', + VAMPIRE: '32' + +}; + +const FILTER_TYPES = { + ALL: '', + MOVIE: '1', + TV: '2', + OVA: '3', + ONA: '4', + SPECIAL: '5', + MUSIC: '6' +}; + +const FILTER_STATUS = { + ALL: '', + FINISHED: '1', + CURRENTLY_AIRING: '2', + NOT_YET_AIRED: '3' +}; + +const FILTER_RATED = { + ALL: '', + G: '1', + PG: '2', + PG_13: '3', + R: '4', + R_PLUS: '5', + RX: '6' +}; + +const FILTER_SCORE = { + ALL: '', + APPALLING: '1', + HORRIBLE: '2', + VERY_BAD: '3', + BAD: '4', + AVERAGE: '5', + FINE: '6', + GOOD: '7', + VERY_GOOD: '8', + GREAT: '9', + MASTERPIECE: '10' +}; + +const FILTER_SEASON = { + ALL: '', + SPRING: '1', + SUMMER: '2', + FALL: '3', + WINTER: '4' +}; + +const FILTER_LANGUAGE = { + ALL: '', + SUB: '1', + DUB: '2', + SUB_DUB: '3' +}; + +const FILTER_SORT = { + DEFAULT: 'default', + RECENTLY_ADDED: 'recently_added', + RECENTLY_UPDATED: 'recently_updated', + SCORE: 'score', + NAME_AZ: 'name_az', + RELEASED_DATE: 'released_date', + MOST_WATCHED: 'most_watched' +}; + +export { + FILTER_LANGUAGE_MAP, + GENRE_MAP, + FILTER_TYPES, + FILTER_STATUS, + FILTER_RATED, + FILTER_SCORE, + FILTER_SEASON, + FILTER_LANGUAGE, + FILTER_SORT +}; diff --git a/src/utils/base_v1.js b/src/utils/base_v1.js new file mode 100644 index 0000000..c5afa33 --- /dev/null +++ b/src/utils/base_v1.js @@ -0,0 +1 @@ +export const v1_base_url = "hianime.do"; diff --git a/src/utils/base_v2.js b/src/utils/base_v2.js new file mode 100644 index 0000000..891b96b --- /dev/null +++ b/src/utils/base_v2.js @@ -0,0 +1 @@ +export const v2_base_url = "kaido.to"; diff --git a/src/utils/base_v3.js b/src/utils/base_v3.js new file mode 100644 index 0000000..49561bb --- /dev/null +++ b/src/utils/base_v3.js @@ -0,0 +1 @@ +export const v3_base_url = "aniplay.lol"; diff --git a/src/utils/base_v4.js b/src/utils/base_v4.js new file mode 100644 index 0000000..2ee4369 --- /dev/null +++ b/src/utils/base_v4.js @@ -0,0 +1 @@ +export const v4_base_url = "9animetv.to"; diff --git a/src/utils/fallback.js b/src/utils/fallback.js new file mode 100644 index 0000000..b63ae81 --- /dev/null +++ b/src/utils/fallback.js @@ -0,0 +1,2 @@ +export const fallback_1="megaplay.buzz" +export const fallback_2="vidwish.live" \ No newline at end of file diff --git a/src/utils/provider.js b/src/utils/provider.js new file mode 100644 index 0000000..44aa0ee --- /dev/null +++ b/src/utils/provider.js @@ -0,0 +1 @@ +export const provider = "https://megacloud.club"; diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..9c294e2 --- /dev/null +++ b/vercel.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "builds": [ + { + "src": "server.js", + "use": "@vercel/node" + } + ], + "routes": [ + { + "src": "/(.*)", + "dest": "server.js", + "headers": { + "Access-Control-Allow-Origin": "*" + } + } + ] +}